hdu 4979 A simple math problem. DLX(多重覆盖)+打表

来源:互联网 发布:淘宝哪家二手手机好 编辑:程序博客网 时间:2024/05/06 10:13

题解:

有一种彩票,共有n个数字,其中r个为获奖数字,每张彩票上选择m个不同数字,若m个数字钟包含r个获奖数字则获奖。问至少要买多少彩票彩能保证获奖。其中n>=m>=r。举例:n=6,m=3,r=2。只用买6张彩票即可。{1,2,3},{1,4,5},{1,3,6},{2,4,6},{2,5,6},{3,4,5}。

题解:

就拿上述的6,3,2来说,买{1,2,3},那么保证了{1,2},{2,3},{1,3}是奖励数字的时候会获奖,那么题目就可以找出所有彩票的对应可达获奖数字。之后就能发现这是一个精确覆盖问题,用DLX算法。其中每列表示获奖的可能数字,行表示彩票上的可能数字。由于列元素可以重复出现,所以是一个变形的精确覆盖(每列可以被覆盖多次)虽然1<=r<=m<=n<=8,但将所有情况都弄出来需要耗大量时间,所以需要用打表的方式解决。

在正常的DLX算法+A*算法的思想剪枝(就是判断在当前情况下到符合情况至少需要的次数),耗时需要20分钟左右。。怎么看出题人都是在坑人鄙视。之后将所有情况打表输出即可。

打表耗时:



AC代码:

#include <cstdio>#include <cstring>#include <cmath>#include <cstdlib>#include <iostream>#include <algorithm>#include <queue>#include <map>#include <set>#include <vector>#include <cctype>#include <ctime>using namespace std;int ans[8][8][8]={    {       {1}    },    {        {2},        {1,1}    },    {        {3},        {2,3},        {1,1,1}    },    {        {4},        {2,6},        {2,3,4},        {1,1,1,1}    },    {        {5},        {3,10},        {2,4,10},        {2,3,4,5},        {1,1,1,1,1}    },    {        {6},        {3,15},        {2,6,20},        {2,3,6,15},        {2,3,4,5,6},        {1,1,1,1,1,1}    },    {        {7},        {4,21},        {3,7,35},        {2,5,12,35},        {2,3,5,9,21},        {2,3,4,5,6,7},        {1,1,1,1,1,1,1}    },    {        {8},        {4,28},        {3,11,56},        {2,6,14,70},        {2,4,8,20,56},        {2,3,4,7,12,28},        {2,3,4,5,6,7,8},        {1,1,1,1,1,1,1,1}    }};int main(){    int n,m,r,T,tt=0;    scanf("%d",&T);    while(T--)    {        scanf("%d%d%d",&n,&m,&r);        printf("Case #%d: %d\n",++tt,ans[n-1][m-1][r-1]);    }    return 0;}


打表代码:

#include <cstdio>#include <cstring>#include <cmath>#include <cstdlib>#include <iostream>#include <algorithm>#include <queue>#include <map>#include <set>#include <vector>#include <cctype>#include <ctime>using namespace std;const int maxn=100;const int maxnode=1000;const int maxr=100;const int INF=1e9;struct DLX{    int n,sz;                                       //列数,结点总数    int S[maxn];                                    //各列结点数    int row[maxnode],col[maxnode];                  //各结点行列编号    int L[maxnode],R[maxnode],U[maxnode],D[maxnode];//十字链表    int ansd,ans[maxr];                             //解    int vis[maxn];                                  //A*算法时标记求过的列    void init(int n)    {        this->n=n;        //虚拟结点        for(int i=0;i<=n;i++)        {            U[i]=i;D[i]=i;L[i]=i-1;R[i]=i+1;        }        R[n]=0;L[0]=n;        sz=n+1;        memset(S,0,sizeof(S));        ansd=INF;    }    void addRow(int r,vector<int>columns)    {        int first=sz;        for(int i=0;i<columns.size();i++)        {            int c=columns[i];            L[sz]=sz-1;R[sz]=sz+1;D[sz]=c;U[sz]=U[c];            D[U[c]]=sz;U[c]=sz;            row[sz]=r;col[sz]=c;            S[c]++;sz++;        }        R[sz-1]=first;L[first]=sz-1;    }    //顺着链表A,遍历除s外的其他元素    #define FOR(i,A,s) for(int i=A[s];i!=s;i=A[i])    void remove(int c)    {        FOR(i,D,c)        {            L[R[i]]=L[i];            R[L[i]]=R[i];        }    }    void restore(int c)    {        FOR(i,U,c)        {            L[R[i]]=i;            R[L[i]]=i;        }    }    int A()//A*思想,求出要找到结果至少还要选择的行数    {        int i,j,k,res=0;        memset(vis,0,sizeof(vis));        FOR(i,R,0)        {            if(!vis[i])            {                res++;                vis[i]=1;                FOR(j,D,i)                    FOR(k,R,j)                        vis[col[k]]=1;            }        }        return res;    }    //d为递归深度    void dfs(int d)    {        if(R[0]==0)                      //找到解        {            ansd=min(ansd,d);                      //记录解得长度            return ;        }        if(d+A()>=ansd)return ;        //找S最小的列c        int c=R[0];                      //第一个为删除的列        FOR(i,R,0)if(S[i]<S[c])c=i;        //remove(c);                      //删除第c列        FOR(i,D,c)                      //用结点i所在行覆盖第c列        {            remove(i);            FOR(j,R,i)remove(j);   //删除结点i所在行能覆盖的所有其他列            dfs(d+1);            FOR(j,L,i)restore(j);  //恢复结点i所在行能覆盖的所有其他列            restore(i);        }        //restore(c);                     //恢复第c列    }    int solve()    {        dfs(0);        if(ansd==INF)return 0;        return ansd;    }}dlx;vector<int>row[maxr];vector<int>v;int c[10][10];int vis[1025];int bitcount[1025];int get(int x){    int ans=0;    while(x)    {        ans+=(x&1);        x=x>>1;    }    return ans;}void init(){    int i,j,k;    memset(c,0,sizeof(c));    c[0][0]=1;    for(i=1;i<=8;i++)    {        c[i][0]=1;        for(j=1;j<=i;j++)            c[i][j]=c[i-1][j]+c[i-1][j-1];    }    for(i=0;i<(1<<8);i++)    {        bitcount[i]=get(i);    }}int main(){    freopen("D:\\in.txt","r",stdin);    freopen("D:\\out.txt","w",stdout);    double a = clock();    init();    int n,m,r;    while(scanf("%d%d%d",&n,&m,&r)!=EOF)    {        int i,j,k,t=0;        dlx.init(c[n][r]);        memset(vis,0,sizeof(vis));        for(i=1,k=0;i<(1<<n);i++)        {            if(bitcount[i]==r)vis[i]=++k;        }        for(i=1;i<=c[n][m];i++)        {            row[i].clear();        }        for(i=1;i<(1<<n);i++)        {            if(bitcount[i]==m)            {                t++;                for(j=i;j;j=(j-1)&i)                {                    if(bitcount[j]==r)                        row[t].push_back(vis[j]);                }                dlx.addRow(t,row[t]);            }        }        printf("%d %d %d:%d\n",n,m,r,dlx.solve());    }    double b = clock();    printf("%lf\n", (b - a) / CLOCKS_PER_SEC); //运行时间    return 0;}


0 0
原创粉丝点击