线段树优化DP

引入

在dp转移方程式有求 一段状态的最大值或和 时,可以考虑线段树优化dp。

[USACO11FEB]Generic Cow Protests G

题目描述

Farmer John 的 \(N\) 头奶牛(\(1 \leq N \leq 10^5\))排成一列,正在进行一场抗议活动。第 \(i\) 头奶牛的理智度为 \(a_i\)\(-10^4 \leq a_i \leq 10^4\))。

FJ 希望奶牛在抗议时保持理性,为此,他打算将所有的奶牛隔离成若干个小组,每个小组内的奶牛的理智度总和都要不小于零。

由于奶牛是按直线排列的,所以一个小组内的奶牛位置必须是连续的。请帮助 FJ 计算一下,满足条件的分组方案有多少种。

输入格式

第一行一个整数 \(N\)

接下来 \(N\) 行,每行一个整数,第 \(i\) 行的整数表示第 \(i\) 头奶牛的理智度 \(a_i\)

输出格式

输出满足条件的分组方案对 \(10^9+9\) 取模的结果。

样例 #1

样例输入 #1

4
2
3
-3
1

样例输出 #1

4

提示

所有合法分组方案如下:

  • \(\texttt{(2 3 -3 1)}\)
  • \(\texttt{(2 3 -3) (1)}\)
  • \(\texttt{(2) (3 -3 1)}\)
  • \(\texttt{(2) (3 -3) (1)}\)

参考程序:

#include<bits/stdc++.h>
#define LL long long
using namespace std;

const int maxn=1e5+5;
const LL mod=1e9+9;

int n,a[maxn],s[maxn];
LL f[maxn],tree[maxn<<2];

void update(int spot,int L,int R,int k,LL ne) {
	if(L==R) {
		tree[spot]=(tree[spot]+ne)%mod;
		return ;
	}
	int mid=(L+R)>>1,lson=spot<<1,rson=lson+1;
	if(k<=mid) update(lson,L,mid,k,ne);
	else update(rson,mid+1,R,k,ne);
	tree[spot]=(tree[lson]+tree[rson])%mod;
	return ;
}

LL query(int spot,int L,int R,int x,int y) {
	if(y<L||R<x) return 0;
	if(x<=L&&R<=y) return tree[spot];
	int mid=(L+R)>>1,lson=spot<<1,rson=lson+1;
	return (query(lson,L,mid,x,y)+query(rson,mid+1,R,x,y))%mod;
}

int main() {
	cin>>n;
	for(int i=1;i<=n;i++)
		cin>>a[i],s[i]=a[i]+s[i-1],a[i]=s[i];
	sort(a,a+1+n);
	int cnt=unique(a,a+1+n)-a;
	for(int i=0;i<=n;i++)
		s[i]=lower_bound(a,a+1+cnt,s[i])-a+1;
	update(1,0,n,s[0],1);
	for(int i=1;i<=n;i++) {
		f[i]=query(1,0,n,0,s[i]);
		update(1,0,n,s[i],f[i]);
	}
	cout<<f[n];
	return 0;
}

[USACO05DEC]Cleaning Shifts S

题目描述

约翰的奶牛们从小娇生惯养,她们无法容忍牛棚里的任何脏东西。约翰发现,如果要使这群有洁癖的奶牛满意,他不得不雇佣她们中的一些来清扫牛棚,约翰的奶牛中有 $ N(1 \leq N \leq 10000) $ 头愿意通过清扫牛棚来挣一些零花钱。

由于在某个时段中奶牛们会在牛棚里随时随地地乱扔垃圾,自然地,她们要求在这段时间里,无论什么时候至少要有一头奶牛正在打扫。需要打扫的时段从某一天的第 $ M $ 秒开始,到第 $ E $ 秒结束 $ (0 \leq M \leq E \leq 86399) $。注意这里的秒是指时间段而不是时间点,也就是说,每天需要打扫的总时间是 $ E-M+1 $ 秒。

约翰已经从每头牛那里得到了她们愿意接受的工作计划:对于某一头牛,她每天都愿意在笫 $ T_1 \ldots T_2 $ 秒的时间段内工作 $ (M \leq T_1 \leq T_2 \leq E) $ ,所要求的报酬是 $ S $ 美元 $ (0 \leq S \leq 500000) $。与需打扫时段的描述一样,如果一头奶牛愿意工作的时段是每天的第 $ 10 \ldots 20 $ 秒,那她总共工作的时间是 $ 11 $ 秒,而不是 $ 10 $ 秒。

约翰一旦决定雇佣某一头奶牛,就必须付给她全额的工资,而不能只让她工作一段时间,然后再按这段时间在她愿意工作的总时间中所占的百分比来决定她的工资。现在请你帮约翰决定该雇佣哪些奶牛以保持牛棚的清洁,当然,在能让奶牛们满意的前提下,约翰希望使总花费尽量小。

输入格式

第 $ 1 $ 行: $ 3 $ 个正整数 $ N,M,E $ 。

第 $ 2 $ 到 $ N+1 $ 行:第 $ i+1 $ 行给出了编号为 $ i $ 的奶牛的工作计划,即 $ 3 $ 个正整数 $ T_1,T_2,S $ 。

输出格式

输出一个整数,表示约翰需要为牛棚清理工作支付的最少费用。如果清理工作不可能完成,那么输出 $ -1 $ 。

样例 #1

样例输入 #1

3 0 4
0 2 3
3 4 2
0 0 1

样例输出 #1

5

提示

约翰有 $ 3 $ 头牛,牛棚在第 $ 0 $ 秒到第 $ 4 $ 秒之间需要打扫。 约翰雇佣前两头牛清扫牛棚,可以只花 $ 5 $ 美元就完成一整天的清扫。

参考程序:

#include<bits/stdc++.h>
using namespace std;

const int maxn=1e4+5,maxt=86399+5;

int n,l,r;
long long tree[maxt<<2],f[maxn],minn=1e18;
struct node {
	int x,y;
    long long cost;
}a[maxn];

bool cmp(node a,node b) {
	return a.x<b.x;
}

void update(int spot,int L,int R,int k,long long ne) {
	if(L==R) {
		tree[spot]=min(ne,tree[spot]);
		return ;
	}
	int mid=(L+R)>>1,lson=spot<<1,rson=lson+1;
	if(k<=mid) update(lson,L,mid,k,ne);
	else update(rson,mid+1,R,k,ne);
	tree[spot]=min(tree[lson],tree[rson]);
	return ;
}

long long query(int spot,int L,int R,int x,int y) {
	if(y<L||R<x) return 1e17;
	if(x<=L&&R<=y) return tree[spot];
	int mid=(L+R)>>1,lson=spot<<1,rson=lson+1;
	return min(query(lson,L,mid,x,y),query(rson,mid+1,R,x,y));
}

int main() {
	for(int i=0;i<maxt<<2;i++)
		tree[i]=1e17;
	cin>>n>>l>>r;
	for(int i=1;i<=n;i++)
		cin>>a[i].x>>a[i].y>>a[i].cost;
	sort(a+1,a+1+n,cmp);
	if(a[1].x>l) {cout<<-1;return 0;}
	f[1]=a[1].cost;
	update(1,0,maxt,a[1].y,f[1]);
	if(a[1].y==r) minn=f[1];
	for(int i=2;i<=n;i++) {
		f[i]=a[i].cost+query(1,0,maxt,a[i].x-1,r);
		update(1,0,maxt,a[i].y,f[i]);
		if(a[i].y==r) minn=min(minn,f[i]);
	}
	if(minn>=1e17) cout<<-1;
	else cout<<minn;
	return 0;
}

重要例题

Intervals

题面翻译

给定 \(m\) 条规则形如 \((l_i,r_i,a_i)\),对于一个 01 串,其分数的定义是:对于第 \(i\) 条规则,若该串在 \([l_i,r_i]\) 中至少有一个 1,则该串的分数增加 \(a_i\)

你需要求出长度为 \(n\) 的 01 串中的最大分数。

\(1\le n,m\le 2\times 10^5\)\(|a_i|\le 10^9\)

样例 #1

样例输入 #1

5 3
1 3 10
2 4 -10
3 5 10

样例输出 #1

20

样例 #2

样例输入 #2

3 4
1 3 100
1 1 -10
2 2 -20
3 3 -30

样例输出 #2

90

样例 #3

样例输入 #3

1 1
1 1 -10

样例输出 #3

0

样例 #4

样例输入 #4

1 5
1 1 1000000000
1 1 1000000000
1 1 1000000000
1 1 1000000000
1 1 1000000000

样例输出 #4

5000000000

样例 #5

样例输入 #5

6 8
5 5 3
1 1 10
1 6 -8
3 6 5
3 4 9
5 5 -2
1 3 -6
4 6 -7

样例输出 #5

10

这题比较特别,对于区间的操作常用缩点的方法

而线段树直接维护dp值,一律在右端点统计贡献


参考程序

#include<bits/stdc++.h>
using namespace std;

const int maxn=2e5+5;

int n,m;
long long f[maxn],tree[maxn<<2],tag[maxn<<2];
struct node {
	int L,R;
	long long c;
}a[maxn];

bool cmp(node a,node b) {
	return a.R<b.R;
}

void pushdown(int spot,int L,int R) {
	if(!tag[spot]) return ;
	int mid=(L+R)>>1,lson=spot<<1,rson=lson+1;
	long long add=tag[spot];
	tag[spot]=0;
	tree[lson]+=add;
	tree[rson]+=add;
	tag[lson]+=add;
	tag[rson]+=add;
	return ;
}

void update(int spot,int L,int R,int x,int y,long long k) {
	if(y<L||R<x) return ;
	if(x<=L&&R<=y) {
		tree[spot]+=k;
		tag[spot]+=k;
		return ;
	}
	pushdown(spot,L,R);
	int mid=(L+R)>>1,lson=spot<<1,rson=lson+1;
	update(lson,L,mid,x,y,k);
	update(rson,mid+1,R,x,y,k);
	tree[spot]=max(tree[lson],tree[rson]);
	return ;
}

int main() {
	cin>>n>>m;
	for(int i=1;i<=m;i++)
		cin>>a[i].L>>a[i].R>>a[i].c;
	sort(a+1,a+1+m,cmp);
	for(int i=1,j=1;i<=n;i++) {
		update(1,1,n,i,i,max(0LL,tree[1]));
		while(a[j].R==i&&j<=m) {
			update(1,1,n,a[j].L,a[j].R,a[j].c);
			++j;
		}
	}
	cout<<max(0LL,tree[1]);
	return 0;
}

题解

【重要例题】[USACO12OPEN]Bookshelf G

题面翻译

当农夫约翰闲的没事干的时候,他喜欢坐下来看书。多年过去,他已经收集了 \(N\) 本书 \((1 \le N \le 100,000)\) , 他想造一个新的书架来装所有书。

每本书 \(i\) 都有宽度 \(W_i\) 和高度 \(H_i\) 。书需要按顺序添加到一组书架上;比如说,第一层架子应该包含书籍 \(1 ... k\) ,第二层架子应该以第 \(k + 1\) 本书开始,以下如此。每层架子的总宽度最大为 \(L(1 \le L \le 1,000,000,000)\) 。每层的高度等于该层上最高的书的高度,并且整个书架的高度是所有层的高度的总和,因为它们都垂直堆叠。

请帮助农夫约翰计算整个书架的最小可能高度。

\(N(1 \le N \le 100,000)\) 本书,每本书有一个宽度 \(W_i\) ,高度 \(H_i\)\((1 \le H_i \le 1,000,000; 1 \le W_i \le L)\)

现在有足够多的书架,书架宽度最多是 \(L (1 \le L \le 1,000,000,000)\) ,把书按顺序 \((\)先放 \(1\) ,再放 \(2.....)\) 放入书架。某个书架的高度是该书架中所放的最高的书的高度。

将所有书放入书架后,求所有书架的高度和的最小值。

题目描述

When Farmer John isn't milking cows, stacking haybales, lining up his cows, or building fences, he enjoys sitting down with a good book. Over the years, he has collected N books (1 <= N <= 100,000), and he wants to build a new set of bookshelves to hold them all.

Each book i has a width W(i) and height H(i). The books need to be added to a set of shelves in order; for example, the first shelf should contain books 1...k for some k, the second shelf should start with book k+1, and so on. Each shelf can have a total width of at most L (1 <= L <= 1,000,000,000). The height of a shelf is equal to the height of the tallest book on that shelf, and the height of the entire set of bookshelves is the sum of the heights of all the individual shelves, since they are all stacked vertically.

Please help FJ compute the minimum possible height for the entire set of bookshelves.

输入格式

第一行:两个数 \(N\)\(L\)

接下来 \(N\) 行每行两个数 \(H_i\)\(W_i\)

输出格式

一个数,书架高度的最小值。

样例 #1

样例输入 #1

5 10
5 7
9 2
8 5
13 2
3 8

样例输出 #1

21

参考程序:

#include<bits/stdc++.h>
#define int long long
using namespace std;

const long long INF=1e18;
const int maxn=1e5+5;

int n,L,h[maxn],root,pre[maxn];
long long f[maxn],s[maxn],w[maxn];
stack<int>st;
struct Segment_Tree {
	long long a,b,tag;
}tree[maxn<<2];
//注:此处第i位存的是f[i-1]

void build(int spot,int L,int R) {
	tree[spot].a=tree[spot].b=INF;
	tree[spot].tag=0;
	if(L==R) return ;
	int mid=(L+R)>>1,lson=spot<<1,rson=lson+1;
	build(lson,L,mid);
	build(rson,mid+1,R);
	return ;
}

void pushdown(int spot,int L,int R) {
	if(!tree[spot].tag) return ;
	int mid=(L+R)>>1,lson=spot<<1,rson=lson+1,add=tree[spot].tag;
	tree[spot].tag=0;
	tree[lson].b=tree[lson].a+add;
	tree[rson].b=tree[rson].a+add;
	tree[lson].tag=add;
	tree[rson].tag=add;
	return ;
}

void modify(int spot,int L,int R,int k,int ne) {
	if(L==R) {
		tree[spot].a=ne;
		return ;
	}
	int mid=(L+R)>>1,lson=spot<<1,rson=lson+1;
	if(k<=mid) modify(lson,L,mid,k,ne);
	else modify(rson,mid+1,R,k,ne);
	tree[spot].a=min(tree[lson].a,tree[rson].a);
	return ;
}

void update(int spot,int L,int R,int x,int y,int ne) {
	if(y<L||R<x) return ;
	if(x<=L&&R<=y) {
		tree[spot].b=tree[spot].a+ne;
		tree[spot].tag=ne;
		return ;
	}
	pushdown(spot,L,R);
	int mid=(L+R)>>1,lson=spot<<1,rson=lson+1;
	update(lson,L,mid,x,y,ne);
	update(rson,mid+1,R,x,y,ne);
	tree[spot].b=min(tree[lson].b,tree[rson].b);
	return ;
}

int query(int spot,int L,int R,int x,int y) {
	if(y<L||R<x) return INF;
	if(x<=L&&R<=y) return tree[spot].b;
	pushdown(spot,L,R);
	int mid=(L+R)>>1,lson=spot<<1,rson=lson+1;
	return min(query(lson,L,mid,x,y),query(rson,mid+1,R,x,y));
}

signed main() {
	cin>>n>>L;
	for(int i=1;i<=n;i++)
		cin>>h[i]>>w[i],s[i]=w[i]+s[i-1];
	st.push(1);
	for(int i=2;i<=n;i++) {
		while(!st.empty()&&h[i]>h[st.top()]) st.pop();
		if(!st.empty()) pre[i]=st.top();
		st.push(i);
	}
	build(1,1,n);
	for(int i=1;i<=n;i++) {//在每多考虑一个位置时,更新受影响的贡献值,并用另一个线段树维护,其余照旧。
		modify(1,1,n,i,f[i-1]);
		if(pre[i]+1<=i) update(1,1,n,pre[i]+1,i,h[i]);
		int l=lower_bound(s,s+i+1,s[i]-L)-s;
		f[i]=query(1,1,n,l+1,i);
	}
	cout<<f[n];
	return 0;
}

题解

posted @ 2023-04-29 06:58  ForBiggerWorld  阅读(251)  评论(0)    收藏  举报