Codeforces1286F Harry The Potter【转换 + 子集卷积】

题目描述:

在这里插入图片描述
n20,ai1015n\le20,|a_i|\le10^{15}

题目分析:

将操作2看做一条边,如果最后的方案连出了一个环,那么将这个环上所有的边换成操作1不会更劣。

所以最优方案一定是操作二连成森林,然后剩下的点用操作一。

因为一棵树可以减少1次操作,所以我们想要分出尽量多的连通块。

考虑怎么判断集合SS能否使用S1S-1操作二清零:

随便选一个点作根,假设每条边两端减掉的值都是xx,从叶子开始依次减,那么减到根的时候将根此时的权值用变量表示,那么与根深度奇偶性相同的点的变量带的符号就是正号,相异的就是负号:
在这里插入图片描述
我们希望根节点的值为0,那么就是 奇数深度节点的权值和 = 偶数深度节点的权值和。

然后考虑每条边可以给两端的任意一端+1,总共有S1S-1条边可以用,那么SS集合可以用操作二清零的条件就是可以将SS分为两个非空集合A,BA,B,满足abs(sum[A]sum[B])<S  sum[S]S1abs(sum[A]-sum[B])<|S|~且~sum[S]的奇偶性与S-1相同

判断一个集合SS是否满足上面的条件,可以O(3n)O(3^n),但是太慢。问题相当于是将SS中的数带上正负号,考虑折半然后合并。设S的左半部分的所有状态为sl[]sl[],右半部分的所有状态为sr[]sr[],因为只需要判断是否存在解,所以先将slslsrsr分别排序(这个可以在求的时候排好),然后设一个指针RR指向srsr的最右端,LL指向slsl的最左端,如果sl[L]+sr[R]<(S1)sl[L]+sr[R]<-(S-1),那么L++L++,知道满足条件后,forfor循环LL判断是否满足sl[L]+sr[R]<S1sl[L]+sr[R]<S-1。(这一部分结合代码理解一下)

这样对于一个状态SScheckcheck的复杂度就是O(2S2)O(2^{\frac {|S|}2}),求个和就是官方题解中的(1+2)n(1+\sqrt 2)^n

然后考虑怎么求答案,官方题解的做法是:如果SS可行,那么令AS=1A_S=1,然后对AApp次子集卷积,如果ApA^{p}中存在某一位不为0,说明存在一种分pp个集合的方案,于是要做的就是找到最小的pp满足ApA^p中所有位都为0,最终的答案就是n(p1)n-(p-1)

求这个pp可以考虑倍增,如果超过了就退,这样复杂度是O(2nn2logn)O(2^n*n^2\log n)的,还有点常数。

这样做显然比较麻烦,我们考虑暴力的子集卷积是O(3n)O(3^n)的,但是其实完全没有必要,对于一个由多个可行集合组成的SS,我们只需要在它最小的那个可行子集处更新它。所以只有当集合TT是可行集合,且不能由其它可行集合组合而成时,我们用它去更新f[TT]f[T\in T']。这样剪枝之后虽然复杂度没有什么保证,但是实际运行效果非常好,Codeforces上目前最快的写法大都是这样写的。

Code:

#include<bits/stdc++.h>
#define maxn 20
#define LL long long
using namespace std;
const int N = 1<<20|5;
int n,f[N];
LL a[maxn];
LL b[maxn],sl[N],sr[N];
void getque(LL *s,int l,int r){
	int m=1; s[1]=0;
	static LL pos[N],neg[N];
	for(int i=l;i<=r;i++,m<<=1){
		for(int j=1;j<=m;j++) pos[j]=s[j]+b[i],neg[j]=s[j]-b[i];
		for(int x=1,y=1,k=1;k<=m<<1;k++)
			s[k]=x>m?neg[y++]:y>m?pos[x++]:pos[x]<neg[y]?pos[x++]:neg[y++];
	}
}
bool check(int S){
	int sz=0; LL sum=0;
	for(int i=1;i<=n;i++) if(S>>i-1&1) sum+=a[i],b[++sz]=a[i];
	if(sum-(sz-1)&1) return 0;
	getque(sl,1,sz/2),getque(sr,sz/2+1,sz);
	int L=1<<(sz/2),R=1<<(sz-sz/2),need=1+(abs(sum)<sz)*2;
	for(int i=R,j=1;i>=1;i--){
		while(j<=L&&sl[j]+sr[i]<=-sz) j++;
		for(int k=j;k<=L&&need&&sl[k]+sr[i]<sz;k++) need--;
	}
	return !need;
}
int main()
{
	scanf("%d",&n); int m=0;
	for(int i=1;i<=n;i++) scanf("%lld",&a[i]),a[i]&&(a[++m]=a[i]);
	n=m; int all=(1<<n)-1;
	for(int s=1;s<=all;s++) if(!f[s]&&check(s)){//!f[s] can cut tons of situations.
		int r=all^s; f[s]=1;
		for(int t=r;t;--t&=r) f[s|t]=max(f[s|t],f[t]+1);
	}
	printf("%d\n",n-f[all]);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章