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 

using namespace std;

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

int b[Maxn], dp[Maxn];
vector  a[Maxn];
stack  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)