HDU 3663 Power Stations(Dancing_Links精确覆盖)

来源:互联网 发布:巩义网络招聘平台 编辑:程序博客网 时间:2024/05/29 10:01

题目地址
题意:有n个城市,告诉你m条路,每个城市都有自己的发电厂,每个发电厂能给与该城市直接相连的城市供电,每个城市的发电厂都有自己的工作周期,我们只能在工作区间中选择一段工作区间来进行工作,(如1~5,不能选择1~2和4~5,只能选择工作区间内的一个连续区间,问能不能找到一个工作表,使得每天每个城市都有且仅有一所发电厂给该城市供电。
思路:我们通过看数据范围可以看到最大只有6天,所以我们可以枚举每个时间段,分别为:{ 1, 1 },{ 1, 2 },{ 1, 3 },{ 1, 4 },{ 1, 5 },{ 2, 2 },{ 2, 3 },{ 2, 4 },{ 2, 5 },{ 3, 3 },{ 3, 4 },{ 3, 5 },{ 4, 4 },{ 4, 5 },{ 5, 5 },{ 0, 0 } 。{0,0}就是该天没有供电。然后我们可以把这个东西抽象成一个矩阵,通过前面枚举的区间范围加上城市的编号得到一个行坐标,城市坐标与第几天的组合得到一个列坐标,然后我们就可以转成求出一个行集合,使得每个列坐标都出现过一次。为了表明该城市每天只能有一个供电,所以设定一个点为如果在当前时间已有发电厂供电,这样的话就可以保证不会由重复供电的情况。然后解决这类问题的方法就是DLX算法求精确覆盖(舞蹈链算法)。
舞蹈链算法是基于深搜和回溯的算法,我们通过把深搜到的行以及在当前不能取的列给删除来减少每一次的数据范围,因为这样的话我们就能得到一个比之前小的矩阵了,然后我们再接着用这个操作,直到全部找完,然后不行就恢复上一层删除的行和列,直到不能恢复了,这样的话就是没有这样的行集合。用下面那个博客中的过程来简述一下吧。

1、从矩阵中选择一行
2、根据定义,标示矩阵中其他行的元素
3、删除相关行和列的元素,得到新矩阵
4、如果新矩阵是空矩阵,并且之前的一行都是1,那么求解结束,跳转到6;新矩阵不是空矩阵,继续求解,跳转到1;新矩阵是空矩阵,之前的一行中有0,跳转到5
5、说明之前的选择有误,回溯到之前的一个矩阵,跳转到1;如果没有矩阵可以回溯,说明该问题无解,跳转到7
6、求解结束,把结果输出
7、求解结束,输出无解消息

推荐一个讲的蛮好的舞蹈链的博客:《跳跃的舞者,舞蹈链(Dancing Links)算法——求解精确覆盖问题》,我就是通过这篇博客入门的。

#include <iostream>#include <cstring>#include <string>#include <queue>#include <vector>#include <map>#include <set>#include <cmath>#include <cstdio>#include <algorithm>#include <iomanip>#define N 2010#define M 610#define MAXSIZE 2010*610#define LL __int64#define inf 0x3f3f3f3f#define lson l,mid,ans<<1#define rson mid+1,r,ans<<1|1#define getMid (l+r)>>1#define movel ans<<1#define mover ans<<1|1 using namespace std;const LL mod = 1000000007;const double eps = 0.001;struct node {    int left, right, up, down, col, row;}mapp[MAXSIZE];int S[MAXSIZE], O[MAXSIZE], H[MAXSIZE];//S记录该列中1元素的个数int head, cnt;int len;vector<int> G[N];int st[N], en[N], ans[N];bool mat[N][M];struct Dancing_Links_X {    void init(int m) {        head = 0;        for (int i = 0; i <= m; i++) {            S[i] = 0;            mapp[i].up = mapp[i].down = i;            mapp[i].left = (i == 0 ? m : i - 1);            mapp[i].right = (i == m ? 0 : i + 1);        }        cnt = m;        memset(H, -1, sizeof(H));    }    void link(int x, int y) {        cnt++;        mapp[cnt].row = x;        mapp[cnt].col = y;        S[y]++;        mapp[cnt].up = mapp[y].up;        mapp[cnt].down = y;        mapp[mapp[y].up].down = cnt;        mapp[y].up = cnt;        if (H[x] == -1) H[x] = mapp[cnt].left = mapp[cnt].right = cnt;        else {            mapp[cnt].left = mapp[H[x]].left;            mapp[cnt].right = H[x];            mapp[mapp[H[x]].left].right = cnt;            mapp[H[x]].left = cnt;        }    }    void remove(int c) {//删去c这个点,以及关联的一列        mapp[mapp[c].right].left = mapp[c].left;        mapp[mapp[c].left].right = mapp[c].right;        for (int i = mapp[c].down; i != c; i = mapp[i].down) {            for (int j = mapp[i].right; j != i; j = mapp[j].right) {                mapp[mapp[j].down].up = mapp[j].up; mapp[mapp[j].up].down = mapp[j].down;                --S[mapp[j].col];            }        }    }    void resume(int c) {//恢复c这个点,以及关联的一列        for (int i = mapp[c].up; i != c; i = mapp[i].up) {            for (int j = mapp[i].left; j != i; j = mapp[j].left) {                ++S[mapp[j].col];                mapp[mapp[j].down].up = mapp[mapp[j].up].down = j;            }        }        mapp[mapp[c].right].left = mapp[mapp[c].left].right = c;    }    bool dance(int k) {        if (mapp[head].right == head) {            len = k - 1;            return true;        }        int s = inf, c;        for (int t = mapp[head].right; t != head; t = mapp[t].right) {            if (S[t] < s) s = S[t], c = t;        }        remove(c);        for (int i = mapp[c].down; i != c; i = mapp[i].down) {            O[k] = mapp[i].row;            for (int j = mapp[i].right; j != i; j = mapp[j].right) {                remove(mapp[j].col);            }            if (dance(k + 1)) {                return true;            }            for (int j = mapp[i].left; j != i; j = mapp[j].left) {                resume(mapp[j].col);            }        }        resume(c);        return false;    }}DLX;int moved[16][2] = { { 1, 1 },{ 1, 2 },{ 1, 3 },{ 1, 4 },{ 1, 5 },{ 2, 2 },{ 2, 3 },{ 2, 4 },{ 2, 5 },{ 3, 3 },{ 3, 4 },{ 3, 5 },{ 4, 4 },{ 4, 5 },{ 5, 5 },{ 0, 0 } };int main() {    cin.sync_with_stdio(false);    int n, m, d;    int a, b;    while (cin >> n >> m >> d) {        for (int i = 1; i <= n; i++) {            G[i].clear();            G[i].push_back(i);        }        for (int i = 0; i < m; i++) {            cin >> a >> b;            G[a].push_back(b);            G[b].push_back(a);        }        for (int i = 1; i <= n; i++) {            cin >> st[i] >> en[i];        }        memset(mat, 0, sizeof(mat));        //第(i – 1) * d + j列表示第i个城市的第j天 是否被这一行的状态(a发电站选择b区间)供电,        //第n * d + j列为1表示这个覆盖来自第j个发电站(因为每个发电站只能用一次,所以要用额外的n列来限制 ,由于这n列每一列只能被覆盖一次,就限制了使用次数也是1)。        for (int i = 1; i <= n; i++) {            for (int j = 0; j < 15; j++) {//遍历每一个区间                int x = (i - 1) * 16 + j + 1;//是当前哪个时间区间哪个发电厂发电                if (moved[j][0] >= st[i] && moved[j][1] <= en[i]) {//如果这个区间在这个城市的开始到结束的区间中                    for (int k = 0; k < G[i].size(); k++) {                        int v = G[i][k];                        for (int l = moved[j][0]; l <= moved[j][1]; l++)                            mat[x][(v - 1)* d + l] = 1;//当该时间区间该发电厂发电时会影响的城市和时间                    }                    mat[x][n * d + i] = 1;                }            }            mat[(i - 1) * 16 + 16][n * d + i] = 1;//表明该城市每天只能有一个供电        }        m = n*d + n;        n = n * 16;        DLX.init(m);        for (int i = 1; i <= n; i++) {            for (int j = 1; j <= m; j++) {                if (!mat[i][j]) continue;                DLX.link(i, j);            }        }        DLX.dance(1);        n = n / 16;        if (len != n) {            cout << "No solution" << endl;        }        else {            for (int i = 1; i <= len; i++) {                int tmp = ((O[i] - 1) / 16) + 1;//找到行的集合。                int tmp2 = O[i] - (tmp - 1) * 16 - 1;                ans[tmp] = tmp2;            }            for (int i = 1; i <= n; i++)                cout << moved[ans[i]][0] << " " << moved[ans[i]][1] << endl;//输出行的集合的工作区间范围        }        cout << endl;    }    return 0;}
原创粉丝点击