洛谷 P1273 有線電視網(樹形揹包)
阿新 • • 發佈:2018-12-30
洛谷 P1273 有線電視網(樹形揹包)
乾透一道題
題面:洛谷 P1273
本質就是個揹包。這道題dp有點奇怪,最終答案並不是dp值,而是最後遍歷尋找那個合法且最優的\(i\)作為答案。dp值存的是當前狀態下的成本,所以合法情況即當成本值大於等於0,不虧本的時候。
因為dp維護的是成本,並且按照揹包思想,存在讓這個使用者接入和不讓這個使用者接入兩種決策,類比揹包,所以狀態轉移方程容易得到原始方程:
\[ dp[s][i][j]=max \{ dp[s][i-1][j-k]+dp[w][size_w][k]-cost[s][w](w \in son[s]) \} \]
\(dp[s][i][j]\)
最終二維的dp方程:
\[ dp[s][j]=max \{ dp[s][j-k]+dp[w][k]-cost[s][w](w \in son[s]) \} \]
192ms AC Code:
#include <cstdio> using namespace std; #define MAXN 3003 #define MAX(A,B) ((A)>(B)?(A):(B)) #define INF 0x3ffffff struct e{ int w,v,nxt; } edge[10000010]; //邊數一定要開大! int dp[MAXN][MAXN],head[MAXN],sz[MAXN]; int n,m,cnt_e; inline void adde(int u, int v, int w){ edge[++cnt_e].w=w; edge[cnt_e].v=v; edge[cnt_e].nxt=head[u]; head[u]=cnt_e; } int solve(int x, int fa){ if(x>=n-m+1&&x<=n) //是否為使用者端 return sz[x]=1; for(register int i=head[x];i;i=edge[i].nxt){ //”遞增”遍歷兒子i sz[x]+=solve(edge[i].v, x); //樹形dp通常自下而上更新 for(register int j=sz[x];j>=0;--j) //列舉狀態 for(register int k=0;k<=sz[edge[i].v];++k) //列舉決策,當前兒子取幾個使用者 dp[x][j]=MAX(dp[x][j], dp[x][j-k]+dp[edge[i].v][k]-edge[i].w); //dp[s][i][j]=max{dp[s][i-1][j-k]+dp[w][size_w][k]-cost[s][w]} 原始狀態轉移方程 } return sz[x]; } int main() { scanf("%d %d", &n, &m); for(register int i=0;i<=n;i++) for(register int j=0;j<=m;j++) dp[i][j]=-INF; for (int i=1;i<=n;i++) dp[i][0]=0; //注意初始化! for(int i=1;i<=n-m;++i){ int sz; scanf("%d", &sz); for(int j=1;j<=sz;++j){ int a,c; scanf("%d %d", &a, &c); adde(i, a, c); //鏈式前向星建邊 } } for(int i=n-m+1;i<=n;++i) scanf("%d", &dp[i][1]); solve(1,0); for(register int i=m;i>=0;--i) //遞減遍歷找到第一個合法值 if(dp[1][i]>=0){ printf("%d\n", i); break; } return 0; }
需要注意:
- 鏈式前向星邊數不是\(N\),一定要開大
- dp的遍歷順序一定要正確,因為你已經優化了一維\(i\)
- 使用者端支付的錢\(w\)應該表示為\(dp[n][1]=w\)
- dp一定要結合dp方程初始化