1. 程式人生 > >Our happy ending

Our happy ending

main 並且 mes 第一個 acm 解決 沒有 方式 序列

題目鏈接

  • 題意:
    輸入n、k、L,n個數,最大值不超過L,在序列中取若幹個數和能達到k的序列個數
    n,k<=20 , 0<=L<=10^9
  • 分析:
    題目關鍵在於和k比較小,所以能夠考慮DP。
    先說一下自己比賽時候想到的DP狀態。好久才發現錯了。

    。。。

    DP[i][j]表示當前是序列中的第i個數(必須選),和能取到j的序列個數。

    這個狀態的問題就是在於反復:對於一個確定的序列來說,由於能夠選取某些數字來求和,所以對於DP[i]來說,同一個序列能夠得到非常多不同的和j,也就是被計算了非常多次從而導致反復。
    對照一下之前見過的一個類似的問題,給定一個序列。問取若幹個數能達到和k的方案數:DP[i][j]表示當前是序列中的第i個數(必須選),和能取到j的序列個數,這個問題這樣表示就是正確的了。
    為什麽會這樣呢?就是由於答案的推斷方式不同:第一個問題的答案推斷是,當前序列假設能取到若幹個數使得和為k。那麽答案加一;第二個問題則是,假設有x種方案。從當前序列中取出若幹個數使得和為k。那麽答案加x。也就是說。第一個問題的推斷根據是序列是否滿足,第二個則是取出來的集合是否滿足。而上述DP的方案事實上就是在選擇集合中的元素,由於DP[i][j]表示i必須選,也就是在集合中存在。

    那麽到此,怎樣推斷一個序列是否是滿足的呢:從左到右枚舉序列當前位置的值,那麽要求當前序列是否是滿足的也就是求和能否到k,那麽不可缺少的須要記錄一下當前序列所能到達的和都有誰。至此。就能夠用狀壓DP來解了。敘述一下二進制代表的意義:第一個1代表和為1。第二個1表示和為2……
    反思一下程序寫的時候的問題:沒實用滾動數組,導致錯了非常多次。

    因為這個題目的狀態轉移僅僅會向更大的狀態值(也能夠是自己)轉移。所以能夠不採用滾動數組,一維解決。

    可是這樣就須要額外註意一個問題。自己轉移到自己的問題。因為這裏理解的不是非常好。導致一直查不出來BUG。學習了。

const int MAXN = 21;
int dp[1 << MAXN];

int main()
{
    int T, n, sum, Max;
    RI(T);
    FE(kase, 1, T)
    {
        RIII(n, sum, Max);
        int Min = min(sum, Max);
        int all = 1 << sum;
        CLR(dp, 0); dp[0] = 1;
        REP(i, n)
        {
            FED(j, all - 1, 0)
            {
                if (dp[j] == 0)
                    continue;
                int x = dp[j];
                for (int k = 1; k <= Min; k++)
                {
                    int nxt = j | ((j << k) & (all - 1)) | (1 << (k - 1));
                    dp[nxt] += x;
                    if (dp[nxt] >= MOD)
                        dp[nxt] -= MOD;
                }
                if (Max - Min > 0)
                    dp[j] = (dp[j] + 1LL * (Max - Min) * x) % MOD;
            }
        }
        int ans = 0;
        FF(i, 1 << (sum - 1), all)
        {
            ans += dp[i];
            if (ans >= MOD)
                ans -= MOD;
        }
        WI(ans);
    }
    return 0;
}


用dp[1]表示0的方法,事實上不好。由於3(11)和2(10)表示的意義是一樣的,並且不含或者包括零(第一個一)沒有什麽意義
const int MAXN = 21;

int dp[1 << MAXN];

int main()
{
    int T, n, sum, Max;
    RI(T);
    FE(kase, 1, T)
    {
        RIII(n, sum, Max);
        int Min = min(sum, Max);
        int all = 1 << (sum + 1);
        CLR(dp, 0); dp[1] = 1;
        REP(i, n)
        {
            FED(j, all - 1, 1)
            {
                if (dp[j] == 0)
                    continue;
                int x = dp[j];
                for (int k = 1; k <= Min; k++)
                {
                    int nxt = j | ((j << k) & (all - 1));
                    dp[nxt] += x;
                    if (dp[nxt] >= MOD)
                        dp[nxt] -= MOD;
                }
                if (Max - Min > 0)
                    dp[j] = (dp[j] + 1LL * (Max - Min) * x) % MOD;
            }
        }
        int ans = 0;
        FF(i, 1 << sum, all)
        {
            ans += dp[i];
            if (ans >= MOD)
                ans -= MOD;
        }
        WI(ans);
    }
    return 0;
}



Our happy ending