[POJ 3245] Sequence Partitioning [动态规划+线段树]

27 篇文章 0 订阅
26 篇文章 0 订阅

已知一个序列,每个元素都是一个有序数对(A,B)。现在让你把这个序列分块,要求对于不同的块,前边的元素的B值必须大于后边的元素的A值,且每块的A值的最大值的和要小于等于Limit。问在这样的条件下,每块的B的和的最大值最小是多少?

首先我们发现,有些元素是必须处于一块儿的。当且仅当这个元素的前边的所有的B值大于这个元素的后边元素的所有的A值时,我们才可以把这个元素作为一块的最后一个元素。

对原序列进行处理,然后把必须处于一块儿的元素缩成一个元素,形成了一个新序列。然后我们就可以直接在新序列中求解,不用在意第一个约束条件了。

接着我们二分答案,然后仅需判断能否找到一组方案,每块的B的和都不超过ans,每块的a的最大值的和不超过Limit。

然后我们用dp判断是否有可行解,定义状态dp[i]表示前i个元素分块,每块的B不超过ans的前提下,每块的a的最大值的和最小是多少。

状态转移为:dp[i]=min{dp[j]+max{A_(j+1),A_(j+2),...,Ai}

然后用线段树维护这个dp数组,线段树中位置j的元素表示dp[j]+max{A_(j+1),A_(j+2),...,Ai的值,每次只需进行区间加法和区间最小值查询即可。

#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int MAXINT=~0u>>1;

struct Node {
	Node *ls,*rs;
	long long v,add;
	void down() {
		if (add==0) return;
		if (ls) {
			ls->v+=add;
			ls->add+=add;
		}
		if (rs) {
			rs->v+=add;
			rs->add+=add;
		}
		add=0;
	}
	void repair() {
		v=min(ls->v,rs->v);
	}
};
Node segNode[100002],*bp,*root;
int left,right;
Node *makeTree(int l,int r) {
	Node *ans=bp++;
	if (l==r) {
		ans->v=ans->add=0;
		ans->ls=ans->rs=NULL;
	} else {
		int t=(l+r)/2;
		ans->ls=makeTree(l,t);
		ans->rs=makeTree(t+1,r);
		ans->v=ans->add=0;
	}
	return ans;
}
void clear(int l,int r) {
	bp=segNode;left=l;right=r;
	root=makeTree(l,r);
}
void set(int ll,int rr,long long x,Node *from=root,int l=left,int r=right) {
	if (l==ll&&r==rr) {
		from->add+=x;
		from->v+=x;
	} else {
		int t=(l+r)/2;
		from->down();
		if (rr<=t) set(ll,rr,x,from->ls,l,t);
		else if (ll>t) set(ll,rr,x,from->rs,t+1,r);
		else {
			set(ll,t,x,from->ls,l,t);
			set(t+1,rr,x,from->rs,t+1,r);
		}
		from->repair();
	}
}
long long get(int ll,int rr,Node *from=root,int l=left,int r=right) {
	if (l==ll&&r==rr) return from->v;
	int t=(l+r)/2;
	from->down();
	if (rr<=t) return get(ll,rr,from->ls,l,t);
	else if (ll>t) return get(ll,rr,from->rs,t+1,r);
	else return min(get(ll,t,from->ls,l,t),get(t+1,rr,from->rs,t+1,r));
}

int n,newn,limit;
int a[50000];
int b[50000];
int maxa[50001];
int minb[50000];
bool isR[50000];
int newa[50001];
int newb[50001];
int stka[50000];
int stkl[50000];

bool canGao(int ans,int a[],int b[],int n) {
	int sumb=0,left=1,p=0;
	long long dpi=0;
	clear(0,n);
	for (int i=1;i<=n;i++) {
		sumb+=b[i];
		while (sumb>ans) sumb-=b[left++];
		if (left>i) return false;
		set(i-1,i-1,(long long)dpi+a[i]);
		stkl[p]=i-1;
		while (p>0&&a[i]>=stka[p-1]) {
			set(stkl[p-1],stkl[p]-1,(long long)a[i]-stka[p-1]);
			p--;
		}
		stka[p++]=a[i];
		dpi=get(left-1,i-1);
		if (dpi>limit) return false;
	}
	return true;
}

int main() {
	int i;
	scanf("%d%d",&n,&limit);
	for (i=0;i<n;i++) scanf("%d%d",&a[i],&b[i]);
	minb[0]=b[0];
	for (i=1;i<n;i++) minb[i]=min(b[i],minb[i-1]);
	maxa[n]=-1;
	for (i=n-1;i>=0;i--) maxa[i]=max(a[i],maxa[i+1]);
	for (i=0;i<n;i++) 
		if (minb[i]>maxa[i+1]) isR[i]=true;
		else isR[i]=false;
	memset(newa,-1,sizeof(newa));
	memset(newb,0,sizeof(newb));
	newn=1;
	for (i=0;i<n;i++) {
		newa[newn]=max(newa[newn],a[i]);
		newb[newn]+=b[i];
		if (isR[i]) newn++;
	}
	newn--;
	newa[0]=0; newb[0]=0;
	int l=0,r=MAXINT;
	while (l<r) {
		int t=((long long)l+r)/2;
		if (canGao(t,newa,newb,newn)) r=t;
		else l=t+1;
	}
	printf("%d\n",l);
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值