[USACO09NOV]Lights G(Meet In The Middle)

[USACO09NOV]Lights G

题目描述

给出一张n个点n条边的无向图,每个点的初始状态都为0。

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

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

输入格式

第一行两个整数n, m

之后m行,每行两个整数a, b,表示在点a, b之间有一条边。

输出格式

一行一个整数,表示最少需要的操作次数。

样例

样例输入
5 6 
1 2 
1 3 
4 2 
3 4 
2 5 
5 3
样例输出
3

思路

使用Meet In The Middle,先将1~mid点的全部情况遍历,将可能得到的结果以及到达对应结果状态所需要的最少步数记录下来,由于1<<35过大,所以推荐使用map进行存储。
将前半部分遍历完成后遍历后半部分,对于每个操作可以得到一个最终状态,将最终状态与全1情况异或可以得到对应的互补状态,然后维护最小操作次数即可。

易错点

1.左移的时候记得使用1ll,不然会爆int。
2.需要初始化map[0] = 0,不然当后半能达到全1状态时会多加上几次操作(前半到达全0的次小操作次数)

#include<bits/stdc++.h>
using namespace std;
#define int long long
vector<int> edge[50];
int N, M, ans;
map<int, int> half;

bool option(int op, int n)
{
    //查看是否对第n位进行操作
    return (op >> n) & 1;
}

int answer(int a)
{
    //得到互补状态
    return ans ^ a;
}

bool getState(int op, int n)
{
    /*
    判断当前位在某种操作下最终状态是0还是1
    */
    int res = 0;
    if(option(op, n)){
        res += 1;
    }
    for(int i = 0; i < edge[n].size(); i++){
        if(option(op, edge[n][i])) res += 1;
    }
    return (res & 1)?true:false;
}

int getOptionTime(int op)
{
    /*
    获得一个操作的总操作次数
    */
    int res = 0;
    while(op){
        res += (op & 1);
        op >>= 1;
    }
    return res;
}

int setStateEnd(int op)
{
    /*
    获得一个操作对应的最终状态,返回对应二进制数
    */
    int res = 0;
    for(int i = 0; i < N; i++){
        if(getState(op, i)){
            res ^= (1ll << i);
        }
    }
    return res;
}

/*void print(int a)
{
    stack<int> s;
    while(a){
        s.push(a & 1);
        a >>= 1;
    }
    while(s.size()){
        cout << s.top();
        s.pop();
    }
    cout <<endl;
}*/

signed main()
{
    cin >> N >> M;
    half[0] = 0;// 维护map[0] = 0
    ans = (1ll << N) - 1;// ans对应的最终想要的状态,用于与得到的状态异或来获取另外的部分
    int minTime = 0x3f3f3f3f;
    for(int i = 0; i < M; i++){// 建边
        int a, b;
        cin >> a >> b;
        edge[--a].push_back(--b);
        edge[b].push_back(a);
    }
    int mid = N >> 1ll;
    for(int op = 0; op <= (1ll << (N/2))-1; op++){// 枚举前半部分的全部操作情况
        int res = setStateEnd(op);// 得到操作情况对应的结果状态
        //维护最小操作次数
        if(half.count(res)){
            half[res] = min(half[res], getOptionTime(op));
        }
        else{
            half[res] = getOptionTime(op);
        }
    }
    for(int op = 0; op <= (1ll << N)-(1ll << (N/2)); op += (1ll << (N/2))){// 枚举后半部分的全部操作情况
        int res = setStateEnd(op);
        int thisAns = answer(res);// 判断当前状态的互补状态
        if(half.count(thisAns)){// 查看是否存在互补状态,若存在,则维护最小值
            int nowTime = half[thisAns];
            nowTime += getOptionTime(op);
            minTime = min(minTime, nowTime);
        }
    }
    /*cout << "------" << endl;
    print(setStateEnd((1ll << 33) + (1ll << 34)));
    print(ans);
    cout << "------" << endl;
    for(int i = 0; i < N; i++){
        cout << i << "---->";
        for(int j = 0; j <edge[i].size(); j++){
            cout << edge[i][j] << ' ';
        }
    cout << endl;
    }*/
    cout << minTime <<endl;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值