2021.8.12携程笔试 |
在做最后一题的时候把题意看错了,悔之莫及,故记录此文引以为戒!
建树游戏
问题描述
有n个节点和n-1条边,形成一棵树,每个节点有一个权值。把其中一条边删除就形成了两棵树,在两棵树之间重新接一条新的边就可以形成一颗新树。新树的权值等于新增边的两点权值相乘。
每条边都可以删除,且可新加的边有很多,故可以形成很多新树,请计算这些新树的数量;同时对于每一条边,删除后可以产生的若干新树的权值之和也不一定相同,请计算这些权值之和中的最大值。
输入描述
第一行整数n,表示点的数量,3⩽ n ⩽100000
第二行n-1个整数,空格隔开,第i个整数ai表示点ai与i之间有一条边
第三行n个整数,空格隔开,表示各个点的权值。0<权值<10000。
输出描述
一行,两个整数,用空格隔开,表示新树的总数量,以及各点删除后可以产生的新树的权值之和的最大值。
样例输入
3
2 3
1 2 3
样例输出
2 3
问题分析
首先简化问题,如果断开一条边,能产生多少种新树呢:答案是这条边两端节点数之积减去原来的边,即两两组合。
同时,能产生的所有新树的权值之和呢:答案这条边两端所有权值之积j减去原先连接的边即可。
那么我们只需DFS一遍,遍历所有的边即可。每个子节点对应一颗子树,将父节点与子节点之间的边断开就可以形成两棵树,然后更新答案。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int n;
ll a[N],tot;
vector<int>g[N];
ll ans1,ans2;
void init()
{
tot=ans1=ans2=0;
a[0]=0;
for(int i=1; i<=n; i++)
g[i].clear();
}
struct node
{
ll cnt,tmpMax,tmptot;
};
node dfs(int pre,int u) // 求所有新树权值之和的最大值
{
long long cnt=1;
long long tc=a[u];
int len=g[u].size();
node tmp;
for(int i=0;i<len;i++)
{
int v=g[u][i];
if(v==pre)continue;
tmp = dfs(u,v);
cnt+=tmp.cnt;
tc+=tmp.tmptot;
ans1+=(n-tmp.cnt)*tmp.cnt-1;
ans2=max(ans2,(tot-tmp.tmptot)*tmp.tmptot-a[u]*a[v]);
// cout<<v<<" "<<tmp.tmptot<<endl;
}
tmp.cnt=cnt,tmp.tmptot=tc;
return tmp;
}
int main()
{
int x;
while(~scanf("%d",&n))
{
init();
for(int i=1; i<n; i++)
{
scanf("%d",&x);
g[i].push_back(x);
g[x].push_back(i);
}
for(int i=1; i<=n; i++)
{
scanf("%lld",&a[i]);
tot+=a[i];
}
dfs(0,1);
cout<<ans1<<" "<<ans2<<endl;;
}
return 0;
}
/*
3
2 3
1 2 3
4
4 1 2
1 1 2 3
*/
问题变形
在读题的时候没看到之和,而误以为是求所有新树的权值的最大值。即每棵新树对应一个权值,求所有可能的新树权值的最大值。
如果是这种题意该如何解答呢,问题相当于任意两个原本不连接的点的权值之积最大。
这个问题也可以通过DFS来解决。
我们只需找出除父节点之外其余节点的最大值即可,与当前点进行连接。
如何理解呢:
- 首先要明白父节点和当前子节点代表两颗新树
- 因为答案中肯定存在两点相连,并且在DFS的时候任意两点肯定有遍历的先后,并且我们需要使得这两点边权尽量大,这样使得乘积尽量大。
- 我们无需同时考虑两点,我们只需更新遍历过的所有点除父节点外的最大权值,那么在遍历当前点的时候,当前点就是被连接的点,当前子节点代表的子树还未被遍历。
- 即固定当前点,我们只需找到除父节点外,父节点所代表的子树的所有权值最大值。
- 父节点所代表的子树会先被遍历,如果父节点那边存在未遍历的点怎么办呢,还是原来的问题,两个点会存在先后遍历的顺序,当我们遍历到被连接的点的时候,其他可能的点会先被遍历到
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e5+10;
int n;
ll a[N],tot;
vector<int>g[N];
ll ans1,ans2;
void init()
{
tot=ans1=ans2=0;
a[0]=0;
for(int i=1; i<=n; i++)
g[i].clear();
}
struct node
{
ll cnt,tmpMax,tmptot;
};
node dfs1(int pre,int u,ll preMax) // 求所有新树权值的最大值
{
int len=g[u].size();
long long cnt=1;
node tmp;
for(int i=0; i<len; i++)
{
int v=g[u][i];
if(v==pre)
continue;
ans2=max(ans2,preMax*a[v]);
tmp=dfs1(u,v,max(preMax,a[u]));
ans1+=(tmp.cnt*(n-tmp.cnt))-1;
cnt+=tmp.cnt;
preMax=max(preMax,tmp.tmpMax); // 其他子树下的最值
}
tmp.cnt=cnt;
tmp.tmpMax=max(preMax,a[u]);
// cout<<u<<" "<<cnt<<endl;
return tmp;
}
int main()
{
int x;
while(~scanf("%d",&n))
{
init();
for(int i=1; i<n; i++)
{
scanf("%d",&x);
g[i].push_back(x);
g[x].push_back(i);
}
for(int i=1; i<=n; i++)
{
scanf("%lld",&a[i]);
tot+=a[i];
}
dfs(0,1);
cout<<ans1<<" "<<ans2<<endl;;
}
return 0;
}
/*
3
2 3
1 2 3
4
4 1 2
1 1 2 3
*/
这种类型的题目与POJ3140有些类似
POI 3140 Contestants Division |
问题描述
给你一棵树,要求你选择一条边进行断开,使得断开后两棵子树权值和之差最小。
问题分析
这个题解法很简单,DFS统计子树权值之和,然后计算差值更新答案。
但是这题坑点很多,首先数据范围,在long long的数据范围,而且差值绝对值不能用abs()
函数对 long long 取绝对值!应该用llabs(long long )
函数原型,或者取负号,求正负最值。
整型类型变量(整数)取绝对值:
int abs( int x );
long int labs( long x );
long long int llabs( long long x );
浮点类型变量(小数)取绝对值:
double( double x );
float fabsf(float x);
long double fabsl( long double x) ;
相关头文件:
#include <stdlib.h> // #include <cstdlib>
#include <math.h> // #include <cmath>
另外一个坑点在于m虽然很大,但是实际一棵树只与n有关,m是误导信息,不过内存稍微开大几倍即可。
const int N=1e5+10;
struct Edge
{
int to,next;
}e[N*20];
int n,m,head[N],tot;
ll sum,ans,a[N];
void init()
{
ans=1e15;
sum=tot=0;
memset(head,-1,sizeof(head));
}
void add(int u,int v)
{
e[tot].to=v,e[tot].next=head[u];
head[u]=tot++;
}
ll dfs(int pre,int u)
{
ll tmp=a[u];
for(int i=head[u];i+1;i=e[i].next)
{
int v=e[i].to;
if(v==pre) continue;
ll cnt=dfs(u,v);
ans=min(ans,llabs(cnt-(sum-cnt))); // ans=min(ans,max(cnt-(sum-cnt),(sum-cnt)-cnt));
tmp+=cnt;
}
return tmp;
}
int main()
{
int u,v;
int cnt=0;
while(~scanf("%d%d",&n,&m)&&(n||m))
{
init();
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
sum+=a[i];
}
for(int i=0;i<m;i++)
{
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
dfs(0,1);
// cout<<ans<<endl;
printf("Case %d: %lld\n",++cnt,ans);
}
return 0;
}