kmjp's blog

競技プログラミング参加記です

Codeforces ECR #154 : F. Four Suits

割と手間取った。
https://codeforces.com/contest/1861/problem/F

問題

4種類のカードがあり、各種類のカードは複数枚存在する。
N人のプレイヤーでゲームを行う。その際、各人が初期状態で持つカードが入力で与えられる。
また、各種類のカードで余った枚数が与えられる。

余ったカードを、各人に割り振ることにする。
その際、全員のカード総数が一致しなければならない。
各人のスコアは、4種のカードのうち最大枚数を持つカードの枚数から、他の人の同様に算出した枚数の最大値の差とする。

余ったカードの割り振りを任意に決められるとき、各人の最大スコアを求めよ。

解法

各人、4種類のそれぞれに極力カードを集める場合を総当たりする。
まず対象の人・種類に極力カードを割り振るのが良い。
その他の人は、極力カードがばらけるようにしたい。
そこで、他人の各種のカード枚数の上限dを二分探索しよう。

各人各種のカード枚数がd以下となるように配れるかは、最大フローで解くことができる。
とはいえまじめに最大フローのアルゴリズムを回すと間に合わない。
ここは最小カットを求めることにする。

ここで参考になるのが最近のABC。
AtCoder ABC #354 (パナソニックグループ プログラミングコンテスト2024) : G - Select Strings - kmjp's blog

これと似た要領で、4種類のカードのうち、カットとなる辺の組み合わせ16通りを総当たりしながら、最小カットの最大値を求めよう。

ll N,A[202020][4],R[202020];
ll RS;
ll S;

ll P[(1<<21)+10];  // Px+Q
ll Q[(1<<21)+10];
ll V[(1<<21)+10][16];
ll Lma[101010],Rma[101010];


int ok(int x,int cur,int add) {
	ll ma=1LL<<60;
	int mask,i;
	
	if(cur&&Lma[cur-1]>x) return 0;
	if(cur<N-1&&Rma[cur+1]>x) return 0;
	
	FOR(mask,1<<4) {
		ll cut=V[x][mask];
		ll sum=0;
		FOR(i,4) {
			if(mask&(1<<i)) {
				cut+=A[N][i];
			}
			else {
				sum+=max(0LL,x-A[cur][i]);
			}
		}
		//cout<<"aa"<<mask<<" "<<cur<<" "<<cut<<" "<<sum<<" "<<R[cur]<<endl;
		cut-=min(sum,R[cur]+add);
		ma=min(ma,cut);
	}
	
	//cout<<x<<" "<<cur<<" "<<ma<<" "<<RS-(R[cur]+add)<<endl;
	
	return (ma>=RS-(R[cur]+add));
}

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	int mask;
	cin>>N;
	FOR(i,N+1) {
		FOR(j,4) {
			cin>>A[i][j];
			S+=A[i][j];
			Lma[i]=max(Lma[i],A[i][j]);
		}
		Lma[i+1]=Lma[i];
		
	}
	for(i=N-1;i>=0;i--) {
		Rma[i]=Rma[i+1];
		FOR(j,4) Rma[i]=max(Rma[i],A[i][j]);
	}
	
	FOR(i,N) {
		int a=S/N;
		FOR(j,4) a-=A[i][j];
		R[i]=a;
		RS+=a;
	}
	
	
	FOR(mask,16) {
		ZERO(P);
		ZERO(Q);
		FOR(i,N) {
			vector<int> V;
			FOR(j,4) if((mask&(1<<j))==0) V.push_back(A[i][j]);
			sort(ALL(V));
			V.push_back(1<<21);
			ll cur=0;
			FOR(j,V.size()-1) {
				ll nex=cur+1LL*(j+1)*(V[j+1]-V[j]);
				P[V[j]]+=j+1;
				Q[V[j]]+=cur-1LL*(j+1)*V[j];
				if(nex<=R[i]) {
					P[V[j+1]]-=j+1;
					Q[V[j+1]]-=cur-1LL*(j+1)*V[j];
					cur=nex;
				}
				else {
					ll over=(R[i]-cur+j)/(j+1)+V[j];
					P[over]-=j+1;
					Q[over]-=cur-1LL*(j+1)*V[j];
					Q[over]+=R[i];
					break;
				}
			}
			
		}
		for(i=0;i<=1<<21;i++) {
			if(i) {
				P[i]+=P[i-1];
				Q[i]+=Q[i-1];
			}
			V[i][mask]=P[i]*i+Q[i];
		}
	}
	
	FOR(i,N) {
		ll ret=0;
		FOR(j,4) {
			int add=min(R[i],A[N][j]);
			A[N][j]-=add;
			R[i]-=add;
			
			int cur=(1<<21)-1;
			for(k=20;k>=0;k--) if(ok(cur-(1<<k),i,add)) cur-=1<<k;
			//cout<<i<<" "<<j<<" "<<cur<<" "<<A[i][j]+add<<" "<<A[i][j]<<" "<<add<<endl;
			ret=max(ret,A[i][j]+add-cur);
			R[i]+=add;
			A[N][j]+=add;
		}
		cout<<ret<<" ";
	}
	cout<<endl;
	
}

まとめ

先にABC解いてたので、考え方はすんなり飲み込めたけど細かいところを詰めるのに手間取った。