天天看點

Codeforces Round #523 (Div. 2) C. Multiplicity 【dp】

題目連結:http://codeforces.com/contest/1061/problem/C

思路:

像類似這種求有銜接關系的數列要第一時間想到  dp[ i ] = dp[ i ] + dp[ i - 1 ] 這種狀态的dp,為什麼?

假設我要讓一個原數組下标為 4的數變成下标為 3 的數,那麼要想數列存在,那麼 必須存在 至少一個 長度為2的數列,在這些數列的尾部加上這個原數組下标為4的數才能構成一個長度為 3 的數列,由此對應的長度為3的個數就是長度為2的個數,加上本身存在的長度為 3的個數,是以得到  dp[ i ] = dp[ i ] + dp[ i - 1] ;

如何确定一個數放在下标 小于原下标的位置(因為數列隻能通過删除某一個數才能改變原數組下标,是以數組下标隻可能小不可能大于自身)

隻要求出目前這個數的所有約數,對應的約數就是能放置的位置,通過 n*sqrt (n) 時間複雜度的算法能夠求出所有數的個數。

這裡有幾點需要注意的:

1,約數放置到vector裡面的時候,一定要按順序放置,通過stack的輔助代替sort的使用,減少時間複雜度,為什麼要按順序放置,當你在周遊約數的時候,必須要從大到小周遊,如果你按小到大,或者随便周遊,當你更新約數大的dp,有可能會使用到你剛剛才更新過的約數小的dp,這時候結果就是錯的了(自己紙上模拟一下就知道了,跟背包空間優化的逆序周遊dp是一樣的道理,如果你用二維dp自然就不用考慮這個)

2,如果目前周遊到的約數比原數組的最大下标還要大時直接不考慮。

之前一直覺得用n*sqrt(n) 的算法求約數會逾時,寫到後面才發現,周遊dp的時候,我們并不需要從頭到尾逐個更新dp的值,因為對應的約數就是dp的位置了,是以直接周遊約數就行了,一個數的約數一般不會太大,是以時間是很充裕的。

#include <bits/stdc++.h>

using namespace std;

const int Maxn = 1e5+10;
const int Mod = 1e9+7;

int b[Maxn], dp[Maxn];
vector <int> a[Maxn];
stack <int> sk;

void solve (int x) { // 約數打表
    int tmp = b[x];
    for (int i = 1; i*i <= tmp; ++i) {
        if (tmp % i != 0) continue;
        if (i*i == tmp) {
            a[x].push_back (i);
        } else {
            a[x].push_back (i); sk.push(tmp/i);
        }
    }
    while (!sk.empty()) {  // 保證數列是從小到大排列的,為dp的逆序
        a[x].push_back (sk.top());
        sk.pop();
    }
}

int main (void)
{
    int n, tmp;
    scanf ("%d", &n);
    for (int i = 0; i < n; ++i) {
        scanf ("%d", &b[i]);
        solve (i);
    }
    dp[0] = 1;
    for (int i = 0; i < n; ++i) {
        int m = a[i].size();
        for (int j = m-1; j >= 0; --j) {  // 從大到小周遊更新dp
            int tmp = a[i][j];
            if (tmp <= n && dp[tmp-1]) { // 保證約數在n範圍内,超出n的約數不考慮
                dp[tmp] = (dp[tmp-1]+dp[tmp]) % Mod;
            }
        }
    }
    int ans = 0;
    for (int i = 1; i <= n; ++i) {
        ans = (ans + dp[i]) % Mod;
    }
    printf ("%d\n", ans);
    return 0;
}
           
dp