【CF1286F】Harry The Potter(折半,子集卷积)
首先若用了第二种操作,就在 \((i,j)\) 之间连一条边。
发现图中不可能出现环,否则我们将这个环删掉,并用等同次数的一操作即可与环上原操作做到等价。
那么考虑一个数集 \(S=\{a_1,\cdots,a_k\}\) 有没有可能形成一条树而全部消掉。
考虑从下往上推,叶子 \(x\) 只有可能取它自己,而另一端有可能取 \(x\pm 1\)。
从而倒数的某个点 \(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)|\}\)。
最后我们要让根取 \(0\),这等价于 \(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\}\)。
最后得到等价条件是:
- \(\sum a_i+(k-1)\) 为偶数。
- 能将 \(S\) 划分为两个非空集合 \(A,B\),且 \(|\sum_{a\in A}a-\sum_{b\in B}b|\leq k-1\)。
如果暴力地枚举 \(S,A\) 并检验是 \(O(3^n)\) 的。更好的做法是枚举 \(S\),然后折半,两半分别求出 \(2^{\frac{|S|}{2}}\) 种子集和并排序好(这里排序能边求边做),然后再双指针扫一下,时间复杂度 \(O(2^{\frac{|S|}{2}})\)。这部分总复杂度:
\[\sum_{i=1}^n\binom{n}{i}2^{i/2}=(1+\sqrt 2)^n
\]
然后原问题相当于要选尽量多的 \(S_1,\cdots,S_m\) 不交且它们都能被表示出来,直接 DP 是 \(O(3^n)\) 的。更好的做法是二分 \(m\),然后转为 \(m\) 次幂的子集卷积,看是否有一位为 \(1\)。用倍增而非二分即可做到 \(O(n^2 2^n\log n)\)。
卡常卡吐了。
#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;
}