Codeforces 1861F - Four Suits

发布时间 2023-10-18 20:58:10作者: tzc_wk

萌新刚学 oi,开道小清新题找找感觉。

首先求出每个人最终有的卡牌数 \(X\),以及每个人需要收到的卡牌数 \(c_i\)

考虑怎么计算一个人 \(i\) 的答案,假设我们希望第 \(i\) 个人最多的卡牌为类型 \(j\),那么得尽可能将类型 \(j\) 的卡牌给 \(i\)——显然我们最多能给 \(\min(b_j,c_i)\) 个卡牌 \(j\) 给第 \(i\) 个人。这样第 \(i\) 个人每种卡牌个数的最大值就确定了,这样我们的目标变为让另外 \(n-1\) 个人每种卡牌个数的最大值尽可能小。考虑二分答案 \(mid\),思考如何检验一个 \(mid\) 是否可行:

  • 如果存在 \(k\ne i,0\le j\le 3\) 满足 \(a_{i,j}>mid\),那么一定不可行。

  • 否则考虑网络流建模。新建源点 \(S\) 和汇点 \(T\),以及 \(x_1\sim x_4,y_1\sim y_n\),然后连边:

    • \(S\to x_k\),容量 \(b_i\)
    • \(x_k\to y_j(j\ne i)\),容量 \(mid-a_{j,k}\)
    • \(y_j\to T(j\ne i)\),容量 \(c_j\)

    最后检验最大流是否为 \(\sum\limits_{i\ne j}c_j\) 即可。

显然每次建一次图是没法接受的。考虑最大流最小割定理,我们枚举哪些与源点相连的边没被割掉,那么割掉剩余的边的代价就是 \(\sum\limits_{i\ne j}\min(c_j,\sum\limits_{x\in S}mid-a_{j,x})\),割掉与 \(S\) 相连的边的代价是 \(\sum\limits_{x\notin S}b_x\)。显然,后者是容易计算的,而对于前者而言,如果我们将所有 \(j\) 按照 \(c_j+\sum\limits_{x\in S}a_{j,x}\) 从大到小排序后,那么取到后者的是一段前缀,二分即可。

时间复杂度 \(O(n·2^4·4·\log^2V)\),由于常数小可以通过。

const int MAXN=5e4;
int n,a[MAXN+5][4],b[4],c[MAXN+5],mxa[MAXN+5];ll sum=0,tot=0;
vector<pair<ll,int> >val[16];
vector<ll>pre[16],suf[16];
bool check(int x,int v){
	for(int S=1;S<16;S++){
		ll sum=0,s=0;
		for(int i=0;i<4;i++)if(~S>>i&1)sum+=b[i];
		int L=0,R=n-1,p=n,cs=__builtin_popcount(S);
		while(L<=R){
			int mid=L+R>>1;
			if(val[S][mid].fi<=v*cs)p=mid,R=mid-1;
			else L=mid+1;
		}
		if(p)sum+=1ll*p*v*cs-pre[S][p-1];
		sum+=suf[S][p];
		for(int i=0;i<4;i++)if(S>>i&1)s+=v-a[x][i];
		sum-=min(s,(ll)c[x]);
		if(sum<tot-c[x])return 0;
	}return 1;
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)for(int j=0;j<4;j++)scanf("%d",&a[i][j]),sum+=a[i][j];
	for(int i=0;i<4;i++)scanf("%d",&b[i]),sum+=b[i];
	for(int i=1;i<=n;i++){
		c[i]=sum/n;
		for(int j=0;j<4;j++)c[i]-=a[i][j];
		tot+=c[i];
	}
	for(int i=1;i<=n;i++)for(int j=0;j<4;j++)chkmax(mxa[i],a[i][j]);
	multiset<int>st;
	for(int i=1;i<=n;i++)st.insert(mxa[i]);
	for(int S=1;S<16;S++){
		for(int i=1;i<=n;i++){
			ll sum=c[i];
			for(int j=0;j<4;j++)if(S>>j&1)sum+=a[i][j];
			val[S].pb(mp(sum,i));
		}
		sort(val[S].begin(),val[S].end());
		reverse(val[S].begin(),val[S].end());
		pre[S].resize(n,0);suf[S].resize(n+1,0);
		for(int i=0;i<n;i++){
			int id=val[S][i].se;
			if(i)pre[S][i]=pre[S][i-1];
			for(int j=0;j<4;j++)if(S>>j&1)pre[S][i]+=a[id][j];
		}
		for(int i=n-1;~i;i--){
			int id=val[S][i].se;
			suf[S][i]=suf[S][i+1]+c[id];
		}
	}
	for(int i=1;i<=n;i++){
		int mx=0;st.erase(st.find(mxa[i]));
		for(int j=0;j<4;j++){
			int can=min(b[j],c[i]),v=can+a[i][j];
			int l=*st.rbegin(),r=v,p=v;b[j]-=can;
			while(l<=r){
				int mid=l+r>>1;
				if(check(i,mid))p=mid,r=mid-1;
				else l=mid+1;
			}chkmax(mx,v-p);b[j]+=can;
		}printf("%d%c",mx," \n"[i==n]);
		st.insert(mxa[i]);
	}
	return 0;
}