hdu 3663 (dancing links)精确覆盖

题目描述:

有n个城市,还有m条边(双向),每个城市都有一个发电站,如果一个发电站工作,它能够给它所在的城市和直接相邻的城市提供电力。并且要求每个城市只能由一个发电站来提供电力(即不能够被两个或以上的发电站同时覆盖)。

然后,每个城市的发电站都有一个允许工作时间 ai bj,表示发电站只能在[ai,bi]内的某个连续区间内工作(也可以一个都不选),并且只能选一个区间(即ai = 1, bi = 5, 不能选择1-2 和4-5两个区间)。

然后给你一个数字D,问你能不能安排这n个发电站的工作时间使1~D的时间区间内,每个城市在每个时间都能够被一个发电站覆盖。

可以的话输出任意一种解决方法。

n <= 60, m<= 150, D<=5

解题报告:

初看题意,是一个覆盖问题,n又很小,搜,怎么搜,DLX的精确覆盖模型。

精确覆盖:一个0,1矩阵,选择某些行,使每一列都有且仅有一个1。即用行覆盖列。

行的定义:

一共n * 16行,16就是[1,5]区间的所有小区间:

{{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表示不用。

这样第(i – 1) * 16 + j就表示第i个发电站选择小区间j时状态。

(1 <= i <= n, 1 <= j <= 16)

列的定义:

对于第(a – 1) * 16 + b行,一共有n * d + n列。

第(i – 1) * d + j列表示第i个城市的第j天 是否被这一行的状态(a发电站选择b区间)供电,

第n * d + j列为1表示这个覆盖来自第j个发电站(因为每个发电站只能用一次,所以要用额外的n列来限制,和数独那题的解法类似,由于这n列每一列只能被覆盖一次,就限制了使用次数也是1)。

这样图就建好了,套用DLX模板即可。有界的话就输出一个就好了。


#include <cstdio>  
#include <cstring>  
#include <iostream>  
#include <cmath>  
#include <algorithm>  
#include <vector>  
#include <bitset>  
#include <queue>  
#define ll long long  
using namespace std;  
  
const int maxn = 60 * 20;  
const int maxm = 60 * 10;  
const int max_size = maxn * maxm;  
const int INF = 1e9;  
  
int L[max_size], R[max_size], U[max_size], D[max_size], C[max_size], H[max_size];  
int S[max_size], O[max_size],row[max_size];  
int head, size;  
int n, m, d, len;  
vector<int> G[100];  
int st[100], ed[100], ans[100];  
bool mat[maxn][maxm];  
int move[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}};  
void init(int N,int M){
      int i;  
    for (i = 1; i <= N; i++)   
            H[i] = -1;  
    for (i = 0; i <= M; i++) {  
        S[i] = 0;  
        L[i + 1] = i;  
        R[i] = i + 1;  
        U[i] = D[i] = i;  
    }  
    R[M] = 0;  
    size = M + 1;  
} 

void Link(int r,int c)
{
    U[size] = c;  
    D[size] = D[c];  
    U[D[c]] = size;  
    D[c] = size;  
    if (H[r] < 0)  
        H[r] = L[size] = R[size] = size;  
    else {  
        L[size] = H[r];  
        R[size] = R[H[r]];  
        L[R[H[r]]] = size;  
        R[H[r]] = size;  
    }  
    S[c]++; 
	row[size] = r ; 
    C[size++] = c; 
}

void Remove(int c) {   //删除c列及相应的行   
    int i, j;  
    R[L[c]] = R[c];  
    L[R[c]] = L[c];  
    for (i = D[c]; i != c; i = D[i]) {  
        for (j = R[i]; j != i; j = R[j]) {  
            U[D[j]] = U[j];  
            D[U[j]] = D[j];  
            S[C[j]]--;  
        }  
    }  
}  
void Resume(int c) {  //恢复c列   
    int i, j;  
    R[L[c]] = c;  
    L[R[c]] = c;  
    for (i = D[c]; i != c; i = D[i]) {  
        for (j = R[i]; j != i; j = R[j]) {  
            U[D[j]] = j;  
            D[U[j]] = j;  
            S[C[j]]++;  
        }  
    }  
}  
bool Dance(int now) {   //舞蹈链   
    if (R[0] == 0)  //输出答案   
       {  len = now;
        return true;
	   }
    int i, j, temp, c;  
    for (temp=INF,i = R[0]; i; i = R[i]) {  
        if(S[i]<temp)  
        {  
            temp=S[i];  
            c=i;  
        }  
    }  
    Remove(c);  
    for(i=D[c];i!=c;i=D[i])  
    {    O[now]=row[i]  ;   //记录答案 ;   
        for(j=R[i];j!=i;j=R[j])  
            Remove(C[j]);  
        if(Dance(now+1))  
            return true;  
        for(j=L[i];j!=i;j=L[j])  
            Resume(C[j]);  
    }  
    Resume(c);  
    return false;  
}  

int main()  
{  
    int a, b;  
    while(~scanf("%d%d%d", &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++)  
        {  
            scanf("%d%d", &a, &b);  
            G[a].push_back(b);  
            G[b].push_back(a);  
        }  
        for(int i = 1; i <= n; i++)  
        scanf("%d%d", &st[i], &ed[i]);  
        memset(mat, 0, sizeof(mat));  
        for(int i = 1; i <= n; i++)  
        {  
            for(int j = 0; j < 15; j++)  
            {  
                int x = (i - 1) * 16 + j + 1;  
                if(move[j][0] >= st[i] && move[j][1] <= ed[i])  
                {  
                    for(int k = 0; k < (int) G[i].size(); k++)  
                    {  
                        int v = G[i][k];  
                        for(int l = move[j][0]; l <= move[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;  
        } 
		int N=n*16,M=n*d+n ;  //行数列数 
        init(N, M);          //初始化 
        //构造十字链表 
        for (int i = 1; i <= N; ++i) 
        for (int j = 1; j <= M; ++j) {
            if (!mat[i][j]) continue;
            else   Link(i,j)  ;     //插入 
        }
        Dance(0);        //舞蹈链 
        if(len != n) printf("No solution\n");  
        else  
        {  
             for(int i = 0; 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++)  
                printf("%d %d\n", move[ans[i]][0], move[ans[i]][1]);  
        }  
        puts("");  
    }  
    return 0;  
}  



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值