1. 程式人生 > >NUIST OJ 1347 供電站 [迭代加深搜尋]

NUIST OJ 1347 供電站 [迭代加深搜尋]

題目

題目描述

你一個程式設計師,不知為何就當上了你們鎮的鎮長(人的一生當然要靠自我奮鬥,當然也要考慮歷史的程序)。你們鎮有 N (3 <= N <= 35)個村,分別標號為 1, 2, …, N,有些村常年供電不足。現在你需要重新規劃鎮上的供電站的選址。現在的要求是,對於鎮裡的每個村,要麼這個村有個供電站,要麼這個村相鄰的村中有一個供電站。你最少需要建幾個供電站?

輸入描述

有多組輸入。
對於每組測試資料,第一行兩個數 N, M,分別表示村子數量和直接相連的村子關係的數量。
接下來 M 行,每行兩個數,表示這兩個村子直接相連。
注意: 最後一組測試資料為 N = 0, M = 0,是一個標記,表示輸入結束,無需計算和輸出任何東西。

輸出描述

每組測試資料輸出一個整數,表示最少需要建的供電站數量。

樣例輸入

8 12
1 2
1 6
1 8
2 3
2 6
3 4
3 5
4 5
4 7
5 6
6 7
6 8
0 0

樣例輸出

2

題目分析

本題初看是一個暴力,暴力列舉選哪些村莊修建發電站然後搜尋一遍圖看一看是不是全部符合要求。但是想一想也知道,這樣肯定會超時,而且如果用二進位制來列舉的話,沒法控制先列舉小數量再列舉大數量。因此,本題採用迭代加深搜尋。
關於迭代加深搜尋,我以前的文章有出現過:
【演算法競賽入門經典】7.6 迭代加深搜尋與IDA* 例題7-10 UVa11211


這裡將其利用到確定修建幾個電站上面,最大深度是幾就是最大能修幾個電站。從小的往大去搜索。
另外,為了快速運算,本題使用一維陣列表示每個村莊,每個村莊中第i位若是為1則表示與第i個村莊相鄰,否則就是不相鄰。這樣,題目又可以簡化成,挑選出最小個數的村莊,使其並起來每一位都是1。
搜尋的格式為dfs(本次迭代優先搜尋的最大層數,當前層數,已選村莊中編號最靠後的那個村莊的標號,long long a表示的每個村子點亮情況)
下面考慮剪枝,目前有二種剪枝策略:
1.若是新選出了第i個村莊,產生的效果與原來情況取並相等的話,實際上就是沒有作用,肯定不是最優解,直接放棄本次搜尋
2.這裡也解釋了為什麼dfs()中有已選村莊中編號最靠後的那個村莊的標號的原因了。當規定從第一個村莊開始往後選擇的時候,不允許回頭選擇。因此若是選到第i個村莊之後,後面i+1~n個村莊全部選擇都不能使得所有村莊點亮,則後面不可能滿足題意了,因此放棄本次搜尋。所以要求有序的從前往後搜尋(從後往前也行,只不過後面的要改),第二個要求是求出第i個村莊到第n個村莊都點亮的話,產生的效果。

本題的坑

因為採用二進位制表示某個村莊的相鄰情況或是點亮情況,所以二進位制的使用會有坑
即使

long long a;
a = 1<<32;

這樣也會出問題,因為1是int,1<<32對於int來說變成一個負數了,然後這個負數複製給a那麼a不可能是你想要的結果,因此,正確的做法應該是:

long long a;
long long one = 1;
a = one<<32;
//a = (long long)1 <<32;
//a = 1ll <<32;

以上寫法都可以,本質上是要求1先是long long然後再進行移位

整體程式碼與執行結果

/*
[email protected]
*/
#include<iostream>
#include<algorithm>
#include<cstring>
#define maxn 36
using namespace std;
long long dat[maxn];
long long tailsum[maxn];
long long dest;
long long one = 1;
int n, m;
bool dfs(int depth, int step, int ci, long long ans) {
    if (ans == dest)
        return true;
    if (step == depth)
        return false;
    for (int i = ci; i <= n; ++i) {
        if ((ans | tailsum[i]) != dest)
            break;
        if ((ans | dat[i]) == ans)
            continue;
        if (dfs(depth, step + 1, i + 1, ans | dat[i]))
            return true;
    }
    return false;
}
int main() {
    while (cin >> n >> m && n) {
        int a, b;
        memset(dat, 0, sizeof(dat));
        memset(tailsum, 0, sizeof(dat));
        dest = (one << n) - 1;
        for (int i = 0; i < m; ++i) {
            cin >> a >> b;
            dat[a] |= one << (b - 1);
            dat[b] |= one << (a - 1);
        }
        for (int i = 1; i <= n; ++i)
            dat[i] |= one << (i - 1);
        tailsum[n] = dat[n];
        for (int i = n - 1; i >= 1; --i) {
            tailsum[i] = dat[i];
            tailsum[i] |= tailsum[i + 1];
        }
        for (int i = 1; i <= n; ++i) {
            if (dfs(i, 0, 1, 0)) {
                cout << i << endl;
                break;
            }
        }
    }
    return 0;
}

結果如下
這裡寫圖片描述