CF2023D - Many Games

HaneDaniko發表於2024-11-10

HDK: 他媽的,這個看著也不像 2900 啊,為啥控我這麼久

lbtl: 他不控你這麼久不就不是 2900 了嗎

暴力

一個比較明顯的暴力思路是,如果我們欽定選定的物品的價值,那麼可以比較容易地由揹包 DP 算出能達到這個欽定值的最大機率

\([0,\sum w_i]\) 列舉所有可能的價值,暴力跑若干次揹包,可以透過高達六個測試點

暴力
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
int p[200001],w[200001];
long double f[200001];
signed main(){
    cin>>n;
    int totw=0;
    for(int i=1;i<=n;++i){
        cin>>p[i]>>w[i];
        totw+=w[i];
    }
    long double ans=0;
    for(int i=1;i<=totw;++i){
        for(int j=0;j<=i;++j){
            f[j]=0;
        }
        for(int j=1;j<=n;++j){
            for(int k=i;k>=w[j];--k){
                f[k]=max(f[k],f[k-w[j]]*p[j]/100.0);
            }
            f[w[j]]=max(f[w[j]],(long double)p[j]/100.0);
        }
        ans=max(ans,f[i]*i);
    }
    printf("%.8Lf",ans);
}

最佳化

一個比較 naive 的最佳化是,你發現你根本就不用欽定選定價值,直接跑一遍揹包就能把所有答案跑出來,但是因為太 naive 了,仍然只有六個測試點的高分

第一個最佳化是對我們列舉上界的最佳化

推一遍式子,考慮設當前 \(\sum \frac{p_i}{100}=x,\sum w_i=y\)

當加入物品 \((p,w)\) 時對答案有正貢獻,當且僅當

\[\begin{aligned}xy&\lt x\times \frac{p}{100}\times (y+w)\\y&\lt \frac{p}{100}(y+w)\\(1-\frac{p}{100})y&\lt\frac{p}{100}w\\y&\lt\frac{pw}{100-p}\end{aligned} \]

由於題目規定 \(pw\le 2\times 10^5\),而 \(100-p\) 的下界是 \(1\),因此加入物品 \((p,w)\) 時對答案有正貢獻的一個必要條件是 \(\sum\limits w_i\le 2\times 10^5\)

這樣我們就將答案區間 從 \([0,\sum w_i]\) 降到 \([0,2\times 10^5]\)

但是

喜報,複雜度還是不對

我們再考慮其他兩個最佳化

第二個最佳化是,我們貪心地想,選擇 \(p_i=100\)\(i\) 一定是最優的,因此我們可以提前將 \(p_i=100\)\(i\) 全部選上,這樣既壓縮了答案區間也減少了物品數量

第三個最佳化是,我們觀察剩下的 \(p_i\neq 100\) 的物品,如果我們將 \(p_i\) 相同的 \(i\) 分成同一組,並且讓每一組內按照 \(w_i\) 降序排列,根據貪心思路,當 \(p\) 相同的時候,應該是 \(w\) 越大越好,最終答案一定是形如從每個 \(p_i\) 的組內選出一個字首

和剛才的思路類似,現在我們欽定我們選擇的物品的 \(p_i\) 都為 \(p\),如果我們給當前 \(p\) 選擇的字首長度從 \(k\) 增加到 \(k+1\),答案更優當且僅當(這裡給每個機率掛分母太麻煩了,在下面幾個式子裡欽定 \(p=\frac{p_i}{100}\)

\[\begin{aligned}p^k\times \sum\limits_k w\lt p^{k+1}\times(w_{k+1}+\sum\limits_k w)\end{aligned} \]

由於新加入的 \(w_{k+1}\) 不大於已有的任何一個數,因此有 \(k\times w_{k+1}\le\sum\limits_k w_i\),因此

\[\begin{aligned}p^k\times \sum\limits_k w&\le p^{k+1}\times\frac{k+1}{k}\times\sum\limits_k w_i\\\frac{k+1}{k}&\lt p\\k+1&\lt k\times p\\k\lt \frac{1}{1-p}\end{aligned} \]

這個式子意味著只有前 \(\frac{1}{1-p}\) 個物品有可能成為最優解

賽時止步於只寫了兩個最佳化,並且發現了最後那個先增後減的單調性,但是沒總結出最後一個結論,比較可惜

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
int p[200001],w[200001];
struct item{
    int w,p;
}a[200001];
int cnt=0;
long long orians;
long double f[200001];
long double ans;
vector<int>v[101];
signed main(){
    cin>>n;
    for(int i=1;i<=n;++i){
    	cin>>p[i]>>w[i];
    	v[p[i]].push_back(w[i]);
    	if(p[i]==100) orians+=w[i];
	}
    for(int i=1;i<=100;++i){
        sort(v[i].begin(),v[i].end(),greater<int>());
    }
	for(int i=1;i<=99;++i){
        int tot=0;
	    for(int j=0;j<=(int)v[i].size()-1;++j){
	    	if(tot>100/(100-i)) break; 
	    	a[++cnt]={v[i][j],i};
            tot++;
		}
	};
	f[0]=1;
	for(int i=1;i<=cnt;++i){
	    for(int j=200000;j>=a[i].w;--j){
	        f[j]=max(f[j],f[j-a[i].w]*a[i].p/100.0);
        }
    }
	for(int i=0;i<=200000;++i){
        ans=max(ans,f[i]*(orians+i));
    }
	printf("%.8Lf",ans);
}

相關文章