原题: http://codeforces.com/gym/102006/problem/F
题意:
有 个测试点,n发提交。给出每一发提交对应每个测试点的正确性,问怎么样组织测试点使测试次数最少。(错误点的后序测试点不会测试)多个答案输出字典序最小的那个。
解析:
用dp[11100]表示已经使用第3、4、5个测试点的最少测试数。当dp[11100]转移到dp[11101]时,需要增加的为:所有成功到达11100的提交数量(即11100、11110、11101、11111)。
用ct[11100]记录上面的情况,这个要用到高维前缀和:
rep(j,0,l-1){
repp(i,(1<<l)-1,0){
if(i&(1<<j))continue;
ct[i]+=ct[i|(1<<j)];
}
}
注意第一维要遍历位,第二维遍历状态。因为按照状态单调顺序不能保证位的递增(011->100)
顺序的话就是在得到答案的基础上,从小到大枚举位,看看是否可以从那边转移。
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=(int)(a);i<=(int)(b);i++)
#define repp(i,b,a) for(int i=(int)(b);i>=(int)(a);i--)
const int maxn=2e6+6;
int l,n;
int a[maxn],dp[maxn],ct[maxn];
int dfs(int sta){
if(dp[sta]!=1e9)return dp[sta];
rep(i,0,l-1){
if(!(sta&(1<<i))){
int to=sta|(1<<i);
dp[sta]=min(dp[sta],dfs(to)+ct[sta]);
}
}
return dp[sta];
}
int main(){
freopen("tests.in","r",stdin);
ios::sync_with_stdio(false);cin.tie(0);
int t;
cin>>t;
while(t--){
cin>>l>>n;
rep(i,0,(1<<l)-1)dp[i]=1e9,ct[i]=0;
dp[(1<<l)-1]=0;
// dp[101]表示已经用了第1和第3个的情况
rep(i,1,n){
string x;cin>>x;
int sta=0;
rep(j,0,x.length()-1){
if(x[j]=='1')sta|=(1<<j);
}
ct[sta]++;
}
rep(j,0,l-1){
repp(i,(1<<l)-1,0){
if(i&(1<<j))continue;
ct[i]+=ct[i|(1<<j)];
}
}
int ans=dfs(0);
cout<<ans<<endl;
int sta=0;
vector<int>V;
while(1){
//printf("dp[%d]=%d\n",sta,dp[sta]);
rep(i,0,l-1){
if(!(sta&(1<<i))){
int to=sta|(1<<i);
if(dp[sta]==dp[to]+ct[sta]){
V.push_back(i+1);
sta=to;
break;
}
}
}
if(sta==(1<<l)-1)break;
}
rep(i,0,V.size()-1){
cout<<V[i]<<(i==V.size()-1?'\n':' ');
}
}
}