[USACO2.2]集合 Subset Sums

版权声明:虽然本蒟蒻很菜,但各位dalao转载请注明出处谢谢。 https://blog.csdn.net/xuxiayang/article/details/84840177

链接

洛谷
USACO


大意

1 n 1\sim n 中选出若干个数,使得选的数之和等于没被选的数之和

数据范围:
n 39 n\leq 39


思路

很显然,这个和必然为 i = 1 n i 2 \frac{\sum_{i=1}^ni}{2} ,所以为 n 2 + n 4 \frac{n^2+n}{4}

想到这里你打个状压( d f s dfs )就可以愉快的 A A 3个点了,因为会 T T 飞,但我们可以用累死购物券的方法把搜索范围一分为2搜索两次,这样就可以 A A 啦!时间复杂度 O ( 2 n / 2 ) = O ( 2 n ) O(2^{n/2})=O(\sqrt{2^{n}})

还有一种方法,发现每个数都只能选一次,也就是一个01背包,时间复杂度 O ( n 3 ) O(n^3)


状压代码

/*
ID:hzbismy1
LANG:C++
TASK:subset
*/
#define file(x) freopen(#x".in","r",stdin);freopen(#x".out","w",stdout)
#include<cstdio>
#include<cctype>
#include<algorithm>
using namespace std;int n,k,sum,now,a[10001];
long long ans;
inline char Getchar()
{
    static char buf[100000],*p1=buf+100000,*pend=buf+100000;
    if(p1==pend)
	{
        p1=buf; pend=buf+fread(buf,1,100000,stdin);
        if (pend==p1) return -1;
    }
    return *p1++;
}
inline int read()
{
	char c;int d=1,f=0;
	while(c=Getchar(),!isdigit(c))if(c==45)d=-1;f=(f<<3)+(f<<1)+c-48;
	while(c=Getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
	return d*f;
}
inline void write(register long long x)
{
	if(x<0)write(45),x=-x;
	if(x>9)write(x/10);
	putchar(x%10+48);
	return;
}
signed main()
{
	file(subset);
	n=read();k=n>>1;
	sum=((1+n)*n)>>1;if(sum&1) return puts("0")&0;
	for(int i=0;i<(1<<k);i++)//暴力前半部分
	{
		now=0;
		for(register int j=0;i>>j;j++) if((i>>j)&1) now+=j+1;
		a[now]++;
	}
	for(int i=0;i<(1<<(n-k));i++)//暴力后半部分
	{
		now=0;
		for(register int j=0;i>>j;j++) if((i>>j)&1) now+=j+k+1;
		if(sum/2>=now) ans+=a[sum/2-now];
	}
	printf("%lld\n",ans/2);//算了两次,所以要除以2
}

dp代码

#include<cstdio>
#include<cctype>
#include<algorithm>
using namespace std;int n,k,sum,now;
long long f[10001];
inline char Getchar()
{
    static char buf[100000],*p1=buf+100000,*pend=buf+100000;
    if(p1==pend)
	{
        p1=buf; pend=buf+fread(buf,1,100000,stdin);
        if (pend==p1) return -1;
    }
    return *p1++;
}
inline int read()
{
	char c;int d=1,f=0;
	while(c=Getchar(),!isdigit(c))if(c==45)d=-1;f=(f<<3)+(f<<1)+c-48;
	while(c=Getchar(),isdigit(c)) f=(f<<3)+(f<<1)+c-48;
	return d*f;
}
inline void write(register long long x)
{
	if(x<0)write(45),x=-x;
	if(x>9)write(x/10);
	putchar(x%10+48);
	return;
}
signed main()
{
	n=read();
	sum=((1+n)*n)>>1;if(sum&1) return puts("0")&0;
	f[0]=1;
	for(register int i=1;i<=n;i++)
	 for(register int j=sum;j>=i;j--)
	  f[j]+=f[j-i];
	printf("%lld",f[sum/2]/2);//因为我们算了两次,所以要除以2
}

猜你喜欢

转载自blog.csdn.net/xuxiayang/article/details/84840177
今日推荐