【ARC080F】Prime Flip(二分图匹配,差分)

这种区间反转的题,套路就是差分。

ai 表示第 i 枚硬币是否正面朝上,显然只有 ax1,ax2,,axn 等于 1,其他都是 0。那么我们的目标是把 a 数组全部变成 0

bi 表示第 i 枚硬币和第 i1 枚硬币是否不同,即 bi=aixorai1。那么我们的目标就变成了把 b 数组全部变成 0,即让每个数都相同。

prime 为所有奇数质数的集合,pprime,那么一次反转可以看做将 aiai+p1 全部取反。

发现将 aiai+p1 取反后其实等价于 bibi+p 两个数取反。

所以现在操作就变成了:任意选择两个正整数 i,j,且满足 jiprime,然后将 bibj 取反。

然后在所有值为枚举满足 bi=bj=1 的任意一对 i,ji<j),考虑如何操作才能将 bibj 都变为 0 且操作数最少,分情况讨论:

  1. jiprime。此时我们可以直接将 bibj 变为 0,共 1 次操作。

  2. ji 为正偶数。

    那么当 ji=2 时,我们可以对 bibi+5 取反,然后再对 bi+2bi+5 取反,共 2 次操作;

    ji=4 时,我们可以对 bibi+7 取反,然后再对 bi+4bi+7 取反,共 2 次操作;

    ji 为其他正偶数时,由哥德巴赫猜想在 107 范围内的正确性,可知 ji 可以分为两个奇质数的和。即存在 p1+p2=ji,且 p1,p2prime。那么我们可以对 bibi+p1 取反,然后再对 bi+p1bi+p1+p2 取反,共 2 次操作。

    综述,当 ji 为正偶数时,最小的操作数都是 2 次。(2)

  3. ji 为除 prime 以外的正奇数。

    ji=1 时,我们可以对 bibi+7 取反,然后对 bi+4bi+7 取反,对 bi+1bi+4 取反,共 3 次操作。

    类似地,可知当 ji=3 时,也存在一种方案且最小操作数为 3

    ji 为其他正奇数时,即 ji>3ji 为奇数时,我们可以将 ji 分解成 “3+正偶数” 的形式,然后再由 (2) 得此时最少的操作数为 3 次。

    综述,当 ji 为除 prime 以外的正奇数时,最小的操作数都是 3 次。

所以为了使得操作数最少,我们应该优先使用第 1 种情况。

设有 kbi=1,并且将他们的下标用集合 S={c1,c2,,ck} 表示。

那么 k 肯定是偶数,因为考虑将 ai 中连续的 1 当做一个块。那么对于每个块 aheadatail,块头贡献一个 bhead=1,块尾贡献一个 btail+1,所以总贡献就是偶数个。

发现如果有 cicjprime,那么 cicj 的奇偶性必定不同。

于是想到将 ci 奇偶分类,并对于所有的 cicjprime,连边 (i,j),那么这就是一个二分图的形式。

显然对这个二分图跑最大匹配就是第 1 种情况的最多使用数。

将这些点取反之后,还剩下一些点需要取反,于是考虑使用第 2 种情况。

显然,若 cicj 为正偶数,那么 cicj 的奇偶性相同。

所以将奇类中剩下的 ci 进行两两取反,将偶类中剩下的 ci 进行两两取反。

然后再考虑第 3 种情况,此时奇类和偶类肯定要么都剩下 0 个未取反、要么都剩下 1 个未取反,因为 k 为偶数。然后对于各剩下 1 个未取反的情况下,将他们两个按第 3 种情况处理。

3 种情况的总操作数加起来,就是最后的答案了。

代码如下:

#include<bits/stdc++.h>

#define N 110
#define INF 0x7fffffff

using namespace std;

int n,a[N];
int tot,odd,b[N<<1];
int cnt=1,head[N<<1],cur[N<<1],nxt[N*N*8+N*4],to[N*N*8+N*4],c[N*N*8+N*4];
int s,t,num[N<<1];

queue<int>q;

void adde(int u,int v,int ci)
{
	to[++cnt]=v;
	c[cnt]=ci;
	nxt[cnt]=head[u];
	head[u]=cnt;
	
	to[++cnt]=u;
	c[cnt]=0;
	nxt[cnt]=head[v];
	head[v]=cnt;
}

bool check(int x)//判断一个数是否属于prime
{
	if(x<=2) return false;
	if(!(x&1)) return false;
	for(int i=2,maxn=sqrt(x);i<=maxn;i++)
		if(!(x%i)) return false;
	return true;
}

bool bfs()
{
	memcpy(cur,head,sizeof(cur));
	memset(num,-1,sizeof(num));
	q.push(s);
	num[s]=0;
	while(!q.empty())
	{
		int u=q.front();
		q.pop();
		for(int i=head[u];i;i=nxt[i])
		{
			int v=to[i];
			if(c[i]&&num[v]==-1)
			{
				num[v]=num[u]+1;
				q.push(v);
			}
		}
	}
	return num[t]!=-1;
}

int dfs(int u,int minflow)
{
	if(u==t||!minflow) return minflow;
	int preflow=0,nowflow;
	for(int i=cur[u];i;i=nxt[i])
	{
		cur[u]=i;
		int v=to[i];
		if(num[v]==num[u]+1&&(nowflow=dfs(v,min(c[i],minflow-preflow))))
		{
			preflow+=nowflow;
			c[i]-=nowflow;
			c[i^1]+=nowflow;
			if(!(minflow-preflow)) break;
		}
	}
	return preflow;
}

int dinic()//用dinic跑最大匹配
{
	int maxflow=0;
	while(bfs())
		maxflow+=dfs(s,INF);
	return maxflow;
}

int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	b[++tot]=a[1];
	for(int i=2;i<=n;i++)
	{
		if(a[i]-a[i-1]>1)
		{
			b[++tot]=a[i-1]+1;//块尾贡献
			b[++tot]=a[i];//块头贡献
		}
	}
	b[++tot]=a[n]+1;
	s=1,t=1+tot+1;
	for(int i=1;i<=tot;i++)
	{
		if(b[i]&1)
		{
			odd++;
			adde(s,1+i,1);
			for(int j=1;j<=tot;j++)
				if(check(abs(b[i]-b[j]))) adde(1+i,1+j,1);
		}
		else adde(1+i,t,1);
	}
	int sum1=dinic();
	printf("%d\n",sum1+(odd-sum1)/2*2+((tot-odd)-sum1)/2*2+((odd-sum1)&1)*3);
	return 0;
}
posted @   ez_lcw  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示