[AtCoder Regular Contest 080] F: Prime Flip (arc080F)

原题链接
https://arc080.contest.atcoder.jp/tasks/arc080_d

Description

给出无限多的卡牌,从1开始顺序标号

现在有N个卡牌是正面朝上的,给出它们的标号,其他的都是反面朝上

你可以进行如下的操作
选择长度为奇质数的一段连续卡牌,将它们翻面

求最少需要多少次操作使得所有卡牌反面朝上

N<=100

Solution

这种翻面一段区间的题,套路就是差分

Fi F i 表示第i张牌和第i-1张牌状态是否相同,0表示相同,1表示不相同。特别的,令第0张牌反面朝上

那么现在的操作就是选择一个位置i,选择一个奇质数p,将第i个位置和第i+p个位置取反
求将全部变成0的最少次数

分类讨论
考虑消掉两个为1的位置 i,j i , j

如果 |ij| | i − j | 是奇质数,那么需要1次操作
如果 |ij| | i − j | 是偶数,那么根据哥德巴赫猜想,大于等于6的偶数都能分解为两个奇质数之和,那么只需要两次。2的话可以5-3得到,4可以7-3得到
如果 |ij| | i − j | 是奇合数,那么需要3次。一次将它变成偶数,然后同第二次

将所有的差分后的位置按照奇和偶分成两组
我们尽量要让一次操作的多

那么差为奇质数的连边,跑二分图最大匹配

剩下的尽量同组匹配(差为偶数)
最后每组最多剩下一个,直接匹配即可

因为差分的1总是两两出现,因此不存在单个的情况

Code

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define N 2005
using namespace std;
int l[N],n,a[N],r[N],w[N],pr[5000005],f[N],a1[N][N];
bool bz[20000005],bp[N];
void prp()
{
    bz[1]=1;
    bz[0]=1;
    fo(i,2,20000000)
    {
        if(!bz[i]) pr[++pr[0]]=i;
        for(int j=1;j<=pr[0]&&i*pr[j]<=20000000;j++)
        {
            bz[i*pr[j]]=1;
            if(i%pr[j]==0) break;
        }
    }
    bz[2]=1;
}
bool find(int k)
{
    fo(i,1,a1[k][0])
    {
        int p=a1[k][i];
        if(!bp[p])
        {
            bp[p]=1;
            if(!f[p]||find(f[p])) 
            {
                f[p]=k;
                return 1;
            }
        }
    }
    return 0;
}
int main()
{
    cin>>n;
    prp();
    fo(i,1,n) scanf("%d",&a[i]);
    sort(a+1,a+n+1);
    fo(i,1,n) 
    {
        if(i==1||a[i-1]!=a[i]-1) w[++w[0]]=a[i];
        if(a[i+1]!=a[i]+1) w[++w[0]]=a[i]+1;
    }
    sort(w+1,w+w[0]+1);
    fo(i,1,w[0]) 
        if(w[i]%2==0) l[++l[0]]=w[i];
        else r[++r[0]]=w[i];
    fo(i,1,l[0])
    {
        fo(j,1,r[0])
        {
            if(!bz[abs(l[i]-r[j])]) a1[i][++a1[i][0]]=j;
        }
    }
    long long s=0;
    fo(i,1,l[0]) 
    {
        memset(bp,0,sizeof(bp));
        if(find(i)) s++;
    }
    printf("%lld",s+2*(long long)((l[0]-s)/2)+2*(long long)((r[0]-s)/2)+3*(long long)((l[0]-s)%2));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值