[JSOI2016]最佳團體
阿新 • • 發佈:2019-02-13
遍歷 pri 好的 子節點 大小 logs for 一道 return 又看到了比值最大(小)問題,就想到了分數規劃,理解清楚題意,列出我們要求的答案就是\(\frac{\sum_{i=1}^{k}pi}{\sum_{i=1}^{k}si}\)
P.S.不知道是本題卡常太過分,還是我的代碼太醜,一定要吸氧才能不超時(這道題我交了幾十遍,最好的成績都T了一個點,歡迎各位不吸氧就能過的巨佬
傳送門
個人認為本題是這道題的加強版,加強之處在於本題要用到背包類樹形DP.所以不會背包類樹形DP的話,可以先做這道題.也可以自行學習.
推薦的第一道題的題解
推薦的第二道題的題解
題意:N個人,1-N編號,每個人價值pi,費用si,推薦人ri,如果選了i,就必須要選擇他的推薦人ri,ri可以等於零(編號為0的人是特殊的,它永遠在團隊中,且沒有價值和費用),從這N個人中選出m個人(不包括編號0的人)構成一個團隊,使得團隊總價值與總費用的比值最大.
又看到了比值最大(小)問題,就想到了分數規劃,理解清楚題意,列出我們要求的答案就是\(\frac{\sum_{i=1}^{k}pi}{\sum_{i=1}^{k}si}\)
想到了分數規劃,就想到了要二分答案,直接二分mid,如果此次二分的值成立,即\(\frac{\sum_{i=1}^{k}pi}{\sum_{i=1}^{k}si}>=mid\)
整理一下上式得\(\sum_{i=1}^{k} (pi-si*mid>=0)\)
所以每次check時,把每個人的價值更新為\(pi-si*mid\),題目就轉換為了有N個人,已知每個人的價值,選出k個人使得價值和>=0,有沒有看到背包的影子?
以上都是分數規劃的思路的模板,現在我們來考慮這道題目的特殊之處,就是每個人都有唯一的推薦人ri,只有推薦人ri被選中,才能選擇第i個人.
因為每個人都有唯一的推薦人ri,相當於樹中每個節點最多只有一個父節點,又因為有0號節點這個特殊的點存在,所以N+1個節點構成了一棵以0號節點為根節點的樹,還因為題目要求最優解,於是就想到了可以樹形DP.
設f[x][i]表示在以x為根節點的子樹中選擇i個點能獲得的最大價值(一般樹形DP都是這樣設的吧),不講了.
P.S.不知道是本題卡常太過分,還是我的代碼太醜,一定要吸氧才能不超時(這道題我交了幾十遍,最好的成績都T了一個點,歡迎各位不吸氧就能過的巨佬吊打教我)
int n,m; int s[2505],p[2505],r[2505],size[2505]; double eps=1e-6;//設置二分精度 double v[2505],f[2505][2505]; vector<int> q[2505]; inline void dfs(int x){ f[x][0]=0;f[x][1]=v[x]; for(int i=2;i<=m;i++)f[x][i]=-1e9; //初始化,這個很顯然吧 size[x]=1; //size數組表示以x為根節點的子樹中的子節點的數量 //把x視作以x為根節點的子樹中的子節點,所以初始值為1 //用於優化樹形DP,不優化T飛5個點 //不懂的話,底下推薦的第二篇博客有提到 for(int i=0;i<q[x].size();i++){ int y=q[x][i]; dfs(y); for(int j=size[x];j>=1;j--) for(int k=0;k<=size[y];k++){ if(j+k>m)break;//優化 f[x][j+k]=max(f[x][j+k],f[x][j]+f[y][k]); } size[x]+=size[y]; } } inline bool check(double mid){ for(int i=1;i<=n;i++) v[i]=p[i]-s[i]*mid; //記得根據mid更新每個人的價值 dfs(0);//從根節點開始遍歷整個樹 return f[0][m]>=0;//判定 } int main(){ m=read();n=read(); m++; //因為0號節點必選,所以相當於我們要選擇m+1個節點 for(int i=1;i<=n;i++){ s[i]=read(); p[i]=read(); r[i]=read(); q[r[i]].push_back(i); } //我直接vector存圖了,也可以用鏈式前向星 double l=0,r=1e6,mid; while(l+eps<r){ mid=(l+r)/2.0; if(check(mid))l=mid; else r=mid; } printf("%.3lf\n",l); return 0; }
推薦1
推薦2
[JSOI2016]最佳團體