\(\rm S\)
题目描述
有 \(n\) 个球,每个球有 R
、G
、Y
三种颜色,\(\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;
}
彩蛋
还有一个 矩阵 的做法,但我真的懒得看了。咕咕咕!