読者です 読者をやめる 読者になる 読者になる

srupのメモ帳

競プロで解いた問題や勉強したことを記録していくメモ帳

yukicoder No.324 落ちてた閉路グラフ

yukicoder dp

問題

問題概要

省略

解法

dpで解いた。直線になっていれば即座にdpだろうなと思ったけど、円になっていたからすぐにわからず苦戦した。円になっている時に注意しなければならないことは、0オリジンで考えた場合、頂点0を選んだ場合、もし頂点n-1も選んだら、n-1の辺選択したことになるということである。よってdp1dp2で2つの場合に分けて、dpを行った。
dp1[i][j][k] := (0番目の頂点を選ばず、)i番目までを考えて、選んだ頂点がj個でi番目の頂点をk=1の時選び、k=0の時選んでいない
dp1[i][j][k] := (0番目の頂点を選び、)i番目までを考えて、選んだ頂点がj個でi番目の頂点をk=1の時選び、k=0の時選んでいない
という配列を考えてループで回した。漸化式は、iの時頂点を選択していて、i+1の時も選択する場合のみ、w[i]の辺の価値を手に入れることになるので、dp + w[i]という形になる。これを考えて、作ればいい。ただし、頂点0を選択していて、頂点i=n-2を選ぶ時は、w[i + 1]の価値も手に入れることになることに注意。

ミス

if(i == n - 2){//0番目の頂点を選んでいるのでw[i + 1]も考える
    dp2[i + 1][j + 1][1] = max(dp2[i][j][k] + w[i + 1], dp2[i + 1][j + 1][1]);
}

上の場合のことを完全に忘れていて、デバッグしてわかった。yukicoderの問題はサンプルが優しくていい。

コード

#include <iostream>
#include <algorithm>
#include <vector>
#include <cstdio>
typedef long long ll;
using namespace std;
#define rep(i,n) for(int i=0;i<(n);i++)

int n, m;
//dp1[i][j][k] := (0番目の頂点を選ばず、)i番目までを考えて、
//選んだ頂点がj個でi番目の頂点をk=1の時選び、k=0の時選んでいない
int dp1[3010][3010][2];
//dp1[i][j][k] := (0番目の頂点を選び、)i番目までを考えて、
//選んだ頂点がj個でi番目の頂点をk=1の時選び、k=0の時選んでいない
int dp2[3010][3010][2];
const int INF = 1e9;
int main(void){
    cin >> n >> m;
    vector<int> w(n);
    rep(i, n) cin >> w[i];

    rep(i, 3010)rep(j, 3010)rep(k, 2){
        dp1[i][j][k] = dp2[i][j][k] = -INF;
    }
    dp1[0][0][0] = 0;
    for (int i = 0; i < n; ++i){
        for (int j = 0; j <= i + 1; ++j){
            for (int k = 0; k < 2; ++k){
                if(dp1[i][j][k] == -INF)continue;
                if(k == 0){//i番目の頂点を選んでいない
                    dp1[i + 1][j + 1][1] = max(dp1[i][j][k], dp1[i + 1][j + 1][1]);//i+1番目を選ぶ
                    dp1[i + 1][j][0] = max(dp1[i][j][k], dp1[i + 1][j][0]);//i+1番目を選ばない
                }else{//i番目の頂点を選んでいる
                    dp1[i + 1][j + 1][1] = max(dp1[i][j][k] + w[i], dp1[i + 1][j + 1][1]);//i+1番目を選ぶ
                    dp1[i + 1][j][0] = max(dp1[i][j][k], dp1[i + 1][j][0]);//i+1番目を選ばない
                }
            }
        }
    }

    dp2[0][1][1] = 0;
    for (int i = 0; i < n - 1; ++i){
        for (int j = 0; j <= i + 1; ++j){
            for (int k = 0; k < 2; ++k){
                if(dp2[i][j][k] == -INF)continue;
                if(k == 0){//i番目の頂点を選んでいない
                    dp2[i + 1][j][0] = max(dp2[i][j][k], dp2[i + 1][j][0]);//i+1番目を選ばない
                    if(i == n - 2){//0番目の頂点を選んでいるのでw[i + 1]も考える
                        dp2[i + 1][j + 1][1] = max(dp2[i][j][k] + w[i + 1], dp2[i + 1][j + 1][1]);//i+1番目を選ぶ
                    }else{
                        dp2[i + 1][j + 1][1] = max(dp2[i][j][k], dp2[i + 1][j + 1][1]);//i+1番目を選ぶ
                    }
                }else{//i番目の頂点を選んでいる
                    dp2[i + 1][j][0] = max(dp2[i][j][k], dp2[i + 1][j][0]);//i+1番目を選ばない
                    if(i == n - 2){//0番目の頂点を選んでいるのでw[i + 1]も考える
                        dp2[i + 1][j + 1][1] = max(dp2[i][j][k] + w[i] + w[i + 1], dp2[i + 1][j + 1][1]);//i+1番目を選ぶ
                    }else{
                        dp2[i + 1][j + 1][1] = max(dp2[i][j][k] + w[i], dp2[i + 1][j + 1][1]);//i+1番目を選ぶ
                    }
                }
            }
        }
    }

    int ans = -INF;
    rep(i, n){
        rep(j, 2){
            ans = max(ans, dp1[n - 1][m][j]);
            ans = max(ans, dp2[n - 1][m][j]);
        }
    }
    printf("%d\n", ans);
    return 0;
}