题意:
给出一组树形链接的(花环)彩灯,有且仅有一个特定的点为根节点,其余每个节点有且仅有一边与父节点相连,总边数等于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多的时间,所以感觉蛮开心的,于是忍不住写了这篇其实没什么必要的题解。就当一次巩固吧 (。・∀・)ノ
其次我本人对这类问题的熟练度还有待加强,如果发现什么错误的地方,欢迎各位看官大佬指出。