( m S)
题目描述
有 (n) 个球,每个球有 R
、G
、Y
三种颜色,(sf Oxide) 觉得如果有两个相邻的球颜色相同很丑。
她每次可以交换两个球,问至少交换多少次才能不丑。
(1le nle 400)。
解法
拿到这道题真的毫无思路… 有一个很重要的结论是:无论怎样交换,相同颜色的球的相对位置不会改变。
这样问题就可以转化为:在长度为 (n) 的序列中填上三种颜色,每种颜色数量固定,填颜色的价值定义为 "逆序对的个数",且相邻位置颜色不同。具体而言,将原序列编号,第 (i) 个 (j) 种球就对应它位置上的编号 ( m id)。填完颜色后,第 (i) 个 (j) 种球有了新的位置 (k),就令 (p_k= m 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(),'
');
return 0;
}
( m Y)
题目描述
有 (n) 个人站成一个圈,每个人有 (a_i) 个球,下面要进行一次操作:
- 每个人把一些它的球交给它左边的人,所有人 同时 进行。
假设操作后每个人有 (b_i) 个球,记这个序列为 (B)。对于每 种 (B),它的价值为 (∏b_i)。
对于所有可能的 (B),你要计算它们的价值和。
(1le nle 10^6,0le a_ile 10^9)。
解法
神仙 (mathtt{dp}) 题,属于不看题解一辈子也想不出来的范畴。考试的时候大家都喊着做过做过,我也感觉自己好像做过… 结束后一看 ( m 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) 个人选球" 的选球方案数。至于状态为什么如此鬼畜,可以看看后面的转移:
- (dp_{i,0}leftarrow dp_{i-1,0})。此时需要计算 (i-1) 的选球方案。考虑 (i-1) 的传球方案:如果 (i-1) 传 (a_i-a) 个球,就剩下 (a) 个球,他的选择方案为 (a) 种,所以系数就是 (sum_{j=1}^{a_i}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}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_isum_{j=1}^{a_i}j-sum_{j=1}^{a_i}j^2)。
完了?由于这是一个环,所以需要枚举第一个人的 (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))),'
');
return 0;
}
彩蛋
还有一个 矩阵 的做法,但我真的懒得看了。咕咕咕…