[洛谷P4322][題解][JSOI2016]最佳團體
阿新 • • 發佈:2020-09-04
0.???
似乎又咕咕咕了好久呢……開了學時間少了(絕不是因為我懶哦)
又刷到了一個有趣的新演算法(or新解法?),特此寫一寫~
1.概述
給出一棵樹和每個點的\(S_i,P_i\),只能從根節點向下連續選點,求\(\max\{\frac{\sum{P_i}}{\sum{S_i}}\}\)。
2.解法
運用到了一種叫做分數規劃的解法(雖然聽起來很高階其實簡單得不能再簡單系列)。首先,我們發現答案中的比值太難看了,去除!於是就變成了:\(\max\{\sum{(P_i-ans\times S_i)}\}\)。暫且將求和號裡面的東西記作\(val_i\),然後根據\(ans\)的不同答案也會有不同的取值(當前答案小了還是大了會體現在\(\sum{val_i}\ge 0\)
總結一下:
1.將答案變換形式,更易於求解;
2.二分答案+判斷。
之後再往二分裡面套一個普通的\(DP\),設\(f[i][j]\)為\(i\)子樹中選\(j\)個點的最大答案,轉移就是依次列舉每個兒子選的個數。
3.程式碼
預設源在我的部落格裡
#define N 2510 int k,n,siz[N]; double val[N],f[N][N],s[N],p[N]; struct Edge { int to,nxt; }e[N<<1]; int head[N],cnt; inline void ade(int u,int v){ e[++cnt].to=v; e[cnt].nxt=head[u]; head[u]=cnt; } void DFS(int now){ f[now][1]=val[now],siz[now]=1; for(rg int i=head[now];i;i=e[i].nxt){ int v=e[i].to; DFS(v); siz[now]+=siz[v]; for(rg int j=min(siz[now],k+1);j;j--){ for(rg int l=0;l<=min(siz[now],j-1);l++){ f[now][j]=max(f[now][j],f[v][l]+f[now][j-l]); } } } } bool Check(double mid){ for(rg int i=1;i<=n;i++){ val[i]=p[i]-mid*s[i]; } for(rg int i=0;i<=n;i++){ for(rg int j=1;j<=k+1;j++)f[i][j]=-INF; } DFS(0); return f[0][k+1]>=0; } int main(){ Read(k),Read(n); for(rg int i=1;i<=n;i++){ int rr; scanf("%lf%lf%d",&s[i],&p[i],&rr); ade(rr,i); } double l=0.0,r=10000.0,mid; while(r-l>eps){ mid=(l+r)/2; if(Check(mid))l=mid; else r=mid; } printf("%.3lf\n",l); return 0; }