把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【AT2689】[ARC080D] Prime Flip(二分图匹配)

题目链接

  • 有无限枚硬币,除了 \(n\) 个位置 \(x_{1\sim n}\) 上的硬币正面朝上以外,其余硬币都反面朝上。
  • 每次可以翻转一个长度为奇素数的区间,求至少操作多少次可以将所有硬币翻为背面。
  • \(1\le n\le 100\)\(1\le x_1 < x_2 < \cdots < x_n\le10^7\)

经典图论转化

关于这种问题有一个比较经典的转化。

考虑对区间 \([l,r]\) 进行一次异或操作,相当于分别对区间 \([1,l-1]\) 和区间 \([1,r]\) 进行异或操作,也就是给 \(l-1\)\(r\) 两个位置打上异或标记。

最后我们相当于要给 \(x_1-1,x_1,x_2-1,x_2,\cdots,x_n-1,x_n\) 打上异或标记(相同的需要除去)。

一次异或操作打上两个标记,相同的标记可以抵消。也就是说连续对 \((a,b)\)\((b,c)\)\((c,d)\) 操作等价于对 \((a,d)\) 操作。如果在一次操作对应的两点 \(x,y\) 间连一条边,那么若干次关联的异或操作可以看作一条路径(如前面的例子可以看作 \(a\rightarrow b\rightarrow c\rightarrow d\))。

所以我们就是要将需要打标记的点两两配对,求出最短路之和的最小值。

路径长度讨论

根据哥德巴赫猜想,大于等于 \(4\) 的偶数可以表示为两个奇素数之和,大于等于 \(3\) 的奇数可以表示为不超过三个奇素数之和。

又由于此题中不光可以加还可以减,\(2\) 可以表示为 \(5-3\)\(1\) 可以表示为 \(3+5-7\)

综上,可以得到:

  • 若两点差为奇素数,它们之间的距离为 \(1\)
  • 若两点差为偶数,它们之间的距离为 \(2\)
  • 若两点差为不是素数的奇数,它们之间的距离为 \(3\)

我们先尽可能匹配出第一种差值,这肯定是在偶数点和奇数点之间,也就是一张二分图,可以直接跑二分图最大匹配。

剩下若干点,首先考虑在二分图同侧两两配对,如果还有剩余再异侧配对一下。

代码:\(O(n^3)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Rg register
#define RI Rg int
#define Cn const
#define CI Cn int&
#define I inline
#define W while
#define N 100
#define V 10000000
using namespace std;
int n,ca,cb,a[N+5],b[N+5],Pt,P[V+5],f[V+5],s[N+5],vis[N+5];
I void Sieve() {for(RI i=2,j;i<=V;++i) for(!f[i]&&(P[++Pt]=i),j=1;i*P[j]<=V;++j) if(f[i*P[j]]=1,!(i%P[j])) break;f[1]=1;}//线性筛
I bool Match(CI x,CI ti) {for(RI i=1;i<=cb;++i) if(!f[abs(a[x]-b[i])]&&vis[i]^ti&&(vis[i]=ti,!s[i]||Match(s[i],ti))) return s[i]=x;return 0;}//二分图匹配
int main()
{
	RI i,j,x;for(Sieve(),scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&x),
		x&1?((!cb||b[cb]^(x-1))?b[++cb]=x-1:--cb,a[++ca]=x):((!ca||a[ca]^(x-1))?a[++ca]=x-1:--ca,b[++cb]=x);//x-1和x需要打标记
	RI t=0;for(i=1;i<=ca;++i) Match(i,i)&&++t;return printf("%d\n",t+(ca+cb-2*t>>1)*2+(ca-t&1)*(3-2)),0;//二分图匹配,剩余先同侧配对,再异侧配对
}
posted @ 2021-11-18 20:02  TheLostWeak  阅读(54)  评论(0编辑  收藏  举报