普及状压选做
第1题
看了题解。
把点 (n) 作为不存在的第二起始点真是惊为天人的操作。(其实是我太菜啦)
#include<bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for(int i=(l);i<=(r);++i)
const int N = 13;
int n,m,v[N],g[N+1][N+1];
pair<int,int>f[1<<N][N+1][N+1];
// f[S][i][j] 表示点集为S, 最前为i, 次前为j(不存在为0), first为最大总价值, second为数量
// f[S][i][j] = f[S-{i}][j][k] + v[i]*v[j] + (g[i)[k]?a[i]*a[j]*a[k]:0);
int main() {
int q;cin>>q;while(q--) {
memset(g,0,sizeof g);
scanf("%d%d", &n,&m);
rep(i,0,n-1)scanf("%d",&v[i]);
rep(i,1,m) {int a,b; scanf("%d%d", &a,&b);
--a;--b; g[a][b] = g[b][a] = 1;
}
memset(f,0x80,sizeof f);
rep(i,0,n-1) f[1<<i][i][n] = make_pair(0,1);
pair<int,int> ans = make_pair(0,0);
rep(S,0,(1<<n)-1) {
rep(i,0,n-1) {
if(!((S>>i)&1)) continue;
int Si = S^(1<<i);
rep(j,0,n-1) {
if(!g[i][j]) continue;
if(!((Si>>j)&1)) continue;
int Sij = Si^(1<<j)^(1<<n);
rep(k,0,n) {
if(!((Sij>>k)&1)) continue;
int tmp = f[Si][j][k].first + v[i]*v[j] + (g[i][k]?v[i]*v[j]*v[k]:0);
if(f[S][i][j].first < tmp) {
f[S][i][j] = make_pair(tmp, f[Si][j][k].second);
} else if(f[S][i][j].first == tmp) {
f[S][i][j].second += f[Si][j][k].second;
}
}
}
}
}
rep(i,0,n-1)rep(j,0,n-1) {
if(ans.first<f[(1<<n)-1][i][j].first) {
ans = f[(1<<n)-1][i][j];
} else if(ans.first==f[(1<<n)-1][i][j].first) {
ans.second += f[(1<<n)-1][i][j].second;
}
}
rep(i,0,n-1) ans.first += v[i];
if(ans.second) printf("%d %d
", ans.first, ans.second/2);
else printf("0 0
");
}
return 0;
}
第2题
似乎是轮廓线的状压
先上乘法原理, 每行的状态用 ({ 0,1 }) 表示, (1) 表示一个竖着的骨牌的上端, 其余为 (0)。
转移似乎很难想啊……但发现一次转移无非考虑在本行怎么摆竖骨牌的上端。
配合预处理大法复杂度减少一个 (O(n)), 开心食用。
最后复杂度是 (O(n*2^{m+1}))
#include<bits/stdc++.h>
using namespace std;
#define sub(i,k) for(int i=0;i<(1<<(k));++i)
#define rep(i,l,r) for(int i=(l);i<=(r);++i)
const int N = 11;
int n,m;
long long f[N+1][1<<N];
int good[1<<N];
int main()
{
while(scanf("%d%d", &n, &m)==2 && n && m) {
memset(good, 0, sizeof good);
sub(i,m) {
bool ok = true;
int cnt = 0;
rep(j,0,m-1) {
if((i>>j)&1) {
if(cnt&1) ok = false;
cnt=0;
} else {
++cnt;
}
}
if(cnt&1) ok = false;
good[i] = ok;
// rep(j,0,m-1) cout<<((i>>j)&1);
// cout<<' '<<ok<<'
';
}
memset(f,0,sizeof f);
f[0][0] = 1ll;
rep(i,1,n) sub(now,m) sub(pre,m) {
if(now&pre) continue;
if(!good[now|pre]) continue;
f[i][now] += f[i-1][pre];
}
cout << f[n][0] << '
';
}
return 0;
}
第3题
经典题哟。
写了一下午自闭了。
换了个写法20min过啦。
#include<bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for(int i=(l);i<=(r);++i)
const int N = 100;
const int M = 10;
int n,m,a[N+1];
int cnt, nodes[61], sum[61];
long long f[2][61][61];
int main() {
m = 10;
scanf("%d%d", &n,&m);
rep(i,1,n)rep(j,0,m-1) { char c;cin>>c;if(c=='H')a[i]|=(1<<j); }
rep(S,0,(1<<m)-1) {
if(S&(S<<1)) continue;
if(S&(S<<2)) continue;
nodes[++cnt] = S;
int tmp = nodes[cnt];
while(tmp) tmp-=tmp&(-tmp), ++sum[cnt];
}
rep(i,1,cnt) {
if(nodes[i] & a[1]) continue;
f[1&1][i][1] = sum[i];
}
long long ans = 0ll;
rep(i,2,n) rep(j,1,cnt) { if(nodes[j]&a[i])continue;
rep(k,1,cnt) {
if(nodes[k]&a[i-1])continue;
f[i&1][j][k] = 0;
rep(l,1,cnt) {
if(nodes[l]&a[i-2])continue;
if(nodes[j]&nodes[k]) continue;
if(nodes[j]&nodes[l]) continue;
f[i&1][j][k] = max(f[i&1][j][k], f[(i-1)&1][k][l]+sum[j]);
}
ans = max(ans, f[i&1][j][k]);
}
}
cout << ans;
return 0;
}
第4题
题意就是求生成树, 因为最优解一定是原图的一个生成树。
刚开始想了个 sb做法, 妄想把每个点的深度信息塞到状态里, 后来发现这不就是爆搜 =_=。
参考题解, 用分层转移的手法, 转移的时候枚举当前生成树的子集作为最后一层, 然后转移, 注意要把当前集的树高塞进状态里。
转移的时候枚举当前集的子集, 将其作为前一状态。、但是无法保证枚举出来的就是 “最后一层”, 似乎可能会出现比最优解更小的解!但是可以冷静地想一下,由于转移总是合法的(即总是生成树), 所以得到的解一定 (ge) 最优解, 而最优解一定会被找出, 所以正确性可以保证owo。
技巧总结: 二进制数表示集合时枚举某集合的子集的方法。
重大失误总结: 没有认识到极限数据(特别是小数据)对答案的影响。
#include<bits/stdc++.h>
using namespace std;
#define rep(i,l,r) for(int i=(l);i<=(r);++i)
const int N = 12;
int n,m,g[N][N],trans[1<<N][1<<N];
long long f[1<<N][N];
void outS(int S) {
puts("
{");
rep(i,0,n-1) if(S&(1<<i)) cout<<i<<", ";
puts("
}
");
}
int main() {
memset(g,0x3f,sizeof g);
memset(f,0x3f,sizeof f);
scanf("%d%d", &n,&m);
if(n==1) {
cout << 0;
return 0;
}
while(m--) {
int u,v,w; scanf("%d%d%d", &u,&v,&w);
--u; --v;
g[u][v] = g[v][u] = min(g[u][v], w);
}
rep(S,0,(1<<n)-1) {
// outS(S);
for(int S0=(S-1)&S;S0;S0=(S0-1)&S) {
int nS = S^S0;
bool ok = true;
int sum = 0;
rep(v,0,n-1) if((1<<v)&nS) {
int Val = 0x3f3f3f3f;
rep(u,0,n-1) if((1<<u)&S0) Val = min(Val, g[u][v]);
if(Val == 0x3f3f3f3f) {ok=false; break;}
sum += Val;
}
if(!ok) trans[S][nS] = 0x3f3f3f3f;
else trans[S][nS] = sum;
}
}
rep(i,0,n-1) f[1<<i][0] = 0;
long long ans = 0x3f3f3f3f;
rep(S,0,(1<<n)-1) {
rep(Sdep,1,n-1) {
for(int S0=(S-1)&S; S0; S0 = (S0-1)&S) if(trans[S][S^S0] != 0x3f3f3f3f)
f[S][Sdep] = min(f[S][Sdep], f[S0][Sdep-1]+Sdep*(trans[S][S^S0]));
if(S==(1<<n)-1) ans = min(ans, f[S][Sdep]);
}
}
cout << ans;
return 0;
}