Codeforces #239D: Maximum Waterfall 题解

解决最大瀑布问题的关键在于构建有向无环图(DAG),通过线段树或端点排序策略进行动态规划。线段树方法中,先离散化端点,按高度排序,通过查询区间最大值确定相邻木板关系。另一种方法是利用set,左端点插入,右端点删除,避免长木板遮挡短木板的情况。最后在DAG上运行dp求解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一个重要的结论是,如果在可以流的木板之间连一条有向边的话,边的条数不会很多,而且这是一个DAG

所以如果已经处理出了图,就在DAG上跑dp就可以了

关键是怎么建图

一个好的思路是按照端点次序进行访问,左端点插入set,右端点删除set,set内按照高度排序,这样在set里进行lower_bound就能找出被当前木板隔开的两块木板,把这两块木板之间的边删掉,并分别与中间木板连边

要注意设置INF的时候要设成2e9而不是1e9

说的有点抽象,看代码吧

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#include <utility>
#include <map>
#include <stack>
#include <set>
#include <vector>
#include <queue>
#include <deque>
#define x first
#define y second
#define mp make_pair
#define pb push_back
#define LL long long
#define Pair pair<int,int>
#define LOWBIT(x) x & (-x)
using namespace std;

const int MOD=1e9+7;
const int INF=1e9;
const int magic=348;

struct node
{
	int h,l,r;
	int num;
}a[100048];

Pair e[200048];int top=0;
int d[200048];queue<int> q;
int seq[200048],stop=0;
int dp[200048];
int n,t;
set<Pair> m,s;
set<Pair>::iterator iter;

int head[200048],nxt[400048],to[400048],f[400048],tot=1;
inline void addedge(int s,int t,int len)
{
	to[++tot]=t;nxt[tot]=head[s];head[s]=tot;f[tot]=len;
	d[t]++;
}

int main ()
{
	int i,j,up,down,x,y;
	scanf("%d%d",&n,&t);
	for (i=1;i<=n;i++)
	{
		scanf("%d%d%d",&a[i].h,&a[i].l,&a[i].r);
		a[i].num=i;
	}
	a[n+1].h=0;a[n+1].l=-INF;a[n+1].r=INF;
	a[n+2].h=t;a[n+2].l=-INF;a[n+2].r=INF;
	for (i=1;i<=n;i++)
	{
		e[++top].x=a[i].l;
		e[top].y=i;
		e[++top].x=a[i].r;
		e[top].y=-i;
	}
	sort(e+1,e+top+1);
	s.insert(mp(0,n+1));
	s.insert(mp(t,n+2));
	m.insert(mp(n+1,n+2));
	for (i=1;i<=top;i++)
	{
		if (e[i].y<0)
		{
			s.erase(mp(a[-e[i].y].h,-e[i].y));
			continue;
		}
		iter=s.lower_bound(mp(a[e[i].y].h,e[i].y));
		up=iter->y;
		down=(--iter)->y;
		m.erase(mp(down,up));
		m.insert(mp(down,e[i].y));
		m.insert(mp(e[i].y,up));
		s.insert(mp(a[e[i].y].h,e[i].y));
	}
	int l1,l2,r1,r2,flow;
	for (iter=m.begin();iter!=m.end();iter++)
	{
		l1=a[iter->x].l;l2=a[iter->y].l;
		r1=a[iter->x].r;r2=a[iter->y].r;
		flow=min(r1,r2)-max(l1,l2);
		addedge(iter->x,iter->y,flow);
	}
	for (i=1;i<=n+2;i++) if (!d[i]) q.push(i);
	while (!q.empty())
	{
		x=q.front();q.pop();
		seq[++stop]=x;
		for (i=head[x];i;i=nxt[i])
		{
			y=to[i];
			d[y]--;
			if (!d[y]) q.push(y);
		}
	}
	dp[n+1]=2*INF;
	for (i=1;i<=stop;i++)
	{
		x=seq[i];
		for (j=head[x];j;j=nxt[j])
		{
			y=to[j];
			dp[y]=max(dp[y],min(f[j],dp[x]));
		}
	}
	printf("%d\n",dp[n+2]);
	return 0;
}

这题还有一种线段树的做法

先将木板的左右端点离散化,再将所有的木板按照高度排序,每处理一块木板后就将这个区间染色,染成木板编号

对于一块木板,在他的左右端点处找最大值,即可找到离它最近的木板,再查询这两块木板重叠处的最大值是否是较小木板的编号,以确定两块木板之间是否有其他木板

要考虑到如果一块很长的木板在上方覆盖住了下方的一块短木板,那么长木板的左右端点都将扫不到短木板,所以要将木板数组reverse以后再进行一轮相同的操作

用线段树连好边以后就在DAG上跑dp就可以了,与方法一无异

这种方法常数略大,而且比较容易出错(惨)

#include <cstdio>
#include <iostream>
#include <cstring>
#include <string>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#include <utility>
#include <map>
#include <stack>
#include <set>
#include <vector>
#include <queue>
#include <deque>
#define x first
#define y second
#define mp make_pair
#define pb push_back
#define LL long long
#define Pair pair<int,int>
#define LOWBIT(x) x & (-x)
using namespace std;

const int MOD=1e9+7;
const int INF=1e9;
const int magic=348;

int head[200048],nxt[400048],to[400048],f[400048],tot=1;
inline void addedge(int s,int t,int len)
{
	to[++tot]=t;nxt[tot]=head[s];head[s]=tot;f[tot]=len;
}

struct node
{
	int left,right;
	int maxn;
	bool f;
}tree[600048];

struct node1
{
	int h,l,r;
	int ll,rr;
}a[200048];

int n,t;
set<pair<int,pair<int,bool> > > s;
set<pair<int,pair<int,bool> > >::iterator iter;
int dp[400048];

bool cmp(node1 x,node1 y)
{
	return x.h<y.h;
}

void build(int cur,int left,int right)
{
	tree[cur].left=left;tree[cur].right=right;tree[cur].maxn=-1;
	tree[cur].f=false;
	if (left+1<right)
	{
		int mid=(left+right)>>1;
		build(cur*2,left,mid);
		build(cur*2+1,mid,right);
	}
}

int query(int cur,int left,int right)
{
	if (tree[cur].left+1==tree[cur].right) return tree[cur].maxn;
	if (tree[cur].f) return tree[cur].maxn;
	int res1=-1,res2=-1,mid=(tree[cur].left+tree[cur].right)>>1;
	if (left<mid) res1=query(cur*2,left,right);
	if (mid<right) res2=query(cur*2+1,left,right);
	return max(res1,res2);
}

void update(int cur,int left,int right,int val)
{
	if (left<=tree[cur].left && tree[cur].right<=right)
	{
		tree[cur].f=true;
		tree[cur].maxn=val;
		return;
	}
	if (tree[cur].f)
	{
		tree[cur].f=false;
		tree[cur*2].f=true;tree[cur*2].maxn=tree[cur].maxn;
		tree[cur*2+1].f=true;tree[cur*2+1].maxn=tree[cur].maxn;
	}
	int mid=(tree[cur].left+tree[cur].right)>>1;
	if (left<mid) update(cur*2,left,right,val);
	if (mid<right) update(cur*2+1,left,right,val);
}

bool check(int x,int y)
{
	int left,right;
	left=max(a[x].ll,a[y].ll);right=min(a[x].rr,a[y].rr);
	if (query(1,left,right)==x) return true; else return false;
}

int main ()
{
	int i,j,xx,y,ff,x;
	scanf("%d%d",&n,&t);
	for (i=1;i<=n;i++) scanf("%d%d%d",&a[i].h,&a[i].l,&a[i].r);
	a[n+1].h=0;a[n+1].l=-INF;a[n+1].r=INF;
	a[n+2].h=t;a[n+2].l=-INF;a[n+2].r=INF;
	n+=2;
	sort(a+1,a+n+1,cmp);
	for (i=1;i<=n;i++)
	{
		s.insert(mp(a[i].l,mp(i,false)));
		s.insert(mp(a[i].r,mp(i,true)));
	}
	xx=1;
	iter=s.begin();
	int last=iter->x;
	if (!iter->y.y) a[iter->y.x].ll=1; else a[iter->y.x].rr=1;
	iter++;
	for (;iter!=s.end();iter++)
	{
		if (!iter->y.y)
			a[iter->y.x].ll=iter->x==last?xx:++xx; 
		else 
			a[iter->y.x].rr=iter->x==last?xx:++xx;
		last=iter->x;
	}
	build(1,1,xx+10);
	for (i=1;i<=n;i++)
	{
		y=i;
		x=query(1,a[i].ll,a[i].ll+1);
		if (check(x,y))
		{
			ff=min(a[x].r,a[y].r)-max(a[x].l,a[y].l);
			addedge(x,y,ff);
		}
		x=query(1,a[i].rr-1,a[i].rr);
		if (check(x,y))
		{
			ff=min(a[x].r,a[y].r)-max(a[x].l,a[y].l);
			addedge(x,y,ff);
		}
		update(1,a[i].ll,a[i].rr,i);
	}
	reverse(a+1,a+n+1);
	for (i=1;i<=n;i++)
	{
		y=i;
		x=query(1,a[i].ll,a[i].ll+1);
		if (check(x,y))
		{
			ff=min(a[x].r,a[y].r)-max(a[x].l,a[y].l);
			addedge(n+1-y,n+1-x,ff);
		}
		x=query(1,a[i].rr-1,a[i].rr);
		if (check(x,y))
		{
			ff=min(a[x].r,a[y].r)-max(a[x].l,a[y].l);
			addedge(n+1-y,n+1-x,ff);
		}
		update(1,a[i].ll,a[i].rr,i);
	}
	reverse(a+1,a+n+1);
	dp[1]=2*INF;
	for (i=1;i<=n-1;i++)
		for (j=head[i];j;j=nxt[j])
		{
			y=to[j];
			dp[y]=max(dp[y],min(dp[i],f[j]));
		}
	printf("%d\n",dp[n]);
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值