[ARC076F]Exhausted?

题意:有$n$个人和$m$个椅子,椅子排成一排,坐标为$1\cdots m$,每个人都想坐在一个椅子上,第$i$个人想坐的椅子的坐标要$\leq Li$或$\geq R_i$,椅子不够可以在任意实数位置加椅子,问最少加多少椅子

其实是问不加椅子最多坐多少个人

先考虑一个网络流做法:对每个椅子建一个点,向$T$连$1$,对每个人建一个点,$S$向它连$1$,它向对应椅子连边

这个做法可以前后缀优化建图,但还是太慢,下面我们从最小割的角度考虑这个问题

特判掉$S,T$不连通的情况,最后割掉的一定是一些$S$的出边和$T$的入边,并且这些$T$的入边构成前缀和后缀,枚举前缀$[1,l]$和后缀$[r,m]$,此时最小割为$l+m-r+1+\sum\limits_{i=1}^n[L_i\gt l\text{ or }R_i<r]$,因为不被这两个前后缀包含的那些人必须在$S$的出边被割掉

现在要求这个式子的最小值,那么将人按$R_i$排序后扫一遍用线段树求答案即可

求最小值之前应先把答案设为$\min(n,m)$,这对应着只割$S$或只割$T$的情况

#include<stdio.h>
#include<algorithm>
using namespace std;
const int inf=2147483647;
struct pr{
	int l,r;
}p[200010];
bool operator<(pr a,pr b){return a.r<b.r;}
int mn[800010],d[800010];
void pushup(int x){
	mn[x]=min(mn[x<<1],mn[x<<1|1]);
}
void build(int l,int r,int x){
	mn[x]=l;
	if(l==r)return;
	int mid=(l+r)>>1;
	build(l,mid,x<<1);
	build(mid+1,r,x<<1|1);
}
void ad(int x,int v){
	mn[x]+=v;
	d[x]+=v;
}
void pushdown(int x){
	if(d[x]){
		ad(x<<1,d[x]);
		ad(x<<1|1,d[x]);
		d[x]=0;
	}
}
void modify(int L,int R,int v,int l,int r,int x){
	if(L<=l&&r<=R)return ad(x,v);
	int mid=(l+r)>>1;
	pushdown(x);
	if(L<=mid)modify(L,R,v,l,mid,x<<1);
	if(mid<R)modify(L,R,v,mid+1,r,x<<1|1);
	pushup(x);
}
int query(int L,int R,int l,int r,int x){
	if(L<=l&&r<=R)return mn[x];
	int mid=(l+r)>>1,res=inf;
	pushdown(x);
	if(L<=mid)res=min(res,query(L,R,l,mid,x<<1));
	if(mid<R)res=min(res,query(L,R,mid+1,r,x<<1|1));
	return res;
}
int main(){
	int n,m,i,j,s;
	bool flag;
	scanf("%d%d",&n,&m);
	flag=1;
	for(i=1;i<=n;i++){
		scanf("%d%d",&p[i].l,&p[i].r);
		if(p[i].l>0||p[i].r<m)flag=0;
	}
	if(flag){
		printf("%d",n);
		return 0;
	}
	sort(p+1,p+n+1);
	build(1,m,1);
	for(i=1;i<=n;i++){
		if(p[i].l>1)modify(1,p[i].l-1,1,1,m,1);
	}
	s=min(n,m);
	for(i=j=1;i<=m;i++){
		while(j<=n&&p[j].r<i){
			if(p[j].l>1)modify(1,p[j].l-1,-1,1,m,1);
			j++;
		}
		s=min(s,m-i+j+query(1,i,1,m,1));
	}
	printf("%d",n-s);
}

猜你喜欢

转载自www.cnblogs.com/jefflyy/p/9495260.html