CF1615F:LEGOndary Grandmaster 题解

1615F:

题意:定义两个01串的距离是使两串完全相同的操作次数,每次操作可以把两个相邻且相同的数翻转(不能相互到达距离为0),现给你两个带问号的01串,对于问号替换的所有情况,求两串距离之和。(n<=2000)

Solution:

这是一道没有知识积累就无从下手的题。

本题有两个关键转化,逐渐把原本离散型的问题转化为连续性的问题。

第一个转化:奇数位翻转。原来操作是两个相同的数可以一起翻转,这个操作是比较难考虑的,因为0和1的数量在不断变化。而我们先将所有奇数位都翻转,你会发现之后的每次操作从“相邻且相同的数”变成了“相邻且不同的数”翻转,也就是相邻01交换位置,这个题意转换使得方案非常直观,因为0和1的数量不变,每次操作可以挪动一个位置,两串距离也就是把所有1对齐的挪动步数,若1数量不同即为不可相互到达。

第二个转化:用每一位来表示挪动步数的代价。原本我们用 xi 表示 s 串第 i 个 1 的位置,yi 表示 t 串第 i 个 1 的位置,s 与 t 距离即为 |xiyi| ,但这不利于接下来的统计,因为我们每个问号具体填什么不确定,我们需要把它转化为更加连续性的问题。有个冷门结论:设 aisi 前缀和,biti 前缀和 (就是 i 之前 '1' 的个数),则 |xiyi|=i=1n|aibi|

为什么等式右边的求和有下标,因为我们把贡献转化到了每一个位置上,我们列举每个位置可能的 |aibi| 情况,求出 n 个位置贡献的和即为答案。

n2 的dp很好求每个位置的 |aibi| 情况,pre[i][j] 表示填前 i 位,使 aibi=j 的情况数,只需要枚举每个问号填什么,然后从上一位转移过来。当然,为了让这种情况合法,后 ni 位的填写需要满足后缀合和前缀和相反,才能满足‘1’的数量相等这一前提,我们还需要求出 suf[i][j] ,这也是为什么dp状态中 aibi 没有加绝对值。

代码有点丑,别指望读了哈哈。

const ll p=1000000007; ll T; ll n,B; char s[N]; char z[N]; ll pre[N][N],suf[N][N]; void chushihua() { for(ll i=0;i<=n+1;i++) for(ll j=0;j<=2*n;j++) pre[i][j] = suf[i][j] = 0; } inline bool check(ll j) { return (j>=0 && j<=2*n); } int main() { T = read(); while(T--) { chushihua(); n = read(); B = n; scanf("%s",s+1); scanf("%s",z+1); for(ll i=1;i<=n;i++) { if(s[i]!='?' && (i&1)) s[i] = (s[i]=='1')?'0':'1'; if(z[i]!='?' && (i&1)) z[i] = (z[i]=='1')?'0':'1'; } pre[0][B] = 1; for(ll i=1;i<=n;i++) { for(ll j=0;j<=2*B;j++) { if(s[i]=='?' && z[i]=='?') { (pre[i][j] += 2ll * pre[i-1][j] %p) %= p; if(check(j+1)) (pre[i][j+1] += pre[i-1][j]) %= p; if(check(j-1)) (pre[i][j-1] += pre[i-1][j]) %= p; } else if(s[i]=='?') { (pre[i][j] += pre[i-1][j]) %= p; if(z[i]=='1') { if(check(j-1)) (pre[i][j-1] += pre[i-1][j]) %= p; } else { if(check(j+1)) (pre[i][j+1] += pre[i-1][j]) %= p; } } else if(z[i]=='?') { (pre[i][j] += pre[i-1][j]) %= p; if(s[i]=='1') { if(check(j+1)) (pre[i][j+1] += pre[i-1][j]) %= p; } else { if(check(j-1)) (pre[i][j-1] += pre[i-1][j]) %= p; } } else { if(check(j+(s[i]-z[i]))) (pre[i][j+(s[i]-z[i])] += pre[i-1][j]) %= p; } } } suf[n+1][B] = 1; for(ll i=n;i>=1;i--) { for(ll j=0;j<=2*B;j++) { if(s[i]=='?' && z[i]=='?') { (suf[i][j] += 2ll * suf[i+1][j] %p) %= p; if(check(j+1)) (suf[i][j+1] += suf[i+1][j]) %= p; if(check(j-1)) (suf[i][j-1] += suf[i+1][j]) %= p; } else if(s[i]=='?') { (suf[i][j] += suf[i+1][j]) %= p; if(z[i]=='1') { if(check(j-1)) (suf[i][j-1] += suf[i+1][j]) %= p; } else { if(check(j+1)) (suf[i][j+1] += suf[i+1][j]) %= p; } } else if(z[i]=='?') { (suf[i][j] += suf[i+1][j]) %= p; if(s[i]=='1') { if(check(j+1)) (suf[i][j+1] += suf[i+1][j]) %= p; } else { if(check(j-1)) (suf[i][j-1] += suf[i+1][j]) %= p; } } else { if(check(j+(s[i]-z[i]))) (suf[i][j+(s[i]-z[i])] += suf[i+1][j]) %= p; } } } ll ans = 0; for(ll i=1;i<=n;i++) { for(ll j=0;j<=2*n;j++) { ll k = 2*n-j; (ans += abs(j-B) * pre[i][j] %p * suf[i+1][k] %p) %= p; } } cout<<(ans%p+p)%p<<endl; } return 0; }

1845E:

补充一个类似的题,这题每次操作可以交换相邻两个01的位置,问正好K次操作后不同的01序列个数。n,K<=1500。

n^3的dp很好设计,f[i][j][k] 表示前 i 个数, b 的前缀和为 j,|aibi|=k 的方案数。

然后发现其实第二维可以缩小到根号,只需要把 j 的定义改为:aibi=j ,改成差值而不是b的值,因为相邻两个位置 j 的改变量不超过 1,因此要想让 sum 小于 k ,j 的值就不能超过根号。

复杂度 O(nkn)

#include <algorithm> #include <iostream> #include <cstring> #include <cstdio> #include <cmath> #define FOR() ll le=e[u].size();for(ll i=0;i<le;i++) #define QWQ cout<<"QwQ\n"; #define ll long long #include <vector> #include <queue> #include <map> using namespace std; const ll N=1888; const ll qwq=303030; const ll inf=0x3f3f3f3f; const ll p=1000000007; ll T; ll n,m,K; ll ans,a[N]; ll f[2][122][N]; ll base = 61; inline ll read() { ll sum = 0, ff = 1; char c = getchar(); while(c<'0' || c>'9') { if(c=='-') ff = -1; c = getchar(); } while(c>='0'&&c<='9') { sum = sum * 10 + c - '0'; c = getchar(); } return sum * ff; } int main() { n = read(); K = read(); m = 60; for(ll i=1;i<=n;i++) a[i] = read(); f[0][base][0] = 1; for(ll i=1;i<=n;i++) { ll cl = i&1; for(ll j=base-m;j<=base+m;j++) { for(ll k=0;k<=K;k++) { if(a[i]) (f[cl][j-1][k+abs(base-j+1)] += f[cl^1][j][k]) %= p; else (f[cl][j+1][k+abs(base-j-1)] += f[cl^1][j][k]) %= p; (f[cl][j][k+abs(base-j)] += f[cl^1][j][k]) %= p; } } for(ll j=base-m;j<=base+m;j++) { for(ll k=0;k<=K;k++) { f[cl^1][j][k] = 0; } } } for(ll i=K;i>=0;i-=2) { (ans += f[n&1][base][i]) %= p; } cout<<(ans%p+p)%p; return 0; }

__EOF__

本文作者枫叶晴
本文链接https://www.cnblogs.com/maple276/p/18037353.html
关于博主:菜菜菜
版权声明:呃呃呃
声援博主:呐呐呐
posted @   maple276  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示