题目链接: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;
}
本文详细解析了CodeForces平台上的C题解题思路,采用动态规划方法,通过求解数列中各元素的约数来更新DP状态,实现高效求解。文章强调了约数顺序的重要性及时间复杂度的优化。
1348

被折叠的 条评论
为什么被折叠?



