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;
}
posted @ 2024-02-27 17:26  maple276  阅读(17)  评论(0编辑  收藏  举报