Loading

CF1305H Kuroni the Private Tutor

假设如果已经知道得分分布为 \(s_1\ge s_2\ge \cdots \ge s_m\),考虑如何判断是否存在满足条件的情况。

考虑网络流:

  1. 对于每一道题 \(i\) ,从源点 \(S\)\(i\) 连接一条下界 \(l_i\) 上界为 \(r_i\) 的边。
  2. 对于每一名学生 \(j\),从 \(j\) 向汇点 \(T\) 容量为 \(s_j\) 的边。
  3. 对于任意一道题 \(i\) 和任意一名学生 \(j\) ,连一条 从 \(i\)\(j\) 容量为 \(1\) 的边。

\(\{s_m\}\) 合法当且仅当这张图存在大小为 \(\sum s_j\) 的流。

然后再把这张图转成一般的(没有下界的)网络流就行了。

具体地,注意到每个试题 \(i\) 都有 \(l_i\) 的流量逆差,因此再建一个超级源点 \(S'\) 向它提供大小为 \(l_i\) 的流。

注意到这张图并不会存在大小超过 \(\sum s_j\) 的流。

于是,若这张图存在大小为 \(f\) 的流,当且仅当存在大小 \(\ge f\) 的流。

根据最大流最小割定理,上述条件等价于这张图的每一个割集的容量和 \(\ge f\)

然后考虑上界,将 \(S\)\(S'\) 看作整体,令割集为 \((A\mid B)\) ,其中 \(S,S'\in S,T\in B\),令 \(P_A,P_B\) 表示题目点集分别与 \(A,B\) 的交集,\(Q_A,Q_B\) 表示学生点集与 \(A,B\) 的交集。

于是割集由如下三种边构成:

  1. \(S\to P_B\) 长度和为 \(\sum\limits _{i\in P_B} r_i\)
  2. \(P_A\to Q_B\) 长度和为 \(|P_A|\times |Q_B|\)
  3. \(Q_A\to T\) 长度和为 \(\sum\limits_{j\in Q_A} s_j\)

因此有:

\[\sum_{i\in P_B}r_i+|P_A|\times |Q_B|+\sum_{j\in Q_A} s_j\ge \sum s_j \]

稍作变形

\[\sum r_i -\sum_{i\in P_A} r_i+|P_A|\times |Q_B|\ge \sum_{j\in Q_B} s_j \]

显然在固定集合大小时,一定是取最大的 \(|P_B|\)\(r_i\)\(|Q_B|\)\(s_j\)

于是假设 \(r_i\) 单调递减,这些不等式可以简化为对于 \(\forall x\in[0,n],y\in [0,m]\),有:

\[xy+\sum_{x+1} r_i+\sum_{y+1}s_j\ge \sum s_j \]

然后来考虑下界,即 \(S'\) 的出边需要全部满流 \((S\in B,S'\in A)\)

可以得到:

\[\sum_{i\in P_B}l_i+|P_A|\times |Q_B|+\sum_{j\in Q_A} s_j\ge \sum l_i \]

同上,假设 \(l_i\) 单调递减,得到:

\[xy-\sum_{i=1}^{x}l_i+\sum_{y+1}^m s_j\ge 0 \]

其实这里还有一种情况是假设 \(S\in A,S'\in B\)

类似上面可以得到:

\[xy+\sum_{i=1}^xl_i+\sum_{x+1}^n r_i +\sum _{y+1}^m s_j\ge\sum s_j \]

但因为 \(\sum\limits _{i=1}^xl_i \ge 0\),所以当上面 \(xy+\sum_{x+1} r_i+\sum_{y+1}s_j\ge \sum s_j\) 成立时这个式子一定成立。

所以条件转化为了:

\[xy+\sum_{y+1}^m s_j\ge \max\left(\sum s_j-\sum_{x+1} r_i,\sum_{i=1}^x l_i \right) \]

然后就变成了对这个式子快速判定。

考虑枚举 \(x\),可以发现不等式右边可以通过预处理快速计算且与 \(y\) 无关,所以只需要找到左边最大值即可。

由于 \(s_j\) 单调低减,所以 \(\sum\limits _{y+1}^m s_j\) 是关于 \(y\) 的下凸数列(差分递增)。

\(xy+\sum\limits _{y+1}^m s_j\) 相当于求两个向量 \(\left(x,1\right)\times \left(y,\sum\limits_{y+1}^m s_j \right)\) 的最大值。

取到最大值的点就是斜率为 \(-x\) 的下切线与凸包的交点,所以这个点的横坐标 \(y\) 是单调递减的,可以通过双指针维护。

时间复杂度为 \(\mathcal O(n+m)\)

考虑二分并列第一的人数 \(w\) 是否可行。

首先可以把给定的额外信息中已经有前 \(w\) 名中两人得分不同的情况丢掉。

然后分类讨论前 \(w\) 名是否有额外信息:

  • 若存在额外信息,则说明现在已经知道前 \(w\) 名的分数了。
    考虑分配得分使 \(s_i\) 尽量去满足上面那个东西。

    由于右边和 \(s_i\) 没有关系,因此再固定 \(y\) 的时候,会尽量让 \(s\) 的后缀和尽量大。

    从调整的角度(每次找一个学生将 \(s_i\) 加1) 分析,由于 \(\sum s\) 是固定的,所以每次会尽量选择可以加的学生中得分最低的那个学生(要时刻满足 \(s_i\ge s_{i+1}\))。

    于是先让每个学生得到最低分(即前 \(w\) 名分数固定,剩下的学生的得分为他自己或后面第一个额外信息),然后不断加分。(当然如果这里如果总分已经 \(>t\) 则已经无解)

    具体地,可以发现每次加分都是满足 \(s_{i-1}\neq s_i\) 的最大的 \(i\)。于是,只要从右往左枚举每个“坑”,简单判断一下当前剩下的分数能不能填平它,如果能就直接填平,否则就直接算一下能填到多高就是了。

    如果发现某个时刻无法加分(每个人的分数都等于他自己或前面第一个额外信息)且总分没有到达 \(t\),那么也是无解。

    否则拿上面那个判定方式把得到的 \(\{s_m\}\) 跑一遍看行不行就好了。

  • 若不存在额外限制,则考虑类似上面先让每个学生得到最低分,然后开始“填坑”。

    但是此时可能会出现填到前面的时候没有分数了导致不满足前 \(w\) 名得分相同的情况。

    此时假设第一名的学生得分为 \(x\),则固定前 \(w\) 名的学生得分均为 \(x\),然后再做一遍就可以了。

二分出 \(w\) 后,考虑这 \(w\) 名得分的最大值。

若前 \(w\) 名中存在额外信息,那么显然最大值为额外信息所给的分数。

否则就继续二分这个分值 \(x\) 是否可行,具体与上面同理。

时间复杂度为 \(\mathcal O(n\log n)\)

#include<bits/stdc++.h>
#define int long long
#define R(i,a,b) for(int i=(a),i##E=(b);i<=i##E;i++)
#define L(i,a,b) for(int i=(b),i##E=(a);i>=i##E;i--)
using namespace std;
int n,m,q,st;
int l[111111],r[111111],sl[111111],sr[111111];
int lim[111111];
int ans[111111];
bool vis[111111];
int ans1;
inline void ns()
{
	cout<<"-1 -1"<<endl;
	exit(0);
}
int divs(int l,int r,int lm,int sum)
{
	int len=r-l+1,t=ans[l];
	if(sum>lm*len) sum=lm*len;
	int qt=sum/len,rem=sum%len;
	fill(ans+l,ans+l+rem,t+qt+1);
	fill(ans+l+rem,ans+r+1,t+qt);
	return sum;
}
int check(int len,int val)
{
	int mx=max(*max_element(lim,lim+m),0ll);
	int cnl=count(lim,lim+len,-1),cmx=count(lim,lim+len,mx);
	if(cnl+cmx!=len||(cmx>0&&mx<val)) return 0;
	if(!cmx) mx=val;
	fill(ans,ans+len,mx);
	memset(vis,true,len);
	L(i,len,m-1)
	{
		if(~lim[i]) vis[i]=1,ans[i]=lim[i];
		else vis[i]=0,ans[i]=ans[i+1];
	}
	int del=-accumulate(ans,ans+m,-st);
	if(del<0) return 0;
	int t=m;
	L(i,len,m-1) if(vis[i])
	{
		i==--t||(del-=divs(i+1,t,ans[i]-ans[t],del),t=i);
	}
	if(len!=t) del-=divs(len,t-1,ans[len-1]-ans[t],del);
	if(!del)
	{
		int sum=0;
		for(int i=0,j=m;i<=n;++i,sum+=j)
		{
			for(;j&&ans[j-1]<=i;sum+=ans[--j]-i);
			if(sum<max(sl[i],st-sr[i])) return 0;
		}
		return 1;
	}
	return !cmx&&(val+=(del+t-1)/t)<=n&&check(len,val);
}
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cin>>n>>m;
	R(i,0,n-1) {cin>>l[i]>>r[i];}
	sort(l,l+n,greater<int>());
	sort(r,r+n,greater<int>());
	L(i,0,n-1) sl[i]=l[i]+sl[i+1],sr[i]=r[i]+sr[i+1];
	L(i,0,n) sl[i]=sl[0]-sl[i];
	cin>>q;
	memset(lim,-1,sizeof(lim));
	lim[m]=0;
	R(i,1,q)
	{
		int x,y;
		cin>>x>>y;lim[x-1]=y;
	}
	cin>>st;
	int l=0,r=m;
	while(l<r)
	{
		int mid=(l+r+1)/2;
		if(check(mid,0)) l=mid;
		else r=mid-1;
	}
	if(!l) ns();
	ans1=l;
	l=0,r=n;
	while(l<r)
	{
		int mid=(l+r+1)/2;
		if(check(ans1,mid)) l=mid;
		else r=mid-1;
	}
	cout<<ans1<<" "<<l<<endl;
}
posted @ 2022-03-07 22:10  yoisaki_hizeci  阅读(38)  评论(0)    收藏  举报