zoukankan      html  css  js  c++  java
  • CF1286F Harry The Potter

    题目传送门

    分析:
    首先知道答案不会超过(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));
    }
    

  • 相关阅读:
    lumen简单使用exel组件
    VIM 批量注释的两种方法 (转)
    linux时间校准 设置时间为上海时区
    lumen发送邮件配置
    centos 下安装redis 通过shell脚本
    shell 脚本接收参数
    linux设置系统变量
    linux通配符
    UCCI协议[转]
    一种编程范式:对拍编程
  • 原文地址:https://www.cnblogs.com/Darknesses/p/12976093.html
Copyright © 2011-2022 走看看