普及状压选做 (持更)

普及状压选做


第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\n", ans.first, ans.second/2);
        else printf("0 0\n");
    }
    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<<'\n';
        }
        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] << '\n';
    }
    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("\n{");
    rep(i,0,n-1) if(S&(1<<i)) cout<<i<<", ";
    puts("\n}\n");
}

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;
}

第5题

第6题

posted @ 2020-07-10 09:42  xwmwr  阅读(164)  评论(2编辑  收藏  举报