[JOISC2017 D2 T1]Arranging Tickets

Description

有n个车站按顺时针排成一圈,编号为1…n
有n种车票,第i种车票可以从i到i+1,也可以从i+1到i
有m类旅客,第i类旅客有ci人,要从ai到bi
你需要给每一个旅客确定方案,最小化所有种类的车票中所需数目的最大值
n<=2e5,m<=1e5

Solution

听说是17年最难的题
题解好长啊
首先我们破环为链,转化一下题意:
数轴上有n个整点,m种区间,第i种区间覆盖了[ai,bi)的整点,有ci个
你可以选择一些区间翻转为[1,ai)∪[bi,n],最小化被被覆盖最多的点被覆盖的次数
然后考虑二分答案,但是怎么判定呢?
结论:若两个区间[l,r),[x,y)没有交集,那么这两个区间不会同时翻转
显然
那么我们的到了一个性质,所有翻转的区间一定有至少一个公共点t
考虑一个玄妙的贪心:
从1往t-1扫,维护一个大根堆,每扫到一个区间的左端点,并且其右端点>r,就把它丢到大根堆里面去.
如果当前点i被覆盖次数>ans那么我们就选择堆顶的区间翻转
因为后面的翻转操作也会对当前点产生影响所以我们需要预先知道总的翻转次数cnt
设当前翻转的区间个数为x,前面被覆盖的次数为now,剩下的翻转次数为cnt,需要满足now-x+cnt-x>ans
取最大的x翻转即可
最后检验一遍后面的点是否合法
这个贪心是对的是因为我们在保证了1~t合法的情况下最小化了t+1 ~n的覆盖次数
然后我们得到了一个O(n^2 c log ^2 n)的优秀做法
接下来我们来证明两个定理:
设bi表示翻转之后i的覆盖次数,所有翻转区间的交集为[l,r),我们取区间中最大的b作为bt
定理1:bt=max(bi)或bt=max(bi)-1
考虑反证,假设当前最大值不在[l,r)中,我们选择被反转的右端点最左和左端点最右的两个区间取消翻转
显然外面的最大值不会增加,所有位置依然合法,且bt增加了
所以bt一定会成为最大值
由于max(bi)=ans,所以我们只需要检验cnt=ans-a[t]或cnt=ans-a[t]+1即可
好我们把c优化掉了
定理2:最优的t满足at=max(ai)
也是考虑反证,设交集为[l,r),也就是说存在i∉[l,r),使得a[i]>=at+1
那么至少存在一个翻转区间不包含i
也就是说bi-ai>=bt-at+1
结合之前的有bi>=bt+2和定理1矛盾
但是如果有很多t满足at=max(ai)呢?
定理3:最优的t满足at=max(ai)且t尽量小或尽量大
同样考虑反证,设al=ar=at(l<t<r)
我们只需要证明所有被翻转的区间都覆盖了al或ar
引理1:不存在翻转区间[x,y)满足l<x<y<=r
继续反证,假定翻转之后的b为c,那么有ct=bt+1,cl=bl-1
同时我们仍然有ct-at<=cl-al,从而有cl>=ct
然后就有bl>=bt+2,矛盾
r也是同理
引理2:假设有两个翻转区间[x1,y1),[x2,y2),满足y1<=r且x2>l我们同时取消翻转不会更劣
注意无论何时bi-ai的值越接近t越小,所以我们有max(bl…br)=max(bl,br)
那么我们就有max(bi)=max(bj),(j<=l或j>=r)
这一部分的b在翻转后显然不会变大
综上,我们只需要取一个t=l,t=r,cnt=ans-a[l],cnt=ans-a[l]+1,分别做贪心检验即可
复杂度O(n log^2 n)

Code

#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;

typedef long long ll;

int read() {
	char ch;
	for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
	int x=ch-'0';
	for(ch=getchar();ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
	return x;
}

const int N=2e5+5;

struct node{int l,r;ll c;}p[N];

int n,m;
ll a[N],tag[N];

vector<pair<int,ll> > vec[N];
priority_queue<pair<int,ll> > q;
#define mp(a,b) make_pair(a,b)
#define pb(a) push_back(a)

bool judge(ll ans,int t,ll cnt) {
	if (ans<cnt) return 0;
	fo(i,1,n) tag[i]=0,vec[i].clear();
	fo(i,1,m) if (p[i].l<=t&&p[i].r>t) vec[p[i].l].pb(mp(p[i].r,p[i].c));
	while (!q.empty()) q.pop();
	int j=0;ll now=0;
	fo(i,1,t) {
		for(int j=0;j<vec[i].size();j++) q.push(vec[i][j]);
		while (a[i]-now+cnt>ans) {
			if (q.empty()) return 0;
			pair<int,ll> tmp=q.top();q.pop();
			ll ret=min(tmp.second,(a[i]-now+cnt-ans+1)/2);
			now+=ret;cnt-=ret;
			tag[tmp.first]+=ret*2;
			if (tmp.second!=ret) q.push(mp(tmp.first,tmp.second-ret));
		}
	}
	tag[t+1]-=now;
	fo(i,t+1,n) {
		tag[i]+=tag[i-1];
		if (tag[i]+a[i]>ans) return 0;
	}
	return 1;
}

bool check(ll ans,int t) {return judge(ans,t,a[t]-ans)||judge(ans,t,a[t]-ans+1);}

int main() {
	n=read();m=read();
	ll l=0,r=0;
	fo(i,1,m) {
		p[i].l=read();p[i].r=read();p[i].c=read();
		if (p[i].l>p[i].r) swap(p[i].l,p[i].r);
		a[p[i].l]+=p[i].c;a[p[i].r]-=p[i].c;r+=p[i].c;
	}
	fo(i,1,n) a[i]+=a[i-1];
	int tl=0,tr=0;
	fo(i,1,n) {
		if (a[i]>a[tl]) tl=i;
		if (a[i]>=a[tr]) tr=i;
	}
	while (l<r) {
		ll mid=l+r>>1;
		if (check(mid,tl)||check(mid,tr)) r=mid;
		else l=mid+1;
	}
	printf("%lld\n",l);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值