洛谷 P1273 【有线电视网】
題目描述
某收費有線電視網計劃轉播一場重要的足球比賽。他們的轉播網和用戶終端構成一棵樹狀結構,這棵樹的根結點位于足球比賽的現場,樹葉為各個用戶終端,其他中轉站為該樹的內部節點。
從轉播站到轉播站以及從轉播站到所有用戶終端的信號傳輸費用都是已知的,一場轉播的總費用等于傳輸信號的費用總和。
現在每個用戶都準備了一筆費用想觀看這場精彩的足球比賽,有線電視網有權決定給哪些用戶提供信號而不給哪些用戶提供信號。
寫一個程序找出一個方案使得有線電視網在不虧本的情況下使觀看轉播的用戶盡可能多。
輸入
輸入文件的第一行包含兩個用空格隔開的整數N和M,其中2≤N≤3000,1≤M≤N-1,N為整個有線電視網的結點總數,M為用戶終端的數量。
第一個轉播站即樹的根結點編號為1,其他的轉播站編號為2到N-M,用戶終端編號為N-M+1到N。
接下來的N-M行每行表示—個轉播站的數據,第i+1行表示第i個轉播站的數據,其格式如下:
??? K?A1?C1?A2?C2?…?Ak?Ck
K表示該轉播站下接K個結點(轉播站或用戶),每個結點對應一對整數A與C,A表示結點編號,C表示從當前轉播站傳輸信號到結點A的費用。最后一行依次表示所有用戶為觀看比賽而準備支付的錢數。
輸出
輸出文件僅一行,包含一個整數,表示上述問題所要求的最大用戶數。樣例輸入
5 3 2 2 2 5 3 2 3 2 4 3 3 4 2樣例輸出
2提示
如下圖所示,共有五個結點。結點①為根結點,即現場直播站,②為一個中轉站,③④⑤為用戶端,共M個,編號從N-M+1到N,他們為觀看比賽分別準備的錢數為3、4、2,從結點①可以傳送信號到結點②,費用為2,也可以傳送信號到結點⑤,費用為3(第二行數據所示),從結點②可以傳輸信號到結點③,費用為2。也可傳輸信號到結點④,費用為3(第三行數據所示),如果要讓所有用戶(③④⑤)都能看上比賽,則信號傳輸的總費用為:
2+3+2+3=10,大于用戶愿意支付的總費用3+4+2=9,有線電視網就虧本了,而只讓③④兩個用戶看比賽就不虧本了。
我用的是多叉樹轉二叉樹的方法,這題只要分四種情況考慮就行了:
①?這是用戶節點,且沒有兄弟(右兒子);
② 這是一個用戶節點,但有右子(兄弟節點);
③?這是轉播站,且沒有兄弟節點;
④?這是個轉播站,且有兄弟節點。
詳細過程可以看代碼,里面有很詳細的注釋:
#include <cstdio>struct tree {int lc,rc; //多叉樹轉二叉樹:左兒子右兄弟 int val; //選擇選擇節點i時能賺多少錢(該節點的值(如果是中轉站則為0)-父節點連到這個節點的邊的值) int num; //表示該節點為根的樹,最多有多少個用戶節點 };const int maxn=3000; int n,m; tree a[maxn+1]; int dp[maxn+1][maxn+1];int maximum(int x, int y) {if (x>y) return x;return y; }//計算以x為根的樹最多有多少個用戶節點 int count(int x) {int res=0;//如果自己是用戶節點,答案+1 if (a[x].lc==0)res++;//如果有左右子,則遞歸統計 if (a[x].rc!=0)res+=count(a[x].rc);if (a[x].lc!=0)res+=count(a[x].lc);a[x].num=res;return res; }void init() {scanf("%d%d",&n,&m);//第i個轉播站的信息 for (int i=1; i<=n-m; i++) {int ii,k,vv;scanf("%d",&k);//對k=1的特殊處理,要放在這個轉播站的左兒子上 scanf("%d%d",&ii,&vv);a[i].lc=ii;a[ii].val-=vv;//其余的放在之前處理過的兄弟節點的右兒子上 for (int j=2; j<=k; j++) {int x,v;scanf("%d%d",&x,&v);a[ii].rc=x;a[x].val-=v;ii=x;}}//用戶節點的數據 for (int i=n-m+1; i<=n; i++) {int x;scanf("%d",&x);a[i].val+=x;}//計算結構體中的num的值,搜索時有用 count(1); }//dp[root][x]表示以root為根的樹,里面有x個用戶節點,所能賺到的最多的錢數 int dfs(int root, int x) {if (dp[root][x]!=0) return dp[root][x]; //記憶化搜索 if (x==0) return 0; //如果沒有連任何一個用戶節點,則不賺不虧,返回0 //如果這是用戶節點,且沒有兄弟(右兒子),那么就加上這個節點的值if (a[root].lc==0&&a[root].rc==0)return dp[root][x]=a[root].val; //如果這是轉播站且沒有兄弟節點,那么只能往左子(兒子節點)走 //因為經過這個節點,所以要加上這個答案。再遞歸左子樹。 if (a[root].lc!=0&&a[root].rc==0)return dp[root][x]=dfs(a[root].lc,x)+a[root].val;//如果是一個用戶節點,但有右子(兄弟節點)//那么分選與不選兩種情況考慮 if (a[root].lc==0&&a[root].rc!=0) {//選的話要加上它的值int res=dfs(a[root].rc,x-1)+a[root].val;//不選的話有限制條件:現在要保留的用戶節點數(x)不大于它右子樹里的用戶節點數 //即保證它的右子里至少有x個用戶節點 if (a[a[root].rc].num>=x)res=maximum(res,dfs(a[root].rc,x)); //取兩種情況中的較優情況 return dp[root][x]=res;}//否則這是個轉播站,且有兄弟節點 int res=-0x7F7F7F7F;//枚舉在分給左子樹i個用戶節點,右子樹x-i個用戶節點的可能性,取最優 //特別注意:當不傳給這個中轉站,只給它的兄弟節點(右子)時,可以不取這個節點的值!//故i從1開始,而非0(我因為這個WA了好多次) for (int i=1; i<=x; i++) {if (a[a[root].lc].num<i) continue; //如果左子樹里只有不到i個用戶節點,不滿足條件 if (a[a[root].rc].num<x-i) continue; //右子同理 res=maximum(res,dfs(a[root].lc,i)+dfs(a[root].rc,x-i)+a[root].val);}//如果全給其兄弟節點時兄弟節點里可以有這么多用戶節點(num>=x),則取較優值 if (a[a[root].rc].num>=x)res=maximum(res,dfs(a[root].rc,x));return dp[root][x]=res; }int main() {init();for (int i=m; i>=0; i--) { //從m~0逆序枚舉給i個用戶信號的情況 int ans=dfs(1,i); //從根節點(1)開始記憶化搜索 //如果不會虧本,則跳出循環,直接輸出答案 if (ans>=0) {printf("%d\n",i);break;}}return 0; }?
轉載于:https://www.cnblogs.com/tweetuzki/p/8277743.html
總結
以上是生活随笔為你收集整理的洛谷 P1273 【有线电视网】的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Linux日常运维管理技巧(一)监控系统
- 下一篇: [高级软件工程教学]团队Beta阶段成绩