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

题解:

有一种彩票,共有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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值