1. 程式人生 > >poj 1155 tele

poj 1155 tele

題目大意:

這道題的大意就是有N個節點,1號節點為發射站,2-n-m 是中途的傳遞站,n-m+1到n為使用者,使用者收到電視訊號就會付出一定的錢,其中每條邊有邊權,代表傳遞的花費,

問在不虧本的情況下最多能給多少個使用者傳送訊號。

題目分析:

這道題就是在樹上揹包,用Dp[i][j]表示以i為根節點管理j個使用者所得到的最大收益。

接下來處理細節,怎麼知道管理的使用者的個數,用一個num陣列標記,使用者設為1,傳遞站設為0,在dfs求解子樹的過程中進行更新,num[root]=∑num[son];

關於使用者付出的金錢,直接用dp[i][0]表示,方便dp的時候使用;

那麼dp方程就是:

dp[root][j+k]=max(dp[root][j+k],dp[root][j]+dp[son][k]);這就是一個揹包;

我們採用正推的方式更新。

但是我們需要思考一個問題,在dp更新的時候可能會出現一些錯誤,例如在更新的過程中dp[root][j]可能是已經使用過這棵son子樹了,而我們的j是在除當前son子樹以外的使用者數,所以我們需要用一個臨時陣列tem提取出來。

最後輸出最大的i使得dp[1][i]大於0;

如下程式碼:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 3100
#define INF 0x7fffffff
using namespace std;
int n,m;
int dp[N][N],head[N],a,b,num[N],len,k,tem[N];
struct Node
{
    int now,next,w;    
}edge[N*2];
void dfs(int root,int fa)
{
    int p;
    for(int i=head[root];i!=-1;i=edge[i].next)
    {
        p=edge[i].now;
        if(p==fa) continue;
        dfs(p,root);
        for(int j=0;j<=num[root];j++)
            tem[j]=dp[root][j];
        for(int j=0;j<=num[root];j++)
            for(int k=1;k<=num[p];k++)
            {
                dp[root][j+k]=max(dp[root][j+k],tem[j]+dp[p][k]-edge[i].w);    
            }
        num[root]+=num[p];
    }    
    
}
void addedge(int u,int v,int W)
{
    edge[len].now=v;
    edge[len].next=head[u];
    edge[len].w=W;
    head[u]=len++;    
}
int main()
{
    scanf("%d%d",&n,&m);
    len=0;
    memset(head,-1,sizeof(head));
    for(int i=1;i<=n-m;i++)
    {
        num[i]=0;
        scanf("%d",&k);
        for(int j=1;j<=k;j++)
        {
            scanf("%d%d",&a,&b);
            addedge(i,a,b);    
        }
    }
    for(int i=1;i<=n;i++)
       for(int j=1;j<=m;j++)
       dp[i][j]=-INF;
    for(int i=n-m+1;i<=n;i++)
    {
       num[i]=1;
       scanf("%d",&dp[i][1]);
    }
    dfs(1,-1);
    for(int i=m;i>=0;i--)
    {
        if(dp[1][i]>=0)
        {
            printf("%d\n",i);
            break;    
        }
    }
    //while(1);
    return 0;
     
    
}