题解:
有一种彩票,共有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; }