luogu 1273有线电视网
某收费有线电视网计划转播一场重要的足球比赛。他们的转播网和用户终端构成一棵树状结构,这棵树的根结点位于足球比赛的现场,树叶为各个用户终端,其他中转站为该树的内部节点。
从转播站到转播站以及从转播站到所有用户终端的信号传输费用都是已知的,一场转播的总费用等于传输信号的费用总和。
现在每个用户都准备了一笔费用想观看这场精彩的足球比赛,有线电视网有权决定给哪些用户提供信号而不给哪些用户提供信号。
写一个程序找出一个方案使得有线电视网在不亏本的情况下使观看转播的用户尽可能多。
输入格式:
输入文件的第一行包含两个用空格隔开的整数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的费用。最后一行依次表示所有用户为观看比赛而准备支付的钱数。
输出格式:
输出文件仅一行,包含一个整数,表示上述问题所要求的最大用户数。
这题网上的题解大多不清不楚,并没有给出真正的复杂度,仅说有很多状态无法达到,就敷衍了事。
先说做法,相信各位都能一眼秒出n3的树上dp,第i个点有j个客户的最小花费(可为负)。
n2状态,O(n)转移。
那么正解呢?
正解就是证一下上面那个方法的复杂度。
各位可以尝试去构造一个卡掉这个方法的数据,然后就会发现,单次的最高复杂度是n2/4,但单次复杂度高的代价是整体的复杂度也是n2/4。
没错,这道题每个节点如果它的子节点多,使得当前这次操作的复杂度高,则它不能对其他点产生贡献,且它的所有子节点将合并,无法再次产生n2级别的贡献(如果产生了,说明这次操作复杂度远远小于n2,相对可以忽略不计),同时,对于一个固定的父节点,固定它的size,我们可以发现,设size为k,有q个子树,第i个子树size为s[i],贡献为s[1]*s[2]+(s[1]+s[2])*s[3]+(s[1]+s[2]+s[3])*s[4]+..... 且s[i]之和小于等于size父节点。
如果子树相同,有q个子树,父节点的size为k
贡献为(q-1)*((k/q)2) (q>=2),显然q=2时最优,且这个最优子结构会带来k2/4的贡献。
感受一下
#include<bits/stdc++.h> #define inf 0x3f3f3f3f using namespace std; int cnt,dp[3005][3005],n,m,head[3006],u[3005]; struct edge{int next,to,val;}e[8000]; inline void insert(int fr,int to,int val){e[++cnt].to=to;e[cnt].next=head[fr];head[fr]=cnt;e[cnt].val=val;} int dfs(int p) { dp[p][0]=0;int s,g=0; for(int i=head[p];i;i=e[i].next){ if(e[i].to>n-m) { e[i].val-=u[e[i].to]; g++; for(int j=g;j>=0;j--){ dp[p][j]=min(dp[p][j],dp[p][j-1]+e[i].val); } continue; } s=dfs(e[i].to);g+=s; for(int j=g;j>=0;j--) for(int k=0;k<=s;k++){ dp[p][j]=min(dp[p][j],dp[e[i].to][k]+dp[p][j-k]+e[i].val); } } return g; } int main(){ memset(dp,0x3f,sizeof(dp)); scanf("%d%d",&n,&m);int q,a,b; for(int i=1;i<=n-m;i++){ scanf("%d",&q); for(int j=1;j<=q;j++){ scanf("%d%d",&a,&b); insert(i,a,b); } } for(int i=1;i<=m;i++)scanf("%d",&u[n-m+i]); dfs(1);int ma=0; for(int i=n;i>=0;i--) { if(dp[1][i]<=0) { ma=i;break; } } printf("%d\n",ma); return 0; }