Crystalfly(代码源div.1 P1)

博主分享了在南京站铜牌赛中解决树上抓蝴蝶问题的经历,通过深度分析和策略优化,探讨了如何在蝴蝶消散时限内最大化收获。关键步骤包括设置sum[]和f[]数组,理解节点策略(直接下走和利用t[i]==3的特性),以及优化算法避免O(n^2)复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

随便讲点

本来是想所有题目做完后一次性写一篇的,没想到div.1div.1div.1那么狠,很多题虽然知道思路但总是AAA不了,总而言之还是自己太菜了。

这题是南京站的铜牌题,赛场上面感觉自己差点写出来,后来看一下其实还是有点差距的,如果当时能再多给我一个小时调代码还是有可能的。或许是因为赛场上面花了太多的时间和精力,后来补这题时候多少有点心理阴影,写了很久都写不出来。

题意

给你一棵树,这棵树上面有很多个节点,每个节点上面有很多蝴蝶,当你走到一个节点时候你将立即获得这个节点上面所有的蝴蝶,但另一方面来说,当你走到一个节点时候周围的蝴蝶将在t[i](t[i]<=3)t[i](t[i]<=3)t[i](t[i]<=3)的时间内消失。请问最多能够抓到多少蝴蝶?

题解

首先明确一些简单的定理:

  1. t[i]==3t[i]==3t[i]==3时候,我们到达一个点的父节点的时候我们还可以先去一次它的兄弟节点,然后再去它
  2. t[i]==1t[i]==1t[i]==1t[i]==2t[i]==2t[i]==2本质相同,都只能直接向下走。

接下来是全部的分析过程

先设定两个数组sum[],f[]sum[],f[]sum[],f[],分别代表当前节点所有子节点的fff值之和以及当前子树根节点消散但子节点没有消散的情况下能够获得的最大权值。

当我们走到一个节点时候我们有两种策略

  • 直接向下走

    这种情况相对简单,直接给出公式f[x]=max(sum[x]+w[son])f[x]=max(sum[x]+w[son])f[x]=max(sum[x]+w[son])

  • 先试探一个子节点然后进入一个t[x]==3t[x]==3t[x]==3的节点

    先给出公式f[x]=max(sum[x]−f[son1]+w[son1]+sum[son1]+w[son2])f[x]=max(sum[x]-f[son1]+w[son1]+sum[son1]+w[son2])f[x]=max(sum[x]f[son1]+w[son1]+sum[son1]+w[son2])其中t[son2]==3,son1!=son2t[son2]==3,son1!=son2t[son2]==3,son1!=son2

    sum[x]sum[x]sum[x]:我们不同多解释;

    −f[son1]+w[son1]+sum[son1]-f[son1]+w[son1]+sum[son1]f[son1]+w[son1]+sum[son1]:被我们尝试进入son1son1son1然后我们取了它的权值惊动了它的子节点。

    w[son2]w[son2]w[son2]:我们尝试进入son2son2son2并接着往下走。

    但是很明显的是,这样做无疑是会时间超限的,因为我们枚举了两个不同的节点,时间复杂度可能会被卡到O(n2)O(n^2)O(n2)

    为了优化算法,我们可以选择,求出w[son2]w[son2]w[son2]的最大值和次大值,然后枚举son1son1son1,当son1==son2maxson1==son2_{max}son1==son2max时候我们选择使用次大值作为w[son2]w[son2]w[son2]

const int N=1e5+10;
vector<vector<int>> tree;
int sum[N],f[N];
int w[N],t[N],n;
void INIT(){
    tree.clear();
    for(int i=0;i<=n+5;i++) sum[i]=f[i]=0;
    return;
}
void dfs(int fa,int x){
    int maxi1=-inf,maxi2=-inf;
    int maxid1=0,maxid2=0;
    for(auto v:tree[x]){
        if(v==fa) continue;
        dfs(x,v);
        sum[x]+=f[v];
        if(t[v]==3) if(w[v]>=maxi1) maxi2=maxi1,maxi1=w[v],maxid2=maxid1,maxid1=v;
        else if(w[v]>maxi2) maxi2=w[v],maxid2=v;
    }
    for(auto v:tree[x]){
        if(v==fa) continue;
        f[x]=max(f[x],sum[x]+w[v]);
        if(v==maxid1) f[x]=max(f[x],sum[x]-f[v]+w[v]+sum[v]+maxi2);
        else f[x]=max(f[x],sum[x]-f[v]+w[v]+sum[v]+maxi1);
    }
    return ;
}
void MAIN(){
    cin>>n;
    tree.resize(n+1);
    for(int i=1;i<=n;i++) cin>>w[i];
    for(int i=1;i<=n;i++) cin>>t[i];
    for(int i=1,u,v;i<n&&cin>>u>>v;i++) tree[u].push_back(v),tree[v].push_back(u);
    dfs(-1,1);
    cout<<(f[1]+w[1])<<endl;
    return ;
}
AC main(){
#ifdef DEBUG
    freopen("/Users/chenzhiyuan/Desktop/ACM/ACM/in.txt", "r", stdin);
    freopen("/Users/chenzhiyuan/Desktop/ACM/ACM/out.txt", "w", stdout);
    unsigned long start=clock();
#endif
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    INI();
    int _=1;
    cin>>_;
    while(_--){
        INIT();
        MAIN();
    }
#ifdef DEBUG
    unsigned long end=clock();
    cout<<(end-start)/1000<<"ms"<<endl;
#endif
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值