洛谷-4929 【模板】舞蹈链(DLX)

题目背景
本题是舞蹈链模板——精确覆盖问题
题目描述
给定一个N行M列的矩阵,矩阵中每个元素要么是1,要么是0
你需要在矩阵中挑选出若干行,使得对于矩阵的每一列jjj,在你挑选的这些行中,有且仅有一行的第j个元素为1
输入格式
第一行两个数N,M
接下来N行,每行M个数字0或1,表示这个矩阵
输出格式
一行输出若干个数表示答案,两个数之间用空格隔开,输出任一可行方案均可,顺序随意
若无解,输出“No Solution!”(不包含引号)

输入输出样例
输入 #1
3 3
0 0 1
1 0 0
0 1 0

输出 #1
2 1 3

输入 #2
3 3
1 0 1
1 1 0
0 1 1

输出 #2
No Solution!

说明/提示
N,M≤500
保证矩阵中1的数量不超过5000个

精确覆盖

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mx=250501;//n*m+m<=250500
inline int read(){
    int x=0;
    char c=getchar();
    while(c>'9'||c<'0')c=getchar();
    while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
    return x;
}
int n,m;
int cnt;
int l[mx],r[mx],u[mx],d[mx],col[mx],row[mx];//每个点的左右上下指针,所在行列
int h[mx];//每行的头结点
int s[mx];//每列的节点数
int ansk[mx];//选了那些集合
void init(int m){//m个元素
    for(register int i=0;i<=m;i++){
        r[i]=i+1;
        l[i]=i-1;
        u[i]=d[i]=i;
    }
    r[m]=0;
    l[0]=m;
    memset(h,-1,sizeof(h));
    memset(s,0,sizeof(s));
    cnt=m+1;
}//初始化
inline void link(int R,int C){//R行C列插入点
    s[C]++;
    row[cnt]=R;
    col[cnt]=C;
    u[cnt]=C;
    d[cnt]=d[C];
    u[d[C]]=cnt;
    d[C]=cnt;
    if(h[R]==-1)h[R]=r[cnt]=l[cnt]=cnt;//该行没有点,直接加入
    else{
        r[cnt]=h[R];
        l[cnt]=l[h[R]];
        r[l[h[R]]]=cnt;
        l[h[R]]=cnt;
    }
    cnt++;
    return;
}
inline void remove(int C){//删除涉及C列的集合
    r[l[C]]=r[C],l[r[C]]=l[C];
    for(int i=d[C];i!=C;i=d[i]){
        for(int j=r[i];j!=i;j=r[j]){
            u[d[j]]=u[j];
            d[u[j]]=d[j];
            s[col[j]]--;
        }
    }
}
inline void resume(int C){//恢复涉及C列的集合
    for(int i=u[C];i!=C;i=u[i]){
        for(int j=l[i];j!=i;j=l[j]){
            u[d[j]]=j;
            d[u[j]]=j;
            s[col[j]]++;
        }
    }
    r[l[C]]=C;
    l[r[C]]=C;
}
bool dance(int deep){
    if(r[0]==0){
        register int i=0;
        for(i=0;i<deep;i++)printf("%d ",ansk[i]);
        return 1;
    }
    int c=r[0];
    int i,j;
    for(i=r[0];i!=0;i=r[i])if(s[i]<s[c])c=i;
    remove(c);
    for(i=d[c];i!=c;i=d[i]){
        ansk[deep]=row[i];
        for(j=r[i];j!=i;j=r[j])remove(col[j]);
        if(dance(deep+1))return 1;
        for(j=l[i];j!=i;j=l[j])resume(col[j]);
    }
    resume(c);
    return 0;
}
int main(){
    n=read(),m=read();
    register int i,j;
    int f;
    init(m);
    for(i=1;i<=n;i++){
        for(j=1;j<=m;j++){
            f=read();
            if(f)link(i,j);
        }
    }
    if(!dance(0))printf("No Solution!");
    return 0;
}

重复覆盖

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mx=250501;//n*m+m<=250500
inline int read(){
    int x=0;
    char c=getchar();
    while(c>'9'||c<'0')c=getchar();
    while(c>='0'&&c<='9')x=x*10+c-'0',c=getchar();
    return x;
}
int n,m;
int cnt;
int l[mx],r[mx],u[mx],d[mx],col[mx],row[mx];//每个点的左右上下指针,所在行列
int h[mx];//每行的头结点
int s[mx];//每列的节点数
int ansk[mx];//选了那些集合
int out=0;
void init(int m){//m个元素
    for(register int i=0;i<=m;i++){
        r[i]=i+1;
        l[i]=i-1;
        u[i]=d[i]=i;
    }
    r[m]=0;
    l[0]=m;
    memset(h,-1,sizeof(h));
    memset(s,0,sizeof(s));
    cnt=m+1;
}//初始化
inline void link(int R,int C){//R行C列插入点
    s[C]++;
    row[cnt]=R;
    col[cnt]=C;
    u[cnt]=C;
    d[cnt]=d[C];
    u[d[C]]=cnt;
    d[C]=cnt;
    if(h[R]==-1)h[R]=r[cnt]=l[cnt]=cnt;//该行没有点,直接加入
    else{
        r[cnt]=h[R];
        l[cnt]=l[h[R]];
        r[l[h[R]]]=cnt;
        l[h[R]]=cnt;
    }
    cnt++;
    return;
}
inline void del(int c){
    for(int i=d[c];i!=c;i=d[i]){
        l[r[i]]=l[i],r[l[i]]=r[i];
    }
}//删除c列
inline void res(int c){
    for(int i=u[c];i!=c;i=u[i]){
        l[r[i]]=i,r[l[i]]=i;
    }
}//恢复c列
inline int leave(){
        int ans=0;
        bool vis[m];
        register int i,j,k;
        memset(vis,0,sizeof(vis));
        for(i=r[0];i!=0;i=r[i]){
            if(vis[i]==0){
                vis[i]=1;
                ans++;
                for(j=d[i];j!=i;j=d[j]){
                    for(k=r[j];k!=j;k=r[k])
                    vis[col[k]]=1;
                }
            }
        }
        return ans;
    }//最优情况还要多少个集合
void dance(int deep){
    //注意:这个剪枝只有在多解情况求最优解时下才用
        if(deep+leave()>=out)return;//剪枝
        if(r[0]==0){
            out=deep;
            return;
        }
        int c=r[0];
        register int i,j;
        for(i=r[0];i!=0;i=r[i])if(s[i]<s[c])c=i;//找到点最少的列
        for(i=d[c];i!=c;i=d[i]){
            del(i);
            for(j=r[i];j!=i;j=r[j])del(j);
            dance(deep+1);
            for(j=l[i];j!=i;j=l[j])res(j);
            res(i);
        }
        return;
}//可以看出重复覆盖的效率并不算太高,与深搜差不多,甚至如果深搜剪枝剪得好比dancing links快,谨慎使用
int main(){
    n=read(),m=read();
    register int i,j;
    int f;
    init(m);
    for(i=1;i<=n;i++){
        for(j=1;j<=m;j++){
            f=read();
            if(f)link(i,j);
        }
    }
    dance(0);
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值