原题链接
​​​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

如果|i−j| | i − j | 是奇质数,那么需要1次操作
如果|i−j| | i − j | 是偶数,那么根据哥德巴赫猜想,大于等于6的偶数都能分解为两个奇质数之和,那么只需要两次。2的话可以5-3得到,4可以7-3得到
如果|i−j| | 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));
}