bzoj1770 USACO NOV09 GOLD 燈(暴力出奇跡)

細雨欣然發表於2017-03-13

【問題描述】

  貝希和她的閨密們在她們的牛棚中玩遊戲。但是天不從人願,突然,牛棚的電源跳閘了,所有的燈都被關閉了。貝希是一個很膽小的女生,在伸手不見拇指的無盡的黑暗中,她感到驚恐,痛苦與絕望。她希望您能夠幫幫她,把所有的燈都給重新開起來!她才能繼續快樂地跟她的閨密們繼續玩遊戲!

  牛棚中一共有N(1 <= N <= 35)盞燈,編號為1到N。這些燈被置於一個非常複雜的網路之中。有M(1 <= M <= 595)條很神奇的無向邊,每條邊連線兩盞燈。 每盞燈上面都帶有一個開關。當按下某一盞燈的開關的時候,這盞燈本身,還有所有有邊連向這盞燈的燈的狀態都會被改變。狀態改變指的是:當一盞燈是開著的時候,這盞燈被關掉;當一盞燈是關著的時候,這盞燈被開啟。

  問最少要按下多少個開關,才能把所有的燈都給重新開啟。資料保證至少有一種按開關的方案,使得所有的燈都被重新開啟。

【輸入格式】

  第一行:兩個空格隔開的整數:N和M。
  第二到第M+1行:每一行有兩個由空格隔開的整數,表示兩盞燈被一條無向邊連線在一起。沒有一條邊會出現兩次。

【輸出格式】

第一行:一個單獨的整數,表示要把所有的燈都開啟時,最少需要按下的開關的數目。

【輸入樣例】

5 6
1 2
1 3
4 2
3 4
2 5
5 3

【輸出樣例】

3

【樣例解釋】

一共有五盞燈。燈1、燈4和燈5都連線著燈2和燈3。按下在燈1、燈4和燈5上面的開關。

【資料範圍】

1 <= N <= 35  1 <= M <= 595

【來源】

bzoj1770 USACO NOV09 GOLD

這道題其實有2種解法,我在這裡就講一下我考試的時候用的暴力(暴力出奇跡),高斯消元的解法參考下面連結的內容(差不多,就最小值和種類數的差別而已):高斯消元的解法

說的暴力,無非就是列舉每個點按不按(其實直接列舉+優化有60分),想要滿分2^35的時間複雜的肯定是不現實的,全部記憶化的話記憶體都不過。
所以我可以用map記憶一半,前2/n個點我們先列舉,然後記錄每種情況的最小次數。然後再列舉後n-2/n個點,出來一種情況就在map裡找和它配合的(記住如果直接滿了要特判一下),如果有就可以。
時間複雜度(2*2^(2/n)),完全可以過。

詳細程式碼如下:

#include<cstdlib>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<map>
using namespace std;
typedef long long ll;
const int maxn=40;

ll a[maxn],b[maxn];
int n,m,ans=55;
ll maxd=0;
map<ll,int>mp;

void run(int i,int t,ll s)
{
    if(i>n/2) 
    {
        if(!mp[s]||mp[s]>t)mp[s]=t;
        if(s==maxd) ans=min(ans,t);
        return;
    }
    run(i+1,t,s);
    run(i+1,t+1,s^b[i]);
}
void run2(int i,int t,ll s)
{
    if(i>n) 
    {
        if(mp[maxd-s]) ans=min(ans,t+mp[maxd-s]);
        if(s==maxd) ans=min(ans,t);
        return;
    }
    run2(i+1,t,s);
    run2(i+1,t+1,s^b[i]);
}
int main()
{
    //freopen("lamp.in","r",stdin);
    //freopen("lamp.out","w",stdout);
    a[1]=1;
    for(int i=2;i<=39;i++) a[i]=a[i-1]<<1;
    scanf("%d%d",&n,&m);
    int x,y;
    for(int i=1;i<=n;i++) b[i]=a[i];

    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        b[x]+=a[y];b[y]+=a[x];
    }
    maxd=a[n+1]-1;
    run(1,0,0);
    run2(n/2+1,0,0);
    cout<<ans;
    return 0;
}

相關文章