题目
解说
刚看完这道题感觉还是挺乱的,可能那时候脑子不太清醒,一度觉得自己又要重拾Tarjan了。当然最后还是发觉应该用树形DP。
(以下dp[u]代表以u为根的包括自己在内的子树的最大利润,bool g[u]表示u及其子树的方案数是否唯一,唯一则为0,否则为1,t[u]代表u的次数,v[u]代表u的价值)
计算最大利润确实挺简单。有点像之前做过的空调教室,但是多了次数限制和负数,但这不难处理。计算u的时候因为每个儿子在走完之后必须返回u来回到根节点,因此我们只能对儿子的dp值进行排序,选取前t[u]-1个,并且遇到负数立即停止因为接下来选的负数只能使总价值变小。
那么怎么处理方案是否唯一呢?
我们开一个bool数组g表示u及其子树的方案数是否唯一。显然,只有以下3种情况下方案数不唯一:
某个取得的儿子dp值为0(选不选都可以)
某个取得的儿子g值为1(在这颗子树中有不同的路径)
下个未选的儿子和最后选择的儿子f值相同(可以替换)(我写的时候忽略了这一点但是还是A了,大约是数据太弱了)
那么就从根开始递归一遍就可以了哦,最后看dp[1]和g[1]就可以了。
(部分实在想不上来的做法参考了DZN大佬的,谢谢啦~)
代码
#include<bits/stdc++.h>
using namespace std;
const int maxn=+,Inf=;
inline int read(){
int s=,w=;
char ch=getchar();
while(ch<''||ch>''){if(ch=='-')w=-;ch=getchar();}
while(ch>=''&&ch<='') s=s*+ch-'',ch=getchar();
return s*w;
}
int n,head[maxn],tot,v[maxn],t[maxn],dp[maxn];
bool g[maxn];
struct node{
int to,next;
}e[*maxn];
void Add(int a,int b){
e[tot].to=b;
e[tot].next=head[a];
head[a]=tot;
tot++;
}
void dfs(int x,int fa){
priority_queue<pair<int,int> > q;//按照dp值大小排序
for(int i=head[x];i;i=e[i].next){
int v=e[i].to;
if(v==fa) continue;
dfs(v,x);
q.push(make_pair(dp[v],v));
}
int num=,sum=,judge=;
while(!q.empty()&&num<t[x]-){
if(q.top().first<) break;//出现拖后腿的立即停止
if(q.top().first==){//有0说明方案不为1,
//且0后面要么是0要么是负的,都无法做贡献,直接退
judge=;
break;
}
sum+=q.top().first;
if(g[q.top().second]==) judge=;
q.pop();
num++;
}
dp[x]=sum+v[x];
g[x]=judge;
}
int main(){
tot=;
t[]=Inf;//注意自己家可以走无数次
n=read();
for(int i=;i<=n;i++) v[i]=read();
for(int i=;i<=n;i++) t[i]=read();
for(int i=;i<=n-;i++){
int x,y;
x=read(); y=read();
Add(x,y);
Add(y,x);
}
dfs(,);
printf("%d\n",dp[]);
if(g[]) printf("solution is not unique");
else printf("solution is unique");
return ;
}
幸甚至哉,歌以咏志。