Atcoder Beginner Contest 200 F - Minflip Summation 题解

传送门

网上看到一个挺妙的解法


题意

规定一个 \(01\) 串的价值为其最少需要的区间 \(01\) 翻转次数,使得其成为同色的串。

初始给定一个含有 \(01\) 与若干 \(?\) 字符的初始串 \(S\) ,把 \(K\)\(S\) 收尾相接得到串 \(T\) 。求将 \(T\) 中每个 \(?\) 都换为 \(0\)\(1\) 时,所有串的价值总和。答案对 \(10^9+7\) 取模。


分析

先不考虑 \(?\) 的限制。

考虑将串收尾相接,变成一个环。不难想到,环上的 \(01\) “脉冲”个数一定是偶数。且稍加观察可以得到,当“脉冲”数量为 \(n\) 时,最少需要的区间翻转次数就是 \({n\over 2}\)

由于等效于一个“脉冲”的贡献是 \({1\over 2}\),由于其总数为偶数,所以正确性没有影响。

\(K\) 个串收尾相连时,只需要算出第一个串的“脉冲”贡献,然后直接乘 \(K\) 即可。


考虑 \(?\) 的限制。

当出现 \(?\) 时,不妨记为 \(s_i=?\) 。(\(s_0=s_n\wedge s_{n+1}=s_1\)

则从概率角度,\(s_i\)\({1\over 2}\) 的概率是 \(0\),有 \({1\over 2}\) 的概率是 \(1\)

\(s_{i-1}\neq ?\) 时,统计量中,一半的 \(s_{i-1}s_i\) 贡献“有脉冲”,为 \({1\over 2}\) ;一半的 \(s_{i-1}s_i\) 贡献“无脉冲”,为 \(0\)

等效于一个 \(s_{i-1}s_i\) 的贡献为 \({1\over 4}\) ,同样由于出现 \(?\) 时,总数为偶数,所以正确性没有影响。

同理可得,\(s_{i+1}\neq ?\) 时一个 \(s_is_{i+1}\) 的贡献也为 \({1\over 4}\)

而出现 \(s_is_{i+1}=??\) 时,从概率角度,\(00, 01, 10, 11\) 的出现概率都是 \({1\over 4}\);因此有无脉冲的概率仍均是 \({1\over 2}\) ,贡献同样也为 \({1\over 4}\)

所以同样当 \(K\) 个串相连时,只需算出第一个串的“脉冲”贡献和 \(?\) 贡献,然后直接乘 \(K\cdot 2^{Kq}\) 算出答案。


代码

记得特判 \(K=1, |S|=1\) 的情况即可。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
typedef pair<ll, ll> pii;
const int MOD=1e9+7;
inline ll fpow(ll a,ll x) { ll ans=1; for(;x;x>>=1,a=a*a%MOD) if(x&1) ans=ans*a%MOD; return ans; }
inline ll sumq(const string &s) { ll ans=0; for(auto c : s) ans+=(c=='?'); return ans; }
inline ll sumf(const string &s){
    ll ans=0;
    char lstc=s.back();
    for(int i=0;i<s.size();lstc=s[i], ++i)
        if(lstc=='?'||s[i]=='?') ++ans;
        else if(lstc!=s[i]) ans+=2;
    return ans*fpow(4, MOD-2)%MOD;
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    string s;
    ll k;
    cin>>s>>k;
    cout<<fpow(2, k*sumq(s))*k%MOD*sumf(s)%MOD*(s.size()>1||k>1);
    cout.flush();
    return 0;
}

感想

第一次见识到这种用概率和期望反过来求统计的方式,非常的 amazing 啊!

说起来,莫名其妙想到量子计算的纠缠态

posted @ 2021-05-29 22:38  JustinRochester  阅读(110)  评论(0编辑  收藏  举报