题目大意:给定一个n个节点的树,1是信号发射塔,1到n-m是中继发射塔,n-m+1 到n是用户,每个用户都会交钱,任意两个节点传输信号都是需要钱的,每个用户最多上交的钱已给定,问在不亏本的情况下,最多有多少用户。
初看题目,研究了下样例,发现每个用户上交的钱在满足自身需求同时还有可能用到其他线路上。
所以我一开始的DP方法是这样打算的,记录下所有用户上交的总钱数 sum,用dp[rt][j]表示以rt为根的树,在分配了j这么多钱之后,最多能有几个用户,写着写着发现不对劲,这样是初始就认为有sum的钱,然后把sum的钱统一分配,明显错了,因为能收到用户的钱,前提是一定要接通它这条线路,。。又试想了改进方法,还是有点问题的样子
后来博客了下,找到另一种dp思想,dp[rt][j],是指在rt为根的树,有j个点联通的最大盈余,初始状态,所有点的dp信息都设置为-INF,除了叶子节点(即用户),用户会上交钱,而且叶子下面没有树了,开销为0,故叶子的值dp[rt][1]即他们上交的钱,接下来就是简单的DP了,枚举dp[rt][j]=max(原值,dp[rt][j-w]+dp[nx][w]-联通代价);
坑爹的是这个题卡时间卡得超级变态啊。。。简直变态啊!!我用vector超时,那就算了,我能理解,换了邻接链表依然超时,TM,原来在遍历的时候,要记录好各个点的子节点的量,只枚举这部分才会不超时。。即便AC的,时间也跑了250MS。简直卡时间变态啊,从头开始枚举都不行。。。我刚查了下POJ的status,有大神0ms还有15ms过得。。。。我简直被打击了啊啊啊啊啊
#include <iostream> #include <cstdio> #include <cstring> #define N 3010 struct node{ int v; int next; } e[N]; int lists[N]; int G[N][N];//记录每两个节点的联通代价 int val[N]; int dp[N][N]; int n,m; int cnt; int sum[N]; void dfs(int rt) { sum[rt]=1; dp[rt][0]=0; int i,j; for (i=lists[rt];i!=-1;){ int nx=e[i].v; i=e[i].next; dfs(nx); sum[rt]+=sum[nx]; for (j=sum[rt];j>=1;j--){ for (int w=1;w<=sum[nx]&&w<=j;w++) { if (dp[rt][j]<dp[rt][j-w]+dp[nx][w]-G[rt][nx]) dp[rt][j]=dp[rt][j-w]+dp[nx][w]-G[rt][nx]; } } } } void add(int a,int b) { e[cnt].v=b; e[cnt].next=lists[a]; lists[a]=cnt++; } int main() { while (scanf("%d%d",&n,&m)!=EOF) { cnt=0; memset(lists,-1,sizeof lists); int i,j,k; for (i=1;i<=n-m;i++) { scanf("%d",&j); for (k=1;k<=j;k++){ int a,b; scanf("%d%d",&a,&b); G[i][a]=b; add(i,a); } } for (i=0;i<=n;i++) { for (j=0;j<=n;j++) dp[i][j]=-10000000; } for (i=n-m+1;i<=n;i++) { scanf("%d",&val[i]); dp[i][1]=val[i]; } dfs(1); for (i=n;i>=0;i--){ if (dp[1][i]>=0){//只有大于0说明这种i方案可行,输出 printf("%d ",i); break; } } } return 0; }