kmjp's blog

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

yukicoder : No.1554 array_and_me

なるほど…。
https://yukicoder.me/problems/no/1554

問題

長さNの整数配列Zを考える。
初期状態でZの各値は0である。

ここで、以下の処理をK回行う。

  • いずれかの添え字iを選び、Z[i]をインクリメントする。各添え字が選ばれる確率P[i]は、入力で与えられる。

f(X)を、処理後のZがXと一致する確率とする。
max(f(X))を求めよ。

解法

おおよそXの各要素の比率と、各添え字が選ばれる確率が近くなる状態ほどf(X)が大きくなる。
Xの総和がKの時、 \displaystyle f(X)=\frac{K!}{\prod_i x_i!} \prod_i {P_i}^{x_i}である。
対数を取ると \displaystyle \log f(X)= \prod_i(x_i \log P_i - \log x_i!) + constである。

 \displaystyle g_i(x_i) = x_i \log P_i - \log x_i!とすると、g(x)の増加量はだんだん減ってくる。
そこで、Priority_queueを使い、g(x)が大きいものから順にK回X[i]をインクリメントしよう。

Xが定まれば、あとは上記f(X)を計算するだけ。

int T,N,K;
int A[101010];
int X[101010];
const ll mo=998244353;

ll modpow(ll a, ll n = mo-2) {
	ll r=1;a%=mo;
	while(n) r=r*((n%2)?a:1)%mo,a=a*a%mo,n>>=1;
	return r;
}

void solve() {
	int i,j,k,l,r,x,y; string s;
	
	cin>>T;
	while(T--) {
		auto cmp = [](int L,int R) {
			return 1LL*A[L]*(X[R]+1)<1LL*A[R]*(X[L]+1);
		};
		priority_queue<int,vector<int>,decltype(cmp)> Q(cmp);
		cin>>N>>K;
		ll S=0;
		FOR(i,N) {
			cin>>A[i];
			S+=A[i];
			X[i]=0;
			Q.push(i);
		}
		ll ret=1;
		FOR(i,K) {
			x=Q.top();
			Q.pop();
			X[x]++;
			ret=ret*(1+i)%mo;
			ret=ret*modpow(X[x])%mo;
			ret=ret*A[x]%mo;
			ret=ret*modpow(S)%mo;
			Q.push(x);
		}
		
		cout<<ret<<endl;
	}
}

解法

logを使うのと、階差の差分まで評価するのがパッと思いつかないな。