CodeForces - 597C Subsequences
一拿到这个题感觉很新颖,不过没啥思路。
看了大家的题解,一个$O(n^2 imes k)$的做法还是能写一写,虽然过不了。
因为本题所有数字是个全排列,我们设状态是$dp[i][j]$,表示长度为$i$的子序列,以$j$结尾的方案数。
状态转移方程是:$dp[i][j]=sum_{t=1}^{j-1}dp[i-1][t]$。预处理所有$dp[1][j]$,为了保证$t$都在$j$前面,我们可以边读入边处理。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e3 + 10; typedef long long ll; ll dp[12][maxn]; int main() { int n, k; scanf("%d %d", &n, &k); memset(dp, 0, sizeof(dp)); for(int p = 1; p <= n; p++) { int x; scanf("%d", & x); dp[1][x] = 1; for(int i = 1; i <= k + 1; i++) { for(int t = 1; t <= x - 1; t++) { dp[i][x] += dp[i - 1][t]; } } } ll ans = 0; for(int i = 1; i <= n; i++) { ans += dp[k + 1][i]; } printf("%I64d ", ans); return 0; }
这么大的复杂度显然是过不了的,那就想想怎么优化。
我们观察到求和那一项求的是前缀和,所以我们可以用树状数组来维护,复杂度降为$O(n imes k imes logn)$。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e5 + 10; typedef long long ll; ll dp[12][maxn]; int n, k; void add(int id, int x, ll val) { while(x <= n) { dp[id][x] += val; x += (x & -x); } } ll sum(int id, int x) { ll res = 0; while(x > 0) { res += dp[id][x]; x -= (x & -x); } return res; } int main() { scanf("%d %d", &n, &k); memset(dp, 0, sizeof(dp)); for(int p = 1; p <= n; p++) { int x; scanf("%d", & x); add(1, x, 1); for(int i = 2; i <= k + 1; i++) { ll tmp = sum(i - 1, x - 1); add(i, x, tmp); } } ll ans = 0; ans = sum(k + 1, n); printf("%I64d ", ans); return 0; }
15年CCPC有道类似题目:HDU 5542
只不过需要离散化一下,但这道题所有数字可能有重复的,不过不影响最终结果,手推一下小数据就知道了。
#include <bits/stdc++.h> using namespace std; const int maxn = 1e3 + 5; typedef long long ll; const ll mod = 1e9 + 7; ll dp[maxn][maxn]; int maxv; void add(int id, int x, ll val) { while(x <= maxv) { dp[id][x] = (dp[id][x] + val) % mod; x += (x & -x); } } ll sum(int id, int x) { ll res = 0; while(x > 0) { res = (res + dp[id][x]) % mod;; x -= (x & -x); } return res; } int a[maxn]; int dis[maxn]; int main() { int T, kase = 0; scanf("%d", &T); while(T--) { int n, k; scanf("%d %d", &n, &k); for(int i = 1; i <= n; i++) scanf("%d", &a[i]), dis[i] = a[i]; sort(dis + 1, dis + n + 1); for(int i = 1; i <= n; i++) { a[i] = lower_bound(dis + 1, dis + n + 1, a[i]) - dis; maxv = max(a[i], maxv); } for(int i = 1; i <= n; i++) { for(int j = 1; j <= maxv; j++) { dp[i][j] = 0; } } for(int p = 1; p <= n; p++) { add(1, a[p], 1); for(int i = 2; i <= k; i++) { ll tmp = sum(i - 1, a[p] - 1); add(i, a[p], tmp); } } ll ans = 0; ans = sum(k, maxv); printf("Case #%d: %I64d ", ++kase, ans); } return 0; }
暑假这道DP,当时状态方程看了别人的也很难懂,现在再拿出来搞一搞。
难受,晚上帮舍友做笔试题,好浪费时间,关键他面试不过……
明天再做完吧……