日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

2021.8.12携程笔试第三题:建树游戏DFS

發布時間:2023/12/31 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 2021.8.12携程笔试第三题:建树游戏DFS 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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; }

總結

以上是生活随笔為你收集整理的2021.8.12携程笔试第三题:建树游戏DFS的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。