\(\rm S\)

题目描述

\(n\) 个球,每个球有 RGY 三种颜色,\(\sf Oxide\) 觉得如果有两个相邻的球颜色相同很丑。
她每次可以交换两个球,问至少交换多少次才能不丑。

\(1\le n\le 400\)

解法

拿到这道题真的毫无思路……有一个很重要的结论是:无论怎样交换,相同颜色的球的相对位置不会改变。因为交换相同颜色的球显然是不优的。

这样问题就可以转化为:在长度为 \(n\) 的序列中填上三种颜色,每种颜色数量固定,填颜色的价值定义为 "逆序对的个数",且相邻位置颜色不同。具体而言,将原序列编号,第 \(i\)\(j\) 种球就对应它位置上的编号 \(\rm id\)。填完颜色后,第 \(i\)\(j\) 种球有了新的位置 \(k\),就令 \(p_k=\rm id\),答案就是 \(p\) 中逆序对个数。

\(dp_{i,j,k,0/1/2}\) 表示三种颜色分别填到 \(i,j,k\) 个,位置 \(i+j+k\) 的颜色是 \(0/1/2\)。颜色是用来判断相邻颜色是否相同的。复杂度 \(\mathcal O(n^3)\),第一维需要滚动一下。

代码

#pragma GCC optimize(2)
#include <cstdio>
#define print(x,y) write(x),putchar(y)
 
template <class T>
inline T read(const T sample) {
    T x=0; char s; bool f=0;
    while((s=getchar())>'9' or s<'0')
        f |= (s=='-');
    while(s>='0' and s<='9')
        x = (x<<1)+(x<<3)+(s^48),
        s = getchar();
    return f?-x:x;
}
 
template <class T>
inline void write(const T x) {
    if(x<0) {
        putchar('-');
        write(-x);
        return;
    }
    if(x>9) write(x/10);
    putchar(x%10^48);
}

#include <cstring>
#include <iostream>
using namespace std;

const int maxn = 405;
const int inf = 0x3f3f3f3f;

int dp[2][maxn][maxn][3],n;
int pre[maxn][3],pos[maxn][3];
char s[maxn];

int add(int p,int j,int k,int b,int c) {
	int ret=0;
	if(pre[p][b]-j>0) ret += pre[p][b]-j;
	if(pre[p][c]-k>0) ret += pre[p][c]-k;
	return ret; 
}

int Sardine() {
	for(int i=1;i<=n;++i) {
		for(int j=0;j<3;++j)
			pre[i][j]=pre[i-1][j];
		if(s[i]=='R') {
			++pre[i][0];
			pos[pre[i][0]][0] = i;
		}
		else if(s[i]=='G') {
			++pre[i][1];
			pos[pre[i][1]][1] = i;
		}
		else {
			++pre[i][2];
			pos[pre[i][2]][2] = i;
		}
	}
	memset(dp,0x3f,sizeof dp);
	for(int i=0;i<3;++i)
		dp[0][0][0][i]=0;
	for(int i=0;i<=pre[n][0];++i) {
		bool d=(i&1); int mn;
		for(int j=0;j<=pre[n][1];++j)
			for(int k=0;k<=pre[n][2];++k) {
				if(!i and !j and !k) continue;
				
				for(int o=0;o<3;++o) dp[d][j][k][o]=inf;
				
				mn = inf;
				for(int o=1;o<3;++o) mn = min(mn,dp[d^1][j][k][o]);
				if(mn^inf) dp[d][j][k][0] = mn+add(pos[i][0],j,k,1,2);
				
				if(j) {
					mn = inf;
					for(int o=0;o<3;++o) 
						if(o^1)
							mn = min(mn,dp[d][j-1][k][o]);
					if(mn^inf) dp[d][j][k][1] = mn+add(pos[j][1],i,k,0,2);
				}
				
				if(k) {
					mn = inf;
					for(int o=0;o<2;++o) mn = min(mn,dp[d][j][k-1][o]);
					if(mn^inf) dp[d][j][k][2] = mn+add(pos[k][2],i,j,0,1);
				}
			}
	}
	int ans = inf;
	for(int i=0;i<3;++i)
		ans = min(ans,dp[pre[n][0]&1][pre[n][1]][pre[n][2]][i]);
	return ans==inf?-1:ans;
}

int main() {
	n=read(9); scanf("%s",s+1);
	print(Sardine(),'\n');
	return 0;
}

\(\rm Y\)

题目描述

\(n\) 个人站成一个圈,每个人有 \(a_i\) 个球,下面要进行一次操作:

  • 每个人把一些它的球交给它左边的人,所有人 同时 进行。

假设操作后每个人有 \(b_i\) 个球,记这个序列为 \(B\)。对于每 \(B\),它的价值为 \(∏b_i\)
对于所有可能的 \(B\),你要计算它们的价值和。

\(1\le n\le 10^6,0\le a_i\le 10^9\)

解法

神仙 \(\mathtt{dp}\) 题,属于不看题解一辈子也想不出来的范畴。考试的时候大家都喊着做过做过,我也感觉自己好像做过……结束后一看 \(\rm AtCoder\),我那一场从这题开始就妹补了……

小提示:这个 \(\mathtt{dp}\) 也理解了蛮久的,表述时难免有些冗长。


首先可以思考一下 \(B\) 最终的种数,这对正解也有启发:发现对于传球序列 \(C\),如果它差分之后相等,最终形成的 \(B\) 就是相同的 —— 所以我们不妨只计算 \(\min C_i=0\) 的传球序列。种数是 \(\prod_{i=1}^n(a_i+1)-\prod_{i=1}^na_i\),一个容斥。

现在要计算 \(\prod b_i\),它的组合意义是 "对于每一 \(B\),每个人在自己最终的球中选择一个球的方案数,且 球不同"。但实际上,当最终的球数(\(b_i\))相同时,即使球的来源不同,编号不同,由计算式我们仍认为这两种方案相同!也就是说,球不同只建立在最终的 \(b_i\) 个球内部。这也是不能直接 \(\mathtt{dp}\) 的原因,这里的 "球不同" 和我们往常的理解有偏差,称往常的理解为 "严格的"。

不妨就先按 球严格不同\(\mathtt{dp}\),这其实就是 "曲线救国" 的思想,因为对于每一种 \(B\) 我们并不好计算贡献,但是对于 每种传球序列 就好算了,这是可以融合到转移方程里的。如何去重?类似上面的容斥,我们钦定传球序列不能有零,同样地 \(\mathtt{dp}\) 一次即可。

\(dp_{i,0/1}\) 分别为 "第 \(i\) 个人在原先球中选择,前 \(i-1\) 个人选球" 的选球方案数;"第 \(i\) 个人在第 \(i-1\) 人给的球中选择,前 \(i\) 个人选球" 的选球方案数。可以发现,因为是同时传球,所以第 \(i\) 个人不会因为得到的球影响传球数量。至于状态为什么如此鬼畜,可以看看后面的转移:

  • \(dp_{i,0}\leftarrow dp_{i-1,0}\)。此时需要计算 \(i-1\) 的选球方案。考虑 \(i-1\) 的传球方案:如果 \(i-1\)\(a_i-a\) 个球,就剩下 \(a\) 个球,他的选择方案为 \(a\) 种,所以系数就是 \(\sum_{j=1}^{a_{i-1}}j\)
  • \(dp_{i,0}\leftarrow dp_{i-1,1}\)。考虑 \(i-1\) 的传球方案,系数为 \(a_i+1\)
  • \(dp_{i,1}\leftarrow dp_{i-1,1}\)。考虑 \(i-1\) 的传球方案:如果 \(i-1\)\(a\) 个球,\(i\) 的选择方案为 \(a\) 种,所以系数就是 \(\sum_{j=1}^{a_{i-1}}j\)
  • \(dp_{i,1}\leftarrow dp_{i-1,0}\)。考虑 \(i-1\) 的传球方案:如果 \(i-1\)\(a_i-a\) 个球,就剩下 \(a\) 个球,二人在此种传球方案中的球选择方案为 \(a(a_i-a)\) 种,所以系数就是 \(\sum_{j=1}^{a_i}j(a_i-j)=a_i\sum_{j=1}^{a_i}j-\sum_{j=1}^{a_i}j^2\).

现在可以解答状态如此鬼畜的问题了:假设第 \(i\) 个人在原先球中选择,那么他的传球数就会影响选择的方案,所以我们不妨在计算传球数时顺带计算选球方案;而如果在第 \(i-1\) 人给的球选择则相反,必须在此时计算选球方案。

完了?由于这是一个环,所以需要枚举第一个人的 \(0/1\) 状态。如枚举 \(0\) 状态,就 \(dp_{1,0}\leftarrow 1,dp_{1,1}\leftarrow 0\),最后答案是 \(dp_{1,0}-1\)(减去最开始加的 \(1\))。

总共是 \(\mathcal O(n)\) 的。

代码

#pragma GCC optimize(2)
#include <cstdio>
#define print(x,y) write(x),putchar(y)
 
template <class T>
inline T read(const T sample) {
    T x=0; char s; bool f=0;
    while((s=getchar())>'9' or s<'0')
        f |= (s=='-');
    while(s>='0' and s<='9')
        x = (x<<1)+(x<<3)+(s^48),
        s = getchar();
    return f?-x:x;
}
 
template <class T>
inline void write(const T x) {
    if(x<0) {
        putchar('-');
        write(-x);
        return;
    }
    if(x>9) write(x/10);
    putchar(x%10^48);
}

#include <cstring>

const int maxn = 1e6+5;
const int mod = 1e9+7;

int n,a[maxn],dp[maxn][2],inv2,inv6;

inline int adj(int x,int y) {
	return x+y>=mod?x+y-mod:(x+y<0?x+y+mod:x+y); 
}

inline int inv(int x,int y=mod-2) {
	int r=1;
	while(y) {
		if(y&1) r=1ll*r*x%mod;
		x=1ll*x*x%mod; y>>=1;
	}
	return r;
}

int s1(int x) {
	return 1ll*x*(x+1)%mod*inv2%mod;
}

int s2(int x) {
	return 1ll*x*(x+1)%mod*(x*2+1)%mod*inv6%mod;
}

int DP(int O,int zero) { 
	memset(dp,0,sizeof dp);
	dp[1][0]=O,dp[1][1]=O^1;
	for(int i=1;i<=n;++i) {
		int to = i%n+1;
		dp[to][0] = adj(dp[to][0],1ll*dp[i][0]*s1(a[i]-zero)%mod);
		dp[to][0] = adj(dp[to][0],1ll*dp[i][1]*(a[i]-zero+1)%mod);
		dp[to][1] = adj(dp[to][1],1ll*dp[i][0]*adj(1ll*a[i]*s1(a[i])%mod,-s2(a[i]))%mod);
		dp[to][1] = adj(dp[to][1],1ll*dp[i][1]*s1(a[i])%mod);
	}
	return adj(O?dp[1][0]:dp[1][1],-1);
}

int main() {
	n=read(9);
	inv2 = inv(2), inv6 = inv(6);
	for(int i=1;i<=n;++i)
		a[i]=read(9);
	print(adj(adj(DP(0,0),DP(1,0)),-adj(DP(0,1),DP(1,1))),'\n');
	return 0;
}

彩蛋

还有一个 矩阵 的做法,但我真的懒得看了。咕咕咕!

posted on 2021-10-19 21:05  Oxide  阅读(153)  评论(0编辑  收藏  举报