CF1615F:LEGOndary Grandmaster 题解
1615F:
题意:定义两个01串的距离是使两串完全相同的操作次数,每次操作可以把两个相邻且相同的数翻转(不能相互到达距离为0),现给你两个带问号的01串,对于问号替换的所有情况,求两串距离之和。(n<=2000)
Solution:
这是一道没有知识积累就无从下手的题。
本题有两个关键转化,逐渐把原本离散型的问题转化为连续性的问题。
第一个转化:奇数位翻转。原来操作是两个相同的数可以一起翻转,这个操作是比较难考虑的,因为0和1的数量在不断变化。而我们先将所有奇数位都翻转,你会发现之后的每次操作从“相邻且相同的数”变成了“相邻且不同的数”翻转,也就是相邻01交换位置,这个题意转换使得方案非常直观,因为0和1的数量不变,每次操作可以挪动一个位置,两串距离也就是把所有1对齐的挪动步数,若1数量不同即为不可相互到达。
第二个转化:用每一位来表示挪动步数的代价。原本我们用 \(x_i\) 表示 s 串第 i 个 1 的位置,\(y_i\) 表示 t 串第 i 个 1 的位置,s 与 t 距离即为 \(\sum|x_i-y_i|\) ,但这不利于接下来的统计,因为我们每个问号具体填什么不确定,我们需要把它转化为更加连续性的问题。有个冷门结论:设 \(a_i\) 为 \(s_i\) 前缀和,\(b_i\) 为 \(t_i\) 前缀和 (就是 i 之前 '1' 的个数),则 \(\sum|x_i-y_i|=\sum\limits_{i=1}^n|a_i-b_i|\)。
为什么等式右边的求和有下标,因为我们把贡献转化到了每一个位置上,我们列举每个位置可能的 \(|a_i-b_i|\) 情况,求出 n 个位置贡献的和即为答案。
\(n^2\) 的dp很好求每个位置的 \(|a_i-b_i|\) 情况,\(pre[i][j]\) 表示填前 i 位,使 \(a_i-b_i=j\) 的情况数,只需要枚举每个问号填什么,然后从上一位转移过来。当然,为了让这种情况合法,后 \(n-i\) 位的填写需要满足后缀合和前缀和相反,才能满足‘1’的数量相等这一前提,我们还需要求出 \(suf[i][j]\) ,这也是为什么dp状态中 \(a_i-b_i\) 没有加绝对值。
代码有点丑,别指望读了哈哈。
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,\(\sum |a_i-b_i|=k\) 的方案数。
然后发现其实第二维可以缩小到根号,只需要把 j 的定义改为:\(a_i-b_i=j\) ,改成差值而不是b的值,因为相邻两个位置 j 的改变量不超过 1,因此要想让 sum 小于 k ,j 的值就不能超过根号。
复杂度 \(O(nk\sqrt n)\)
#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;
}