CodeForces - 767C. Garland

>>原题链接

题意:

给出一组树形链接的(花环)彩灯,有且仅有一个特定的点为根节点,其余每个节点有且仅有一边与父节点相连,总边数等于n-1。每个彩灯在一定环境的亮度与其被设定的系数(有正负)有关。将这组彩灯分为三组,要求三组彩灯的总亮度相等。即,每组内彩灯的系数和相等。

    输出要分出来的两个节点,多解输出任意,无解输出-1。


解题思路:

根据题意,这是一个图的搜索问题,要求找到合适的分割点,也就是寻找子树满足系数和等于三分之一的总系数(sum)。所以我们可以先用一个数组(temp[i]),储存以每个节点为根的子树的总系数,然后再搜索temp[i]等于sum/3倍数的点。

为什么是倍数?

    例举一个比较粗糙的情况,假如彩灯串成这样:1(3)->2(3)->3(3) 

    括号里是系数,经过处理,temp数组里会是这样的:1(9)、2(6)、3(3) 

    此时sum/3=3,我们用3去搜索,就会发现漏解了。具体的解决方法见代码实现。


代码实现:

#include<cstdio>
#include<vector>
using namespace std;
const int maxn=1e6+5;
vector<int> ed[maxn];//因为问题不是很复杂,所以采用向量来存边
int temp[maxn],a[maxn],vis[maxn];//temp储存结点系数,a储存父结点,vis储存某个结点所在子树里符合条件结点的数量。
int key,sum,n,root,num;

int dfs(int f)
{
    int cnt=0;//f节点为根节点的树上满足条件的节点的数量

    for(int i=0;i<ed[f].size();i++)
    {
        cnt+=dfs(ed[f][i]);//累加满足条件的节点数量
        temp[f]+=temp[ed[f][i]];//累加总系数
    }

    if(temp[f]==(cnt+1)*key)//如果总系数是key的相应倍数,++cnt记录到vis
        vis[f]=++cnt;//这样做的好处是前面子树上如果已经有了满足条件的节点,这里的比较就会提升倍数,减少重判与误判。

    return cnt;
}

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%d %d",&a[i],&temp[i]);
        sum+=temp[i];
        if(a[i]==0)root=i;
        ed[a[i]].push_back(i);//建单向边,虽然是无向图,但是本题目搜索等值只要父到子搜就好了,可以减少时间。其他问题要另外考虑。
    }

    if(sum%3!=0)return printf("-1")&&0;//sum不是3的倍数,不能实现要求。

    key=sum/3;
    num=dfs(root);//记录深搜到的符合条件的节点的数量
    if(num<3)return printf("-1")&&0;//找到的点(包括根节点)的数量不足3个,不能实现要求。

    for(int i=1,j=2;i<=n;i++)
    {
        if(vis[i]==2||vis[i]==1)
        {
            j--;
            printf("%d ",i);
            if(!j)break;
        }
    }

    return 0;
}

解题后记:

    因为前辈之前给了链式前向星的解法代码蛮长的,我感觉自己理解有些吃力···后来也是去网上参考了一些题解,然后在自己试了好多遍,写出了这个相对比较简明的代码。我也是多想了一步才发现,改成有向图给我省了200ms多的时间,所以感觉蛮开心的,于是忍不住写了这篇其实没什么必要的题解。就当一次巩固吧 (。・∀・)ノ

    其次我本人对这类问题的熟练度还有待加强,如果发现什么错误的地方,欢迎各位看官大佬指出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值