USACO09NOV Lights G(meet in the middle)

洛谷P2962 [USACO09NOV] Lights G

题目大意

有一个有 n n n个点 m m m条边的无向图,每个点的初始状态为 0 0 0

你可以操作任意一个点,操作结束后该点以及所有与该点相邻的点的状态都会改变,由 0 0 0变成 1 1 1或从 1 1 1变成 0 0 0

你需要求出最少的操作次数,使得在所有操作完成之后所有点的状态都是 1 1 1

1 ≤ n ≤ 35 , 1 ≤ m ≤ 595 1\leq n\leq 35,1\leq m\leq 595 1n35,1m595


题解

前置知识:折半搜索(meet in the middle)

我们可以用 v x v_x vx存储对 x x x进行操作之后会改变的点, v x v_x vx每个二进制位表示每个点的状态,二进制位为 1 1 1表示会改变,为 0 0 0表示不会改变。

我们发现,每个点最多修改一次(修改两次相当于不修改)。那我们枚举每个点是否修改,最后判断这个图是否满足题意,满足的话就更新答案。这样做的书剑复杂度是 O ( 2 n ) O(2^n) O(2n)的。

我们考虑用折半搜索,分别搜索 1 1 1 n / 2 n/2 n/2 n / 2 + 1 n/2+1 n/2+1 n n n的状态,如果两种状态合并后的状态中每个点都为 1 1 1,则当前花费就是到达两种状态的花费之和,用当前花费更新答案即可。

每种状态的最小花费可以用 m a p map map来保存,每次修改和查询的时间复杂度为 O ( log ⁡ 2 n ) O(\log 2^n) O(log2n),也就是 O ( n ) O(n) O(n)

总时间复杂度为 O ( n 2 n / 2 ) O(n2^{n/2}) O(n2n/2)

code

#include
using namespace std;
int n,m,ans=10000;
long long all=0,v[45];
map<long long,int>mp;
void dfs(int t,int to,long long s,int now){
	if(!mp.count(s)) mp[s]=now;
	else mp[s]=min(mp[s],now);
	if(mp[s^all]||s==all){
		ans=min(ans,mp[s^all]+now);
	}
	if(t<=to){
		dfs(t+1,to,s,now);
		dfs(t+1,to,s^v[t],now+1);
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1,x,y;i<=m;i++){
		scanf("%d%d",&x,&y);
		v[x]^=(1ll<<y);
		v[y]^=(1ll<<x);
	}
	for(int i=1;i<=n;i++){
		v[i]^=(1ll<<i);
		all^=(1ll<<i);
	}
	dfs(1,n/2,0,0);
	dfs(n/2+1,n,0,0);
	printf("%d",ans);
	return 0;
}

你可能感兴趣的:(题解,题解,c++)