BZOJ4753: [Jsoi2016]最佳團體(分數規劃+樹上揹包)
阿新 • • 發佈:2018-11-10
BZOJ4753: [Jsoi2016]最佳團體(分數規劃+樹上揹包)
具體實現
看到分數和最值,考慮分數規劃
我們要求的是一個\(\dfrac{\sum P_i}{\sum S_i}\)最大對吧,考慮二分一個答案\(mid\)
那麼就會有合法條件\(\dfrac{\sum P_i}{\sum S_i}\ge mid\),化簡一下:\(\sum{(P_i-S_i×mid)}\ge 0\)
所以每次二分一個\(mid\)之後得到一個新陣列v[i]=P[i]-S[i]×mid
,我們跑揹包\(check\)它最後是否大於等於\(0\)就可以了,因為有約束條件,所以把樹建出來跑樹上揹包
dp[i][j]
表示\(i\)節點選了\(j\)個湊出的\(v\)的最大值,我們最後就只要判斷dp[0][K+1]>=0
就可以啦
等一下!
這個\(dp\)的轉移看上去向\(n^2\)的啊,再乘上\(Dfs\)的\(n\)的複雜度不就複雜度假了嗎
其實不然,總體來看,每一對節點的\(dp\)狀態只會在他們的\(Lca\)處被轉移一次,想一想為什麼(我想了挺久),所以複雜度就是\(n^2\)的啦
然而它還是卡常啊,最後\(Junlier\)就卡了很久,終於\(988ms\)卡過去了。。。
程式碼
#include<bits/stdc++.h> #define il inline #define rg register #define ldb double #define lst long long #define rgt register int #define N 2550 #define pb push_back #define qw G[now][i] using namespace std; const lst Inf=1e18; const ldb eps=1e-4; il int read() { int s=0,m=0;char ch=getchar(); while(!isdigit(ch)){if(ch=='-')m=1;ch=getchar();} while( isdigit(ch))s=(s<<3)+(s<<1)+(ch^48),ch=getchar(); return m?-s:s; } int K,n,Dex; int siz[N]; struct LJL{int S,P,fa;}ljl[N]; ldb v[N],tmp[N],dp[N][N],Ans; vector<int> G[N]; il void Merge(rgt x,rgt y) { for(rgt i=0;i<=K+1;++i)tmp[i]=-Inf; for(rgt i=1,up;i<=siz[x];++i) { up=min(K+1-i,siz[y]); for(rgt j=1;j<=up;++j) tmp[i+j]=max(tmp[i+j],dp[y][j]+dp[x][i]); }for(rgt i=0;i<=K+1;++i)dp[x][i]=max(dp[x][i],tmp[i]); } void Dfs(rgt now) { for(rgt j=0;j<=K+1;++j)dp[now][j]=-Inf; dp[now][1]=v[now],siz[now]=1; for(rgt i=0;i<G[now].size();++i) Dfs(qw),Merge(now,qw),siz[now]+=siz[qw]; } il bool check(ldb lim) { for(rgt i=1;i<=n;++i) v[i]=ljl[i].P-ljl[i].S*lim; Dfs(0);return dp[0][K+1]>=0; } int main() { K=read(),n=read(); for(rgt i=1;i<=n;++i) { ljl[i]=(LJL){read(),read(),read()}; G[ljl[i].fa].pb(i); }ldb le=eps,ri=1e5,mid; while(ri-le>eps) { mid=(le+ri)/2; if(check(mid))Ans=mid,le=mid+eps; else ri=mid-eps; }return printf("%.3lf\n",Ans),0; }