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