Atcoder 好题
ARC193C Grid Coloring 3
题面:
给你一个 \(n\times m\) 的网格初始全没有被染色,每次可以选择 \((x,y,c),1\leq x\leq n,1\leq y\leq m,1\leq c\leq C\) 将,第 \(x\) 行和第 \(y\) 列全部染成第 \(c\) 种颜色(覆盖之前的颜色),可以操作任意次,问最终有多少种不同的染色方案。\(n,m<=400,C<=1e9\)
题解:
因为后面染色能覆盖前面的颜色,所以要将操作序列时光倒流,现在变成每次染还没有颜色的格子,有多少种方案。
在第一次操作(翻转操作序列前的最后一次操作)染了一个十字之后,钦定后面的每一次操作中心 \((x,y)\) 必须已经被染过色,那第 \(x\) 行和第 \(y\) 列中必有一个已经全部染色了,那么操作就变成了每次给一行或者一列染一个颜色(分别记为 \(A,B\) 操作),显然最终的答案是不变的。
但这时直接枚举每次操作是啥仍然不太可做,原因是对于操作序列中两次连续的 \(A\) 操作或 \(B\) 操作,交换他们的操作顺序不影响答案,所以会算重。由于 \(A\) 和 \(A\) 之间互不干扰,\(B\) 和 \(B\) 之间也是,这启发我们每次可以同时操作操作序列中一段极长的 \(A\) 或 \(B\),显然这样转化后答案仍然是对的,这就好做了。
将第一段特殊处理,枚举最终有 \(i\) 行 \(j\) 列都是第一次操作的颜色,方案数是 \(C\binom{n}{i}\binom{m}{j}\),然后进行 \(dp\)。
先设一个大概的雏形 \(f_{n,m,* }\) 表示给 \(n\times m\) 的网格染色的答案。
在此之后的下一个极长段中,显然不能有一行或一列的颜色和第一次操作的颜色一样,于是我们将 \(dp\) 改成 \(f_{n,m,t,* }\) 表示有 \(t\) 中颜色不能作为一行或一列全部相同时的颜色,显然 \(t\in\{0,1\}\)。
考虑下一个极长段,不妨设其是 \(A\) 操作的极长段,设此次操作了 \(r\) 行,方案数是 \((C-t)^r\),后续操作中不再有 \(t\) 的限制,所以从 \(f_{n-r,m,0,* }\) 转移过来。
但是为了保证这一段 \(A\) 是极长的,要求剩下的 \(n-r\) 行中不能有某一行的颜色全部相同,列同理,所以要记 \(f_{n,m,t,x,y}\),\(x,y\) 分别表示当前是否限定每一行(列)的颜色不能完全相同。发现 \(x,y\) 两个限制最多同时存在一个(如果同时存在两个的话就没有能操作的东西了)所以我们可以只记一个(下面只记列是否有限制),通过交换行列数来限定是行还是列,例如上边的转移被写作 \(f_{n,m,t,0}\leftarrow f_{m,n-r,0,1}\)。
然后是 \(f_{n,m,t,1}\) 的转移,要求是没有一列的颜色全相同。分两类讨论,如果这 \(r\) 行本身的颜色就有不同的,肯定符合要求,方案数是 \((C-t)^r-(C-t)\),从 \(f_{m,n-r,0,1}\) 转移过来。如果这 \(r\) 行都相同,那要求这列其余部分不能全都是当前的这种颜色,这个状态就是 \(f_{m,n-r,1,1}\) 的限制,方案数是 \((C-t)\)。
最后答案是 \(f_{n-i,m-j,1,0}+f_{m-j,n-i,1,1}\) 即考虑执行完最终第一次操作的那些之后的第一个极长段时 \(A\) 还是 \(B\)。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;inline ll read(){ll s=0,k=1;char c=getchar();while(c>'9'||c<'0'){if(c=='-') k=-1;c=getchar();}while(c>='0'&&c<='9'){s=(s<<3)+(s<<1)+(c^48);c=getchar();}return s*k;
}const int N=405,mod=998244353;
ll fac[2][N],f[N][N][2][2],C[N][N],K;
int n,m;void Add(ll &x,ll y){x+=y;if(x>=mod) x-=mod;
}ll dfs(int n,int m,int t,int c){if(f[n][m][t][c]>=0) return f[n][m][t][c];ll res=fac[t][n];if(c) Add(res,mod-(K-t));for(int i=1;i<n;i++){ll tmp=0;if(!c) Add(tmp,fac[t][i]*dfs(m,n-i,0,1)%mod);else{Add(tmp,(K-t)*dfs(m,n-i,1,1)%mod);Add(tmp,(fac[t][i]-(K-t)+mod)%mod*dfs(m,n-i,0,1)%mod);}Add(res,tmp*C[n][i]%mod);}return f[n][m][t][c]=res;
}int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);n=read();m=read();K=read();int S=max(n,m);K%=mod;if(K==0) K=mod;for(int i=0;i<=S;i++){C[i][0]=1;for(int j=1;j<=i;j++){C[i][j]=C[i-1][j];Add(C[i][j],C[i-1][j-1]);}}fac[0][0]=fac[1][0]=1;for(int i=1;i<=S;i++){fac[0][i]=fac[0][i-1]*K%mod;fac[1][i]=fac[1][i-1]*(K-1)%mod;}memset(f,-1,sizeof(f));ll ans=1;for(int i=1;i<n;i++)for(int j=1;j<m;j++)Add(ans,C[n][i]*C[m][j]%mod*(dfs(n-i,m-j,1,0)+dfs(m-j,n-i,1,1))%mod);(ans*=K)%=mod;printf("%lld",ans);return 0;
}
ARC193D Magnets
题面:
给两个长为 \(n\) 的 \(01\) 序列 \(A,B\),每次可以选择一个位置让每个 \(1\) 都向选的位置靠近一位,若一个位置上有多个 \(1\) 认为是一个。问最少多少次操作能让 \(A\) 序列和 \(B\) 序列一样,或报告无解。\(n\leq 1e6\)
题解:
观察操作发现,如果我们操作在所有 \(1\) 前或所有 \(1\) 后,可以起到一个平移的效果,即 \(A\) 中所有 \(1\) 的相对位置不变,这启示我们算出 \(A,B\) 的差分数组 \(da,db\)。问题转化为要把 \(A\) 的差分数组变成和 \(B\) 的差分数组一样(删去所有 \(da_i=0\))。
考虑一次操作会导致 \(da\) 发生什么变化:如果操作在两个 \(1\) 中间,会导致 \(da_i-2\)(称为 \(X_i\) 操作);如果操作在某个 \(1\) 上,会导致 \(da_i,da_{i+1}\) 都减少 \(1\)(称为 \(Y_i\) 操作)。特别的,如果操作在第一个或最后一个 \(1\) 上,只会令 \(da_1-1\) 或 \(da_m-1\)。
先不考虑这两个特殊的操作,那么每次操作都会让 \(da\) 的总和 \(-2\),操作次数的总和就是 \(\frac{\sum da_i-\sum db_i}{2}\)。我们让 \(da_i\) 依次和 \(db_j\) 匹配,如果 \(da_i>db_j\) 那么我们一直 \(da_i-2\) 直到 \(da_i==db_j+1\) 我们让 \(da_i-1,da_{i+1}-1\),然后匹配 \(da_{i+1},db_{j+1}\),否则如果 \(da_i<db_j\) 就把 \(da_i\) 减成零。如果最后 \(db_j\) 有剩下的没有配上或者 \(da_{m+1}\) 被 \(-1\) 了就不合法,我们就需要通过特殊操做尽量让他合法并保证次数最小。
要求最小次数,那我们只要执行尽量少的特殊操作就行了,因为别的操作都是 \(-2\) 而特殊操作是 \(-1\)。
有结论,特殊操做只会在开头和结尾各最多做一次。
如果最开始一下做了两次特殊操作(不妨设是在开头做的),那么可以将其改为一个 \(X\) 操作并向左平移一位,这样更改后没有变化操作数,连绝对位置都没有变化,可以替换。
如果匹配了某个 \(da\) 和 \(db\) 的前缀后 \(da_i\) 现在成了队头并对其做了一次特殊操作,那么考虑对 \(da_{i-1}\) 执行的操作,如果执行了 \(X_{i-1}\),可以替换为 \(Y_{i-1}\) 和对 \(i-1\) 是队头时的特殊操作,如果执行了 \(Y\) 操作类似,这样我们就把 \(i\) 这个位置的特殊操作前移了,以此类推,我们可以全部都移到最开始的位置做。
所以这题的做法就是枚举首尾是否进行特殊操作,然后按上述方式检测是否合法,同时统计操作次数。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;inline ll read(){ll s=0,k=1;char c=getchar();while(c>'9'||c<'0'){if(c=='-') k=-1;c=getchar();}while(c>='0'&&c<='9'){s=(s<<3)+(s<<1)+(c^48);c=getchar();}return s*k;
}const int N=1e6+5,inf=1e9+7;
int n;
char s[N],t[N];int solve(int pl,int pr,vector<int>A,vector<int>B){if(pl) for(int i=1;i<A.size();i++) A[i]--;if(pr) for(int i=0;i<A.size()-1;i++) A[i]++;vector<int>dx,dy;for(int i=1;i<A.size();i++)if(A[i]>A[i-1]) dx.push_back(A[i]-A[i-1]);for(int i=1;i<B.size();i++)if(B[i]>B[i-1]) dy.push_back(B[i]-B[i-1]);if(dy.size()>dx.size()) return inf;int j=0,op=0,ans=0;for(int i=0;i<dx.size();i++){dx[i]-=op;int d=dx[i];if(j<dy.size()&&dx[i]>=dy[j]){d-=dy[j];j++;}ans+=d+1>>1;op=d&1;}if(j<dy.size()||op) return inf;return ans+abs(A[0]+ans-B[0])+pl+pr;
}int main(){
// freopen(".in","r",stdin);
// freopen(".out","w",stdout);int T=read();while(T--){n=read();scanf("%s",s+1);scanf("%s",t+1);vector<int>A,B;for(int i=1;i<=n;i++){if(s[i]=='1') A.push_back(i);if(t[i]=='1') B.push_back(i);}if(B.size()>A.size()){puts("-1");continue;}if(A.size()==1){printf("%d\n",abs(A[0]-B[0]));continue;}int ans=inf;for(int i=0;i<2;i++)for(int j=0;j<2;j++)ans=min(ans,solve(i,j,A,B));if(ans>inf/2) puts("-1");else printf("%d\n",ans);}return 0;
}