1. 程式人生 > 其它 >【圖上狀壓dp】2021CCPC女生賽C題連鎖商店

【圖上狀壓dp】2021CCPC女生賽C題連鎖商店

連鎖商店
vp時候dfs了一發,線性dp了一發。dfs的時間複雜度是n!,圖上怎麼線性dp...
當時考慮到了狀壓,但是想把·每個景點直接狀壓,時間複雜度是2^32,就沒有多想。但實際上應該把每種公司狀壓,這樣若一種公司只出現了一次則取紅包一定是最優的。只有在公司出現次數大於等於2的時候才考慮取和不取,也就是使用狀壓,而公司出現次數最多為36/2=18次。
設狀態方程f[i][S]為從1走到i點,第二類公司的狀態為S時的最大紅包金額

點選檢視程式碼
int g[N][N]; int c[N]; int w[N];
map<int,int>mp; bool st[N];
int f[N][1<<18];int idx;int id[N],dig[N];
int main(){
    int n,m,u,v;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>c[i],mp[c[i]]++;
    for(int i=1;i<=n;i++) if(mp[i]>=2) id[idx] =i , dig[i] =idx++; //離散化第二類公式,方便做狀壓
    for(int i=1;i<=n;i++) if(mp[c[i]]==1)st[i]=1;//標記第一類
    for(int i=1;i<=n;i++) cin>>w[i];
    while (m--){
        cin>>u>>v;
        g[u][v]=1;
    }
    for (int k = 1; k <= n; k++) { //floyd跑出兩點之間是否聯通
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                if (i != j) g[i][j] |= (g[i][k] & g[k][j]);
            }
        }
    }
    //初始化第一個節點
    if(st[1]) f[1][0] = w[c[1]];
    else f[1][1<<dig[c[1]]] = w[c[1]];
    //從前往後更新
    for(int u=1;u<=n;u++){
        for(int S=0;S<1<<idx;S++){
            for(int v=1;v<=n;v++){
                if(!g[u][v]) continue;
                if(st[v]) f[v][S] = max(f[v][S],f[u][S] +w[c[v]]);
                else{
                    if(!((S>>dig[c[v]])&1)){
                        f[v][S|(1<<dig[c[v]])] = max(f[v][S|(1<<dig[c[v]])],f[u][S] +w[c[v]]);
                    }
                }
            }
        }
    }
    for (int i = 1; i <= n; i++) {
        int ans = 0;
        for (int S = 0; S < 1 << idx; S++) ans = max(ans, f[i][S]);
        cout << ans << endl;
    }
    return 0;
}