洛谷 P3475 [POI2008]POD-Subdivision of Kingdom

把一张无向图分为两个点集,使得不同点集的点之间的边数最少,n<=26。
n<=26,所以,可以爆搜,然后用二进制表示某个点是在第一个点集还是在第二个点集。初始化所有点都在点集S2中,然后在爆搜的过程中不断更新最小值。
那么,下面就可以得到一个爆搜程序
#include <bits/stdc++.h>
#define lowbit(x) x&(-x)
using namespace std;
const int N=14;
int n,m,x,y,s1,s2,ans,ans1;
int p[N<<1];

inline int getnum(int p)
{
	int sum=0;
	for (register int i=1; i<=n; ++i) if ((1<<(i-1))&p) sum++;
	return sum;
}

void dfs(int x,int now,int sum)
{
	if (now==n/2)
	{
		if (sum<ans) ans=sum,ans1=s1;
		return;
	}
	for (register int i=x+1; i<=n; ++i)
	{
		int add1=getnum(p[i]&s1);
		int add2=getnum(p[i]&s2);
		s1|=(1<<(i-1));
		s2^=(1<<(i-1));
		dfs(i,now+1,sum-add1+add2);
		s1^=(1<<(i-1));
		s2|=(1<<(i-1));
	}
}

int main(){
	ans=2e9;
	scanf("%d%d",&n,&m);
	for (register int i=1; i<=m; ++i)
	{
		scanf("%d%d",&x,&y);
		p[x]|=(1<<(y-1));
		p[y]|=(1<<(x-1));
	}
	s1=0; s2=(1<<n)-1;
	dfs(0,0,0);
	for (register int i=1; i<=n; ++i) if (ans1&(1<<(i-1))) printf("%d ",i);
return 0;
}
上交后发现,Tle 5个点,爆搜复杂度是正确的呀?c[26][13]十分小。原来,对于每一个状态,都会执行两次O(n)的getnum,所以因为这个大常数,TLE。
那么,我们就把每一个状态产生的贡献预处理一下吧
下面就是预处理的过程。
inline int query(int x)
{
	int res=0;
	while (x)
	{
		res++;
		x-=lowbit(x);
	}
	return res;
}
for (register int p=0; p<(1<<26); ++p) s[p]=query(p); 
然而,在这个过程中,我们又发现了一个问题:(1<<26)*4/1024/1024=256MB,如果是在NOIP中,一般是512MB内存没有任何问题,但是,洛谷内存限制为125MB,所以,炸内存了。
怎么解决这个问题呢?
设s[p]是p这个数用二进制表示后1的个数,然后,我们假设现在p二进制位数为8,那么可以得到:s[p]=s[p>>4]+s[p-((p>>4)<<4)],就是把8位的总贡献分为前四位和后四位来统计,是不是觉得很神奇。当然,s[p]=s[p>>3]+s[p-((p>>3)<<3)](前5位和后3位),s[p]=s[p>>2]+s[p-((p>>2)<<2)](前6位和后2位)…
所以,对于一个26位的数,我们可以把它分为前1位和后25位,前2位和后24位,前3位和后23位… 那么,我们只要把它分为前13位和后13位来统计,就可以把数组开到1<<13了,当然,你想再开大点,比如分为前14位和后12位等等等等,都没关系。
下面给出AC代码
#include <bits/stdc++.h>
#define lowbit(x) x&(-x)
using namespace std;
const int N=14;
int n,m,x,y,s1,s2,ans,ans1;
int p[N<<1],s[1<<N];

inline int query(int x)
{
	int res=0;
	while (x)
	{
		res++;
		x-=lowbit(x);
	}
	return res;
}

inline int suan(int x)
{
	return s[x>>13]+s[x-((x>>13)<<13)];
}

void dfs(int x,int now,int sum)
{
	if (now==n/2)
	{
		if (sum<ans) ans=sum,ans1=s1;
		return;
	}
	for (register int i=x+1; i<=n; ++i)
	{
		int add1=suan(p[i]&s1);
		int add2=suan(p[i]&s2);
		s1|=(1<<(i-1));
		s2^=(1<<(i-1));
		dfs(i,now+1,sum-add1+add2);
		s1^=(1<<(i-1));
		s2|=(1<<(i-1));
	}
}

int main(){
	ans=2e9;
	scanf("%d%d",&n,&m);
	for (register int p=0; p<(1<<13); ++p) s[p]=query(p); 
	for (register int i=1; i<=m; ++i)
	{
		scanf("%d%d",&x,&y);
		p[x]|=(1<<(y-1));
		p[y]|=(1<<(x-1));
	}
	s1=0; s2=(1<<n)-1;
	dfs(0,0,0);
	for (register int i=1; i<=n; ++i) if (ans1&(1<<(i-1))) printf("%d ",i);
return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值