【CF1286F】Harry The Potter(折半,子集卷积)

首先若用了第二种操作,就在 ( i , j ) (i,j) (i,j) 之间连一条边。

发现图中不可能出现环,否则我们将这个环删掉,并用等同次数的一操作即可与环上原操作做到等价。

那么考虑一个数集 S = { a 1 , ⋯   , a k } S=\{a_1,\cdots,a_k\} S={a1,,ak} 有没有可能形成一条树而全部消掉。

考虑从下往上推,叶子 x x x 只有可能取它自己,而另一端有可能取 x ± 1 x\pm 1 x±1

从而倒数的某个点 y y y 第二层有可能取 y − ∑ x ∈ s o n ( y ) ( x ± 1 ) = y − ∑ x ∈ s o n ( y ) x + { − ∣ s o n ( y ) ∣ , − ∣ s o n ( y ) ∣ + 2 , ⋯   , ∣ s o n ( y ) ∣ − 2 , ∣ s o n ( y ) ∣ } y-\sum_{x\in son(y)}(x\pm 1)=y-\sum_{x\in son(y)}x+\{-|son(y)|,-|son(y)|+2,\cdots,|son(y)|-2,|son(y)|\} yxson(y)(x±1)=yxson(y)x+{son(y),son(y)+2,,son(y)2,son(y)}

最后我们要让根取 0 0 0,这等价于 0 ∈ ∑ d e p ( x )   m o d   2 = 1 x − ∑ d e p ( x )   m o d   2 = 0 x + { − ( k − 1 ) , − ( k − 1 ) + 2 , ⋯   , ( k − 1 ) − 2 , k − 1 } 0\in \sum_{dep(x)\bmod 2=1}x-\sum_{dep(x)\bmod 2=0}x+\{-(k-1),-(k-1)+2,\cdots,(k-1)-2,k-1\} 0dep(x)mod2=1xdep(x)mod2=0x+{(k1),(k1)+2,,(k1)2,k1}

最后得到等价条件是:

  • ∑ a i + ( k − 1 ) \sum a_i+(k-1) ai+(k1) 为偶数。
  • 能将 S S S 划分为两个非空集合 A , B A,B A,B,且 ∣ ∑ a ∈ A a − ∑ b ∈ B b ∣ ≤ k − 1 |\sum_{a\in A}a-\sum_{b\in B}b|\leq k-1 aAabBbk1

如果暴力地枚举 S , A S,A S,A 并检验是 O ( 3 n ) O(3^n) O(3n) 的。更好的做法是枚举 S S S,然后折半,两半分别求出 2 ∣ S ∣ 2 2^{\frac{|S|}{2}} 22S 种子集和并排序好(这里排序能边求边做),然后再双指针扫一下,时间复杂度 O ( 2 ∣ S ∣ 2 ) O(2^{\frac{|S|}{2}}) O(22S)。这部分总复杂度:
∑ i = 1 n ( n i ) 2 i / 2 = ( 1 + 2 ) n \sum_{i=1}^n\binom{n}{i}2^{i/2}=(1+\sqrt 2)^n i=1n(in)2i/2=(1+2 )n
然后原问题相当于要选尽量多的 S 1 , ⋯   , S m S_1,\cdots,S_m S1,,Sm 不交且它们都能被表示出来,直接 DP 是 O ( 3 n ) O(3^n) O(3n) 的。更好的做法是二分 m m m,然后转为 m m m 次幂的子集卷积,看是否有一位为 1 1 1。用倍增而非二分即可做到 O ( n 2 2 n log ⁡ n ) O(n^22^n\log n) O(n22nlogn)

卡常卡吐了。

#include<bits/stdc++.h>

#define ll long long

int main()
{
	int n;
	std::cin>>n;
	std::vector<ll> a;
	for(int i=0;i<n;i++)
	{
		ll x; std::cin>>x;
		if(x) a.push_back(x);
	}
	n=a.size();
	if(!n)
	{
		std::cout<<0;
		return 0;
	}
	int maxn=1<<n;
	std::vector<int> popc(maxn),f(maxn);
	for(int i=1;i<maxn;i++) popc[i]=popc[i>>1]+(i&1);
	for(int S=0;S<maxn;S++)
	{
		ll sum=0;
		std::vector<ll> b;
		for(int i=0;i<n;i++)
			if((S>>i)&1) b.push_back(a[i]),sum+=a[i];
		int nn=b.size();
		if(nn<=1||((sum+nn-1)&1)) continue;
		std::function<std::vector<ll>(int,int)> find
		=[&](int l,int r) -> std::vector<ll>
		{
			std::vector<ll> A{0},B;
			for(int k=l;k<=r;k++)
			{
				B=A;
				for(auto &x:A) x+=b[k];
				for(auto &x:B) x-=b[k];
				std::vector<ll> C; int i=0,j=0;
				while(i<(int)A.size()&&j<(int)B.size())
					C.push_back(A[i]<B[j]?A[i++]:B[j++]);
				while(i<(int)A.size()) C.push_back(A[i++]);
				while(j<(int)B.size()) C.push_back(B[j++]);
				A.swap(C);
			}
			return A;
		};
		int mid=nn/2;
		auto L=find(0,mid),R=find(mid+1,nn-1);
		ll lsum=0,rsum=0;
		for(int i=0;i<=mid;i++) lsum+=b[i];
		for(int i=mid+1;i<nn;i++) rsum+=b[i];
		bool f1=1,f2=1;
		for(int i=0,j=(int)R.size()-1;i<(int)L.size();i++)
		{
			while(j>=0&&L[i]+R[j]>nn-1) j--;
			if(j<0) break;
			if(f1&&L[i]==lsum)
			{
				if((R[j]!=rsum||j)&&L[i]+(R[j]==rsum?R[j-1]:R[j])>=-(nn-1)){f[S]=1;break;}
				f1=0;
			}
			else if(f2&&L[i]==-lsum)
			{
				if((R[j]!=-rsum||j)&&L[i]+(R[j]==-rsum?R[j-1]:R[j])>=-(nn-1)){f[S]=1;break;}
				f2=0;
			}
			else if(L[i]+R[j]>=-(nn-1)){f[S]=1;break;}
		}
	}
	auto fwt=[&](std::vector<int> &a) -> void
	{
		for(int bit=0,mid=1;mid<maxn;bit++,mid<<=1)
			for(int i=0,len=mid<<1;i<maxn;i+=len)
				for(int j=0;j<mid;j++) a[i+mid+j]-=a[i+j];
	};
	auto ifwt=[&](std::vector<int> &a) -> void
	{
		for(int bit=0,mid=1;mid<maxn;bit++,mid<<=1)
			for(int i=0,len=mid<<1;i<maxn;i+=len)
				for(int j=0;j<mid;j++) a[i+mid+j]-=a[i+j];
	};
	auto conv=[&](const std::vector<int> &A,const std::vector<int> &B) -> std::vector<int>
	{
		int sf=0,sg=0;
		std::vector<std::vector<int>> f,g,h;
		f=g=h=std::vector<std::vector<int>>(n+1,std::vector<int>(maxn));
		for(int i=0;i<maxn;i++)
			if(A[i]) f[popc[i]][i]=A[i],sf|=(1<<popc[i]);
		for(int i=0;i<maxn;i++)
			if(B[i]) g[popc[i]][i]=B[i],sg|=(1<<popc[i]);
		for(int i=0;i<=n;i++) if((sf>>i)&1) fwt(f[i]);
		for(int i=0;i<=n;i++) if((sg>>i)&1) fwt(g[i]);
		for(int i=0;i<=n;i++)
			if((sf>>i)&1) for(int j=0;i+j<=n;j++)
				if((sg>>j)&1) for(int k=0;k<maxn;k++)
					if(f[i][k]&&g[j][k]) h[i+j][k]+=f[i][k]*g[j][k];
		for(int i=2;i<=n;i++) ifwt(h[i]);
		std::vector<int> C(maxn);
		for(int i=0;i<maxn;i++) C[i]=h[popc[i]][i];
		return C;
	};
	std::vector<std::vector<int>> g(4);
	g[0]=f;
	for(int i=1;i<=3;i++)
		g[i]=conv(g[i-1],g[i-1]);
	int ans=0;
	std::vector<int> now(maxn);
	bool empty=1;
	for(int i=3;i>=0;i--)
	{
		if(empty)
		{
			bool flag=0;
			for(int j=0;j<maxn;j++)
				if(g[i][j]){flag=1;break;}
			if(flag) now=g[i],ans+=(1<<i),empty=0;
			continue;
		}
		auto tmp=conv(now,g[i]);
		bool flag=0;
		for(int j=0;j<maxn;j++)
			if(tmp[j]){flag=1;break;}
		if(flag) now=tmp,ans+=(1<<i);
	}
	std::cout<<n-ans;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值