【AtCoder Regular Contest 080 F】Prime Flip

二分图匹配,哥德巴赫猜想。

题目大意:

给定一个长度为 $10^7$ 的 01 序列 $a$,有 $n$ 个位置为 1,其余位置为 0

每次可以将一段长度为奇质数的区间的 0 变成 11 变成 0

问至少操作多少次,才能使这个序列变为全 0

题解:

我们令 $b_i=a_i\mathrm{\ xor\ }a_{i-1}$,然后对 $a_i$ 的区间反转,就变成了对 $b_i$ 的两个间隔为奇质数的位置进行反转。

由于题目强调了质数,所以我们将 1 出现的位置按照奇偶分类。

然后考虑两点的距离,令其为 $d$。

  • $d$ 为奇质数。那么我们只需要一次操作就可以让这两个点反转。
  • $d$ 为偶数。根据哥德巴赫猜想,当 $d\gt 4$ 时,一定能表示为两个奇质数相加($10^7$ 以内没有反例)。而 $4=7-3,2=5-3$,所以 $d$ 为偶数时需要 $2$ 次。
  • $d$ 为奇合数。根据哥德巴赫猜想,当 $d\geq 9$ 时,一定能表示为三个奇质数的和。所以 $d$ 为奇合数需要 $3$ 次。

我们需要操作次数尽可能少,那么让奇质数距离匹配最多,剩下的偶数距离匹配。最后如果奇数、偶数各剩下一个,则奇合数匹配。

这里不用考虑拿一个奇质数和一个奇合数换两个合数匹配,因为代价是相等的。

奇质数匹配的部分,连边跑二分图最大匹配即可。偶数匹配的部分,对剩余的奇数个数和偶数个数直接计算即可。奇合数匹配部分,看最后是否还有剩余即可。

Code:

#include<iostream>
#include<cstdlib>
#include<vector>
#include<cstring>
using namespace std;
const int N=205,M=1e7+10;
int n,a[N],pri[M/10],tot,ans;
bool vis[M],b[M],ur[N];
int ld[N],rd[N],cl,cr,even,odd;
int lf[N];
vector<int>G[N];
void sieve(int n){
    vis[1]=1;
    for(int i=2;i<=n;++i){
        if(!vis[i])pri[++tot]=i;
        for(int j=1;j<=tot&&i*pri[j]<=n;++j){
            vis[i*pri[j]]=1;
            if(i%pri[j]==0)break;
        }
    }
}
bool dfs(int u){
    for(int to:G[u])
    if(!ur[to]){
        ur[to]=1;
        if(!lf[to]||dfs(lf[to])){
            lf[to]=u;
            return 1;
        }
    }
    return 0;
}
int main(){
    ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;++i){
        cin>>a[i];
        b[a[i]]=1;
    }
    for(int i=10000001;i;--i)if(b[i]^=b[i-1]){
        if(i&1)ld[++cl]=i;else rd[++cr]=i;
    }
    sieve(10000000);
    for(int i=1;i<=cl;++i)
    for(int j=1;j<=cr;++j)if(!vis[abs(ld[i]-rd[j])])G[i].push_back(j);
    memset(lf,0,sizeof lf);
    for(int i=1;i<=cl;++i){
        memset(ur,0,sizeof ur);
        ans+=dfs(i);
    }
    memset(ur,0,sizeof ur);
    for(int i=1;i<=cr;++i)ur[lf[i]]=1;
    for(int i=1;i<=cl;++i)if(!ur[i])++odd;
    for(int i=1;i<=cr;++i)if(!lf[i])++even;
    ans+=(odd>>1<<1)+(even>>1<<1)+(odd&1)*3;
    cout<<ans<<'\n';
    return 0;
}