題目連結: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;
}