题目传送门
分析:
首先知道答案不会超过(n),做(n)次操作1绝对完成任务了
我们考虑用操作2替换操作1减少次数
我们将整个序列看做(n)个点,操作2将其中两个点相连
首先我们不会连出环,这样环上的点全都可以使用操作1,无法达到减少操作次数的目标
没环?那就是森林了呗
考虑其中的一个子集构成了树,相连的两个点虽然相减的值不一样,但是出现的差异最多不超过子集大小(每一条边只贡献1的差异)
如果把这1的微小差距忽略,那么相连的两点在“大体上”同增同减,相距为2的点在“大体上”你减我加,总和“大体上”不变
很容易(并不)联想到二分图,同部的点总和“大体上”不变
于是我们枚举某个集合(S)的子集,将集合一分为二为(A,B)
如果(|Sum_A-Sum_B|<|S|)便说明这个划分所带来的差异是在接受范围的
由于集合(S)会形成(|S|-1)条边,所以(|Sum_A-Sum_B|)还要和(|S|-1)同奇偶
满足如上两个条件的集合,完成该集合的任务可以少操作一次
接下来考虑如何合并子集
我很菜,表示到这一步已经很烧脑了,写(O(3^n))剪剪枝九秒应该能过
(代码是(O(3^n))的)
究极巨佬说可以达到(O((1+sqrt 2)^n+2^{n}n^{2}logn))
我tm直呼内行.jpg
前一个部分是在枚举子集求合法可连成树的集合时,将每个集合分成两部分排序后寻找合法区间中的值
复杂度分析:
(~~~~O(sum_{k=0}^{n}C_n^{k}2^{frac{k}{2}}))
(=O(sum_{k=0}^{n}C_n^{k}sqrt 2^k))
(=O((1+sqrt 2)^n))
(二项式定理)
真是流氓的复杂度。。。
后一个部分,做一个生成函数(F(x))
合法的集合的位置的值为1,其余为0
我们来子集卷积
考虑做(p)次卷积,整个生成函数不为0,说明其中能够找到一个集合,能够被(p)个不相交集合合并起来形成,那么就可以减少p次操作
找到第一个(p)使得(F(x)^p=0)就好了
具体过程和树上倍增相似,写起来太精污了就不写了(
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<queue>
#include<algorithm>
#define maxn 2000005
#define INF 0x3f3f3f3f
#define MOD 998244353
using namespace std;
inline long long getint()
{
long long num=0,flag=1;char c;
while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;
while(c>='0'&&c<='9')num=num*10+c-48,c=getchar();
return num*flag;
}
int n;
long long a[maxn],sum[maxn];
int ans[maxn],sz[maxn];
int vis[maxn];
int main()
{
n=getint();
for(int i=0;i<n;i++)
{
a[i]=getint();
if(!a[i])n--,i--;
}
int S=(1<<n)-1;
for(int i=0;i<=S;i++)for(int j=0;j<n;j++)if(i&(1<<j))sum[i]+=a[j];
for(int i=0;i<=S;i++)
{
sz[i]=sz[i>>1]+(i&1);
for(int j=(i-1)&i;((j<<1)>=i)&&j;j=(j-1)&i)
{
int tmp=i^j;
long long num=abs(sum[j]-sum[tmp]);
if(num<sz[i]&&((sz[i]-num)&1)){vis[i]=1;break;}
}
}
for(int i=1;i<=S;i++)if(vis[i])
{
ans[i]=max(ans[i],1);int t=S^i;
for(int j=t;j;j=(j-1)&t)ans[i|j]=max(ans[i|j],ans[j]+1);
}
printf("%d
",n-*max_element(ans+1,ans+S+1));
}