P3084

我草,刚在 typora 写完发现洛谷发题解入口被关了……心态崩了………………

没错我就是那位被标签「差分约束」骗进来的人。

Portal

这题确实可以差分约束,做法也很显然,不过那样是 (mathrm O(n(n+m))) 的 SPFA,普通写可以得 60pts。不过看到 zqy 用某些邪教优化 SPFA 过了这道题,我以后有时间学一下!

这题实际上是个不错的 DP。

我们考虑对于一个选的牛的序列,它合法的充要条件是什么。

很简单,就是每个区间内恰好有一头牛。那么如何简洁的体现到序列上呢?注意到恰好有一头牛意味着不能有 (0) 头或大于 (1) 头。(0) 头的话,那就是一个坐落在序列中两相邻牛之间的一个区间;大于 (1) 头的话,就是当且仅当包含了序列中连续两头牛。然后这两个条件不能有任何一个序列满足,就这样很好的贴合序列了。

下面考虑对这个序列 DP。设 (dp_i) 表示考虑前 (i) 头牛中,若 (i) 选,则满足条件的最大的选的牛数。我们考虑将上面那两个条件融入到任意一个相邻对里面去,也就是说每次 DP 的时候只需要考虑 (i) 与决策 (j) 满足条件。

条件一是说不能有 (j<l_k,r_k<i)。这里的 ([r_k<i]) 固定了,唯一能做的就是调整 (j)。也就是要满足 (forall r_k<i,jgeq l_k),即 (jgeqmaxlimits_{r_k<i}{l_k})

条件二是说不能有 (l_kleq j,ileq r_k),同理推出等价于 (j<minlimits_{r_kgeq i}{l_k})

这样我们就得出了 (j) 的上下界,除此之外还有个隐含限制 (jin[0,i))。加上去之后容易得到 (i) 的决策区间,我们要找的是区间里面的 DP 值最大值。

考虑如何实现。上下界显然可以按 (r) 排序,然后分别从前往后、从后往前扫一遍即可求出。然后不难发现下界显然是单调不降的,上界反过来看是单调不升的,那正过来也就是单调不降的。咦,那恰好可以单调队列优化。于是复杂度线性。

但还有一个线性对数的地方,那就是排序……不过注意到值域线性,所以可以对 (r) 桶排来实现严格的线性复杂度。然后卡了卡常,用 c++17 虐了最优解 1ms,成为了最优解。

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
char _buf[1<<23],*_st,*_ed;
#define getchar() (_st==_ed&&(_ed=(_st=_buf)+fread(_buf,1,1<<22,stdin),_st==_ed)?EOF:*_st++)
void read(int &x){
	x=0;char c=getchar();
	while(!isdigit(c))c=getchar();
	while(isdigit(c))x=(x<<1)+(x<<3)+(c^48),c=getchar();
}
const int N=200000,M=100000;
int n,m;
int rgl[M+1],rgr[M+1];
struct addedge{
	int sz,val[M+1],nxt[M+1],head[N+1];
	void init(){sz=0;memset(head,0,sizeof(head));}
	void ae(int x,int y){
		++sz,val[sz]=y,nxt[sz]=head[x],head[x]=sz;
	}
}lft;
int l[N+2],r[N+2];
int dp[N+2];
int q[N+2],head,tail;
int main(){
	read(n),read(m);
	lft.init();
	for(int i=1;i<=m;++i)read(rgl[i]),read(rgr[i]),lft.ae(rgr[i],rgl[i]);
	int now=0;
	for(int i=1;i<=n;++i)for(int j=lft.head[i];j;j=lft.nxt[j])rgl[++now]=lft.val[j],rgr[now]=i;
	for(int i=1,j=0,mx=0;i<=n+1;++i){
		while(j+1<=m&&rgr[j+1]<i)mx=max(mx,rgl[++j]);
		l[i]=mx;
	}
	for(int i=n+1,j=m+1,mn=inf;i;--i){
		while(j-1&&rgr[j-1]>=i)mn=min(mn,rgl[--j]);
		r[i]=min(i-1,mn-1);
	}
//	for(int i=1;i<=n+1;i++)cout<<l[i]<<" "<<r[i]<<"!
";
	q[tail++]=0;
	for(int i=1;i<=n+1;++i){
		for(int j=r[i-1]+1;j<=r[i];++j){
			while(head<tail&&dp[q[tail-1]]<=dp[j])--tail;
			q[tail++]=j;
		}
		while(head<tail&&q[head]<l[i])++head;
		dp[i]=head<tail?dp[q[head]]+1:-inf;
	}
	if(dp[n+1]<0)puts("-1");
	else cout<<dp[n+1]-1;
	return 0;
}
珍爱生命,远离抄袭!
原文地址:https://www.cnblogs.com/ycx-akioi/p/p3084.html