洛谷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 1≤n≤35,1≤m≤595
前置知识:折半搜索(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)。
#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;
}