CodeForcse 1305H Kuroni the Private Tutor
CodeForcse 1305H Kuroni the Private Tutor
https://codeforces.com/contest/1305/problem/H
\(m\) 个同学参加了一场考试,有 \(n\) 道题,每道题的分数都是 \(1\) .你碰巧的瞥了一眼最终成绩单,得知了一些信息
- 第 \(i\) 道题至少有 \(L_i\) 个同学做出来了,至多有 \(R_i\) 个同学做出来
- 第 \(p_i\) 名的分数为 \(s_i\) , \(i \in [1,q]\)
- 所有同学的总分为 \(T\)
其中排名的顺序为第一名得分最高,最后一名得分最低,分数相同的顺序任意.
问最多有多少名同学分数和第一名相同,在满足和第一名相同成绩的人数最大化的同时,第一名的分数最大为多少.
特别的,如果不存在满足以上限制的成绩单,输出"-1 -1"
\(1 \le n,m \le 10^5\) , \(0 \le q \le m\)
\(0 \le l_i \le r_i \le m\)
\(1 \le p_i \le m, 0 \le s_i \le n\) ,满足 \(p_i\) 互不相同,且对于任意 \(i,j \in [1,q]\) ,若 \(p_i \le p_j\) ,则 \(s_i \ge s_j\)
Tutorial
考虑假设我们已经决定了每位同学的分数 \(b_1 \le b_2 \le \cdots, \le b_m, \sum b_i = T\) ,想要判断是否存在给每个同学分配它做对的题目集合,使得满足 \(L_i,R_i\) 限制的方案.
发现这个可以建立上下界网络流模型,设立源点和汇点,还有 \(n\) 个点表示题目, \(m\) 个点表示同学.源点向第 \(i\) 个题目的点连接上界为 \(L_i\) 下界为 \(R_i\) 的边,第 \(i\) 个同学向汇点连接上界,下界均为 \(b_i\) 的边.每对题目和同学之间连接上界为 \(1\) ,下界为 \(0\) 的边.
那么我们需要满足两个条件
- 存在可行流
- 最大流为 \(T\)
可以利用最小割的模型来思考,可以枚举最小割的形式,得到这样的式子
其中 \(sumL_j\) 表示 \(L\) 中前 \(i\) 小的数的和, \(sumR_i\) 表示 \(R\) 中前 \(i\) 小的数的和, \(B_i = \sum_{j=1}^i b_j\) . \(sL\) 表示所有 \(L\) 的和.
这可以用斜率优化做到 \(O(n+m)\) 判断.
显然,和第一名相同的同学数是可以二分的.
而从最后的式子的形式可以看出,若 \(a\) majorize \(b\) ,那么 \(a\) 作为 \(B\) 会比 \(b\) 作为 \(B\) 更优秀,也就是说让排名小的同学分数尽量大是更优的,所以第一名的分数也是可以二分的.
在二分第一名的分数的时候,需要分别考虑一下两种情况
-
被 \(p_i,s_i,T\) 的限制而判非法
-
被 \(L_i,R_i\) 的限制而判非法
Code
https://codeforces.com/contest/1305/submission/72366397
#include <algorithm>
#include <cassert>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#define debug(...) fprintf(stderr,__VA_ARGS__)
#define invalid0(a,b,c) (A[b]-A[a])*(c-b)>=(A[c]-A[b])*(b-a)
#define invalid1(a,b,c) (ll)(a)*(c-b)<=(A[c]-A[b])
#define fi first
#define se second
using namespace std;
inline char nc()
{
return getchar();
static char buf[100000],*l=buf,*r=buf;
return l==r&&(r=(l=buf)+fread(buf,1,100000,stdin),l==r)?EOF:*l++;
}
template<class T> void read(T &x)
{
x=0; int f=1,ch=nc();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=nc();}
while(ch>='0'&&ch<='9'){x=x*10-'0'+ch;ch=nc();}
x*=f;
}
template<class T> inline bool Cmin(T &x,T y) {return x>y?x=y,1:0;}
typedef long long ll;
typedef pair<int,int> pii;
const ll INF=1e18;
const int maxn=1e5+50;
int n,m,q; ll T;
int mx;
int re;
int a[maxn];
int b[maxn];
int tmp[maxn];
ll sL;
ll sum;
ll B[maxn];
ll L[maxn];
ll R[maxn];
bool check0(ll rest)
{
vector<pii> rec;
int now=1;
for(int i=1;i<=m;++i)
{
if(a[i]!=-1)
{
if(now<i) rec.push_back(make_pair(now,i-1));
now=i+1;
}
}
for(int i=0;i<rec.size()&&rest;++i)
{
int l=rec[i].fi,r=rec[i].se;
ll d=(ll)(r-l+1)*(B[r+1]-B[r]);
if(rest>=d)
{
rest-=d;
for(int j=l;j<=r;++j) B[j]=B[r+1];
}
else
{
int x=rest/(r-l+1),y=rest-(ll)x*(r-l+1);
for(int j=l;j<=r;++j)
{
B[j]+=x;
if(r-j+1<=y) ++B[j];
}
rest=0;
}
}
return rest==0;
}
ll cal(ll *A)
{
static int sta[maxn]; int top=0;
for(int i=0;i<=n;++i)
{
while(top>1&&invalid0(sta[top-1],sta[top],i)) --top;
sta[++top]=i;
}
ll mn=INF;
for(int i=0;i<=m;++i)
{
while(top>1&&invalid1(m-i,sta[top-1],sta[top])) --top;
int x=sta[top];
Cmin(mn,A[x]+B[i]+(ll)(n-x)*(m-i));
}
return mn;
}
bool check1()
{
// for(int i=1;i<=n;++i) debug("%lld ",L[i]); debug("\n");
// for(int i=1;i<=n;++i) debug("%lld ",R[i]); debug("\n");
// for(int i=1;i<=m;++i) debug("%lld ",B[i]); debug("\n");
for(int i=1;i<=m;++i) B[i]+=B[i-1];
assert(B[m]==T);
return cal(L)>=sL&&cal(R)>=T;
}
bool sol(int len)
{
int rec=-1;
for(int i=m-len+1;i<=m;++i) if(a[i]!=-1)
{
if(rec!=-1&&rec!=a[i]) return 0;
rec=a[i];
}
int l,r; bool ok=0;
if(rec!=-1) l=r=rec;
else l=mx,r=min((ll)n,(T-sum)/len+mx);
// l=r=8;
while(l<=r)
{
int mid=(l+r)>>1;
for(int i=1;i<=m;++i) B[i]=b[i];
ll rest=T-sum;
for(int i=m-len+1;i<=m;++i)
{
rest-=mid-B[i]; B[i]=mid;
tmp[i]=a[i],a[i]=mid;
}
// debug("%lld\n",rest);
// for(int i=1;i<=m;++i) debug("%lld ",B[i]); debug("\n");
if(rest<0)
{
r=mid-1;
}
else if(!check0(rest))
{
l=mid+1;
}
else
{
if(check1()) ok=1,re=mid,l=mid+1;
else r=mid-1;
}
for(int i=m-len+1;i<=m;++i)
{
a[i]=tmp[i];
}
}
return ok;
}
int main()
{
read(n),read(m);
for(int i=1;i<=n;++i)
{
read(L[i]),read(R[i]);
}
read(q);
memset(a,-1,sizeof(a));
for(int i=1;i<=q;++i)
{
int p,s; read(p),read(s);
a[p]=s;
}
read(T);
sort(L+1,L+n+1);
sort(R+1,R+n+1);
for(int i=1;i<=n;++i)
{
L[i]+=L[i-1];
R[i]+=R[i-1];
}
sL=L[n];
reverse(a+1,a+m+1);
for(int i=1;i<=m;++i)
{
if(a[i]!=-1) mx=a[i];
b[i]=mx;
sum+=mx;
}
if(sum>T)
{
puts("-1 -1");
return 0;
}
int l=1,r=m,an=-1; re=-1;
while(l<=r)
{
int mid=(l+r)>>1;
if(sol(mid)) an=mid,l=mid+1;
else r=mid-1;
}
// cerr<<sol(7)<<endl;
printf("%d %d\n",an,re);
return 0;
}