2024 年春节集训 _ 第二课 - 数据结构优化动态规划
【例题 \(1\) 】 递增子序列
考虑 \(dp.\)
\(dp[i][j]\) 表示以元素 \(i\) 为结尾,长度为 \(k\) 的方案数。
那么显而易见就有一个转移方程:
\[dp[i][j]=\sum_{a[k]<a[i] ,\ k<i} dp[k][j-1]
\]
先抛去第二维度的 \(j\) ,这是可以做一个关于 \(a[i]\) 值的大小,数值是 \(dp\) 的树状数组的。
维护两个操作:单点修改,区间查询。
当然因为 \(a[i]\) 实在是太大了,所以离散化。
如果加上第二层维度那就建立 \(k\) 个树状数组就好了。
程式
#include <bits/stdc++.h>
#define int long long
#define ls (u << 1)
#define rs (u << 1 | 1)
#define lson ls, l, mid
#define rson rs, mid + 1, r
#define mid ((l + r) >> 1)
const int inf = 1e18;
const int N = 1e4 + 10;
const int mod = 123456789;
using namespace std;
int n, m;
struct ta {
int tree[N];
inline int lowbit(int x) { return x & -x; }
inline void update(int x, int k) {
while (x <= N) (tree[x] += k) %= mod, x += lowbit(x);
}
inline int query(int x) {
int res = 0;
while (x) (res += tree[x]) %= mod, x -= lowbit(x);
return res;
}
} t[105];
int tmp, val[N], b[N];
signed main() {
while (~scanf("%lld%lld", &n, &m)) {
for (int i = 1; i <= m; ++i) memset(t[i].tree, 0, sizeof t[i].tree);
memset(val, 0, sizeof val), memset(b, 0, sizeof b);
tmp = 0;
for (int i = 1; i <= n; ++i) scanf("%lld", &val[i]), b[i] = val[i];
stable_sort(b + 1, b + n + 1);
int len = unique(b + 1, b + n + 1) - b - 1;
for (int i = 1; i <= n; ++i) val[i] = lower_bound(b + 1, b + len + 1, val[i]) - b;
for (int i = 1; i <= n; ++i) {
t[1].update(val[i], 1);
for (int j = 1; j < m; ++j) {
tmp = t[j].query(val[i] - 1);
if (!tmp)
break;
t[j + 1].update(val[i], tmp);
}
}
printf("%lld\n", t[m].query(n) % mod);
}
return 0;
}
选做:
【练习 \(1\) 】 折线统计
考虑 \(dp.\)
按 \(x\) 排序,
设 \(dp[i][j][0/1]\) 为以 \(i\) 位置结束,长度为 \(j\) 的 \(0\) 上升 \(1\) 下降的方案数。则有
\[dp[i][j][0]=\sum _{a[k]<a[i],\ k<i } dp[k][j][0] + \sum _{a[k]<a[i],\ k<i } dp[k][j-1][1]
\]
\[dp[i][j][0]=\sum _{a[k]>a[i],\ k<i } dp[k][j-1][0] + \sum _{a[k]>a[i],\ k<i } dp[k][j][1]
\]
由于我是 \(Sach\),所以我不予证明。
注意因为 \(y\) 很大所以要离散化。
优化使用关于 \(a[i]\) 建立维护 \(dp\) 的树状数组。没有试过线段树。
时间复杂度分析:
\(\mathcal{O(n)O(\log \alpha)O(m)=O(nm \log n)}\) 瓶颈在计算 \(dp\) 上。
不过足以通过本题。
程式
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10,mod=1e5+7;
int n,k,mx;
struct fwt{ int tree[N];
inline int lb(int x){ return x&-x; }
inline void upd(int x,int k){ while(x<=mx) (tree[x]+=k)%=mod, x+=lb(x); }
inline int q(int x){ int s=0; while(x) (s+=tree[x])%=mod, x-=lb(x); return s%=mod;}
}t1[12],t2[12];
struct pt{ int x,y; } a[N];
inline bool cmp(pt x,pt y){ return x.x<y.x; }
signed main(){
scanf("%lld%lld",&n,&k), k++;
for(int i=1;i<=n;++i) scanf("%lld%lld",&a[i].x,&a[i].y), mx=max(mx,a[i].y);
stable_sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;++i){
t1[1].upd(a[i].y,1), t2[1].upd(a[i].y,1);
for(int j=2;j<=k;++j){
int tmp1=(t1[j].q(a[i].y-1)+t2[j-1].q(a[i].y-1))%mod, tmp2=((t1[j-1].q(mx)-t1[j-1].q(a[i].y))%mod+(t2[j].q(mx)-t2[j].q(a[i].y))%mod+mod)%mod;
if(!tmp1 && !tmp2) break;
t1[j].upd(a[i].y,tmp1), t2[j].upd(a[i].y,tmp2);
}
}
return printf("%lld\n",(t1[k].q(mx)+t2[k].q(mx))%mod),0;
}