题意:
解法:
观察到t只有20,考虑状压dp,
令d[i]表示已经将二进制集合为i的位置排在前面,的最小总值.
转移方程:
枚举i集合中,放在最后的位置为j的代价:
d[i]=min(d[i],d[i^(1<<j)]+cost),其中(i>>j&1)=1,cost为将第j为放在最后的代价.
考虑cost如何计算,设cnt[x]为(p&x)=x的数字p的个数,
那么cost=(cnt[i^(1<<j)]-cnt[i])*cal(i),其中cal(i)是二进制i中1的个数.
cnt[]可以用超集SosDp计算
code:
#include<bits/stdc++.h>
using namespace std;
const int maxm=2e6+5;
char s[maxm];
int cnt[maxm];
int pre[maxm];
int d[maxm];
int t,n;
int cal(int x){
int ans=0;
while(x){
ans+=x%2;
x/=2;
}
return ans;
}
void solve(){
scanf("%d%d",&t,&n);
for(int i=0;i<(1<<t);i++){
cnt[i]=0;
}
int tot=0;
for(int i=1;i<=n;i++){
scanf("%s",s);
int num=0;
for(int j=0;j<t;j++){
if(s[j]=='1'){
num|=(1<<j);
}
}
if(num==(1<<t)-1){
tot+=t;
}else{
cnt[num]++;
}
}
for(int j=0;j<t;j++){
for(int i=(1<<t)-1;i>=0;i--){
if(!(i>>j&1)){
cnt[i]+=cnt[i|(1<<j)];
}
}
}
for(int i=0;i<(1<<t);i++){
d[i]=1e9;
}
d[0]=0;
for(int i=1;i<(1<<t);i++){
int num=cal(i);
for(int j=0;j<t;j++){
if(i>>j&1){
int cost=(cnt[i^(1<<j)]-cnt[i])*num;
int temp=d[i^(1<<j)]+cost;
if(temp<d[i]){
d[i]=temp;
pre[i]=j;
}else if(temp==d[i]){
pre[i]=max(pre[i],j);
}
}
}
}
int ans=d[(1<<t)-1]+tot;
printf("%d\n",ans);
vector<int>res;
int x=((1<<t)-1);
while(x){
res.push_back(pre[x]);
x^=(1<<pre[x]);
}
reverse(res.begin(),res.end());
for(auto i:res){
printf("%d ",i+1);
}
puts("");
}
signed main(){
freopen("tests.in","r",stdin);
int T=1;cin>>T;
while(T--){
solve();
}
return 0;
}