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

    一、题目

    点此看题

    二、解法

    答案上界显然是 (n),我们考虑怎么样把答案变小,显然我们要考虑怎么合理利用操作二。

    我们用图论模型考虑操作的结构,如果对 (u,v) 使用了操作二,那么我们把 ((u,v)) 连边。不难发现最优解的图一定是操作二的一个森林,因为如果操作二成环那么肯定没有直接使用操作一要好。

    那么要求这个森林可以用状压 (dp) 的技巧解决,关键问题是如果判断集合 (s) 是否能成为一棵树。

    这个问题比较复杂,可以考虑枚举法,假设我们已经枚举出了树的结构和每个点的点权:

    如果我们不考虑那个 (+1),发现每个点的贡献只和其深度有关,那么我们最后列出来的方程就是:奇数位置的点权(-)偶数位置的点权(=0),把 (+1) 考虑进去的话那么每个 (+1) 可以任意贡献 (+1/-1),所以可以列出方程:

    [|sum_{i=1}^{sz}(-1)^{dep_i}cdot v_i|<sz sum_{i=1}^sv_i=szmod 2 ]

    暴力子集枚举是 (O(3^n)),可以考虑折半搜索,也就是把两边的所有情况枚举出来,时间复杂度 (O(2^{frac{|S|}{2}})),然后 ( t two-pointers) 合并即可,总时间复杂度 (O((1+sqrt 2)^n)),证明:

    [sum_{Sin U}2^{frac{|S|}{2}}=sum_{Sin U}sqrt 2^{|S|}=sum_{i=0}^nsqrt 2^icdot{nchoose i}=(1+sqrt 2)^n ]

    最后考虑怎么状压 (dp),好像还是要用子集枚举啊,但是这道题比较特殊可以加点剪枝,考虑刷表法,如果 (f[s]) 已经被组合出来过了就那它就不去更新,也就是我们只拿最基本的单位去更新,时间复杂度 (O(3^n)) 还是快啊。

    三、总结

    本题使用了推结论的两大技巧:一是观察操作结构然后转图论模型;而是枚举法确定状态推出真正有关的量。我觉得枚举法是真的神奇,通过枚举我们可以知道所有信息,然后分析枚举后的状态看什么信息才是真正需要的。

    子集枚举的优化技巧也值得借鉴,折半搜索那个复杂度是真的牛逼,还可以考虑有效转移来剪枝。

    #include <cstdio>
    #include <iostream>
    using namespace std;
    #define int long long
    const int M = 1<<20;
    int read()
    {
    	int x=0,flag=1;char c;
    	while((c=getchar())<'0' || c>'9') if(c=='-') flag=-1;
    	while(c>='0' && c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
    	return x*flag;
    }
    int n,m,a[25],b[25],f[M],sl[M],sr[M];
    int Abs(int x) {return x>0?x:-x;}
    void get(int *s,int &k,int l,int r)
    {
    	s[0]=0;
    	static int ad[M],sb[M];
    	for(int i=l;i<r;i++,k<<=1)
    	{
    		for(int j=0;j<k;j++)
    			ad[j]=s[j]+b[i],sb[j]=s[j]-b[i];
    		int p=0,q=0,t=0;
    		while(p<k && q<k) s[t]=ad[p]<sb[q]?ad[p++]:sb[q++],t++;
    		while(p<k) s[t]=ad[p++],t++;
    		while(q<k) s[t]=sb[q++],t++;
    	}
    }
    int check(int s)
    {
    	int sz=0,sum=0,l=1,r=1;
    	for(int i=0;i<n;i++)
    		if(s&(1<<i)) b[sz++]=a[i],sum+=a[i];
    	if(Abs(sum)%2!=(sz-1)%2) return 0;
    	get(sl,l,0,sz/2);
    	get(sr,r,sz/2,sz);
    	int nd=1+(Abs(sum)<sz)*2;
    	for(int i=r-1,j=0;i>=0;i--)
    	{
    		while(j<l && sl[j]+sr[i]<=-sz) j++;
    		for(int k=j;k<l && nd && sl[k]+sr[i]<sz;k++)
    			nd--;
    	}
    	return !nd;
    }
    signed main()
    {
    	n=read();
    	for(int i=0;i<n;i++)
    	{
    		a[i]=read();
    		if(!a[i]) {i--;n--;continue;}
    	}
    	m=(1<<n)-1;
    	for(int s=1;s<=m;s++)
    		if(!f[s] && check(s))
    		{
    			f[s]=1;int cs=m-s;
    			for(int t=cs;t;t=(t-1)&cs)
    				f[s|t]=max(f[s|t],f[s]+f[t]);
    		}
    	printf("%lld
    ",n-f[m]);
    }
    
  • 相关阅读:
    hdu 3714 Error Curves(三分)
    hdu 4717 The Moving Points(第一个三分题)
    hdu 4722 Good Numbers(规律题)
    分布式平台基础算法浅析
    Linux下通过管道杀死所有与tomcat相关的进程
    实习番外篇:解决C语言使用Makefile无法实现更好的持续集成问题
    SHELL脚本之awk妙用
    如何在CentOS7上安装Python3及对应问题
    欧拉定理和费马小定理
    最大公约和最小公倍数
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15134937.html
Copyright © 2011-2022 走看看