《算法竞赛进阶指南》0x58 数据结构优化DP

0x58 数据结构优化DP

清理班次

题意:

给定区间 [ 1 , T ] [1,T] [1,T],给定 n n n 条线段 [ l i , r i ] [l_i, r_i] [li,ri],选择最少数量的线段,使区间每个点都被覆盖

解析:

将所有线段按右端点 r i r_i ri 升序排序。

f i f_i fi 为覆盖区间 [ 1 , i ] [1,i] [1,i] 所有点的最少线段数。对每条线段 [ l i , r i ] [l_i, r_i] [li,ri],都有转移 f r i = min ⁡ ( f r i , min ⁡ j = l i − 1 r i f j + 1 ) f_{r_i} = \min(f_{r_i},\min\limits_{j = l_i-1}\limits^{r_i}f_j+1) fri=min(fri,j=li1minrifj+1)

时间复杂度为 O ( n 2 ) O(n^2) O(n2)

但可以发现 min ⁡ j = l i − 1 r i f j \min\limits_{j = l_i-1}\limits^{r_i}f_j j=li1minrifj 是区间最小值,可以用线段树维护最小值,支持区间查询和单点修改。

初值 f 0 f_0 f0 = 0,将所有点的坐标+1。因此初值为 f 1 = 0 f_1 = 0 f1=0

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define fi first
#define se second
#define debug(x) cerr << #x << ": " << (x) << endl
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
const int maxn = 1e6+10;
const int maxm = 1e5+10;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> pii;

struct seg{
	int l, r;
	seg(){}
	seg(int l, int r) : l(l), r(r){}
	bool operator < (const seg &b) const{
		return r < b.r;
	}
}s[maxn];
int f[maxn];
int n, T, l, r;

inline int ls(int x){return x << 1;}
inline int rs(int x){return x << 1 | 1;}

int t[maxn << 2];
void pushup(int k){
	t[k] = min(t[ls(k)], t[rs(k)]);
}
void build(int k, int l, int r){
	if(l == r){
		t[k] = INF;
		return;
	}
	int mid = (l+r) >> 1;
	build(ls(k), l, mid);
	build(rs(k), mid+1, r);
	pushup(k);
}
void modify(int k, int l, int r, int pos, int v){
	if(l == r && l == pos){
		t[k] = v;
		return;
	}
	int mid = (l+r) >> 1;
	if(pos <= mid)
		modify(ls(k), l, mid, pos, v);
	else
		modify(rs(k), mid+1, r, pos, v);
	pushup(k);
}
int query(int k, int l, int r, int x, int y){
	if(x <= l && y >= r)
		return t[k];
	int mid = (l+r) >> 1;
	int res = INF;
	if(x <= mid)
		res = min(res, query(ls(k), l, mid, x, y));
	if(y > mid)
		res = min(res, query(rs(k), mid+1, r, x, y));
	return res;
}

int main(){
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	
	cin >> n >> T;
	T++;
	rep(i, 1, n){
		cin >> l >> r;
		l++, r++;
		l = max(2, l); r = min(T, r);
		s[i] = seg(l, r);
	}
	
	sort(s+1, s+1+n);
	build(1, 1, T);
	modify(1, 1, T, 1, 0);
	memset(f, INF, sizeof(f));
	f[1] = 0;
	
	rep(i, 1, n){
		f[s[i].r] = min(f[s[i].r], query(1, 1, T, s[i].l-1, s[i].r)+1);
		modify(1, 1, T, s[i].r, f[s[i].r]);
	}
	
	if(f[T] == INF)
		cout << -1 << endl;
	else
		cout << f[T] << endl;
	return 0;
}


清理班次2

题意:

给定区间 [ M , E ] [M,E] [M,E],给定 n n n 条线段 [ l i , r i ] [l_i, r_i] [li,ri],选择该线段的代价为 c i c_i ci,询问使 [ M , E ] [M,E] [M,E] 被覆盖的最小代价。

解析:

基本同上题。

f i f_i fi 表示覆盖区间 [ M , i ] [M,i] [M,i] 的最小代价。

对于每条线段, f b i = min ⁡ ( f b i , min ⁡ j = l i − 1 r i f j + c i ) f_{b_i} = \min(f_{b_i},\min\limits_{j = l_i-1}\limits^{r_i}f_j+c_i) fbi=min(fbi,j=li1minrifj+ci)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define fi first
#define se second
#define debug(x) cerr << #x << ": " << (x) << endl
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
const int maxn = 1e5+10;
const int maxm = 1e5+10;
const ll INF = 0x3f3f3f3f3f3f3f3f;
typedef pair<int, int> pii;

struct seg{
	int l, r; 
	ll c;
	seg(){}
	seg(int l, int r, ll c) : l(l), r(r), c(c){}
	bool operator < (const seg &b) const{
		return r < b.r;
	}
}s[maxn];
ll f[maxn];
int n, L, R, l, r, w;

inline int ls(int x){return x << 1;}
inline int rs(int x){return x << 1 | 1;}

ll t[maxn << 2];
void pushup(int k){
	t[k] = min(t[ls(k)], t[rs(k)]);
}
void build(int k, int l, int r){
	if(l == r){
		t[k] = INF;
		return;
	}
	int mid = (l+r) >> 1;
	build(ls(k), l, mid);
	build(rs(k), mid+1, r);
	pushup(k);
}
void modify(int k, int l, int r, int pos, ll v){
	if(l == r && l == pos){
		t[k] = v;
		return;
	}
	int mid = (l+r) >> 1;
	if(pos <= mid)
		modify(ls(k), l, mid, pos, v);
	else
		modify(rs(k), mid+1, r, pos, v);
	pushup(k);
}
ll query(int k, int l, int r, int x, int y){
	if(x <= l && y >= r)
		return t[k];
	int mid = (l+r) >> 1;
	ll res = INF;
	if(x <= mid)
		res = min(res, query(ls(k), l, mid, x, y));
	if(y > mid)
		res = min(res, query(rs(k), mid+1, r, x, y));
	return res;
}


int main(){
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	
	cin >> n >> L >> R;
	L++, R++;
	rep(i, 1, n){
		cin >> l >> r >> w;
		l++, r++;
		l = max(l, L); r = min(r, R);
		s[i] = seg(l, r, w);
	}
	
	sort(s+1, s+1+n);
	build(1, L-1, R);
	modify(1, L-1, R, L-1, 0);
	memset(f, INF, sizeof(f));
	f[L-1] = 0;
	
	rep(i, 1, n){
		f[s[i].r] = min(f[s[i].r], query(1, L-1, R, s[i].l-1, s[i].r) + s[i].c);
		modify(1, L-1, R, s[i].r, f[s[i].r]);
	}
	
	if(f[R] == INF)
		cout << -1 << endl;
	else
		cout << f[R] << endl;
	return 0;
}


赤壁之战

题意:

给定长度为 n n n 的序列 a a a,询问有多少个长度为 m m m 的严格递增子序列。

解析:

f i , j f_{i,j} fi,j 为在以第 i i i 个数为结尾的,长度为 j j j 的严格递增子序列的个数

长度大的递增子序列是由长度小的递增子序列后面接上几个数形成的,所以按长度划分dp 的阶段。

f i , j = ∑ k = 1 i − 1 f k , j − 1 [ a k < a i ] f_{i,j} = \sum\limits_{k=1}\limits^{i-1} f_{k, j-1}[a_k < a_i] fi,j=k=1i1fk,j1[ak<ai]

时间复杂度为 O ( n 3 ) O(n^3) O(n3),需要优化。

对于长度为 j j j 的阶段,需要快速求和上一阶段满足条件的状态。对 i i i 而言,上一阶段有贡献的状态需要满足条件: k < i k<i k<i a k < a i a_k<a_i ak<ai,二维偏序,可以用树状数组在 log ⁡ n \log n logn 的时间内解决。

所以,用树状数组维护上一阶段的状态,在本阶段,查询 a i − 1 a_i-1 ai1 的前缀和。

初始条件: f i , 1 = 1 f_{i,1} = 1 fi,1=1,需要离散化,答案为 ∑ i = 1 n f i , m \sum\limits_{i=1}\limits^n f_{i, m} i=1nfi,m

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define fi first
#define se second
#define debug(x) cerr << #x << ": " << (x) << endl
#define rep(i, a, b) for(int i = (a); i <= (b); i++)
const int maxn = 5e3+10;
const int maxm = 5e3+10;
const ll mod = 1e9+7;
const int N = 5e3;
const int INF = 0x3f3f3f3f;
typedef pair<int, int> pii;

inline int lowbit(int x){return x & (-x);}
struct BIT{
	ll c[maxn];
	void add(int x, int v){
		for(; x <= N; x += lowbit(x))
			c[x] = (c[x] + v) % mod;
	}
	ll query(int x){
		ll res = 0;
		while(x){
			res = (res+c[x]) % mod;
			x -= lowbit(x); 
		}
		return res;
	}
	void init(){
		memset(c, 0, sizeof(c));
	}
}tr;
ll dp[maxn][maxn];
vector<ll> t;
ll a[maxn], n, m;
void solve(int T){
	
	cin >> n >> m;
	t.clear();
	tr.init();
	for(int i = 1; i <= n; i++){
		cin >> a[i];
		t.push_back(a[i]);
	}
	sort(t.begin(), t.end());
	t.erase(unique(t.begin(), t.end()), t.end());
	for(int i = 1; i <= n; i++)
		a[i] = lower_bound(t.begin(), t.end(), a[i])-t.begin()+1;
	
	for(int i = 1; i <= n; i++)
		dp[i][1] = 1;
	for(int len = 2; len <= m; len++){
		tr.init();
		for(int i = 1; i <= n; i++){
			dp[i][len] = tr.query(a[i]-1);
			tr.add(a[i], dp[i][len-1]);
		}
	}
	int res = 0;
	for(int i = 1; i <= n; i++)
		res = (res + dp[i][m]) % mod;
	printf("Case #%d: ", T);
	cout << res << endl;
	return;
}
int main(){
	
	int T;
	cin >> T;
	for(int i = 1; i <= T; i++){		
		solve(i);
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值