一道校oj题

本文解析了一道关于树上动态规划(DP)与最小割的算法题,通过二次换根技巧,高效求解任意两点间距离之和,避免了网络流的复杂计算。

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

这是一道为了说明校赛数据范围的题,一共三题这是第三道【前两道就是a+b那种的】被卡了很久。
下周就是校赛了,希望自己这只小菜鸡可以成为预备役~

【题目描述】

给你一颗带权的n个点的无向树T,另外还有一个n个点的无向完全图G,G中u-v的边的流量等于T中两个点的距离请计算完全图中所有点对的最小割的和。其中,(u,v)和(v,u)算做一个点对
//不知道算不算侵犯了学长的版权。。。如果学长看到了。。。我就删掉

【思考】

之前从来没有接触过网络流,所以刚看到最小割是一脸懵逼的,然后百度了最小割最大流,然后搞了很久才搞明白样例
“边的流量等于T中两个点的距离”这句话的意思是,完全图上的边权是树上两个点最短的那条路径的边权之和
“点对的最小割”把一个点从完全图中孤立出来的最小割是最小的,而一个点对(u,v)就是看是孤立u还是孤立v得到的最小割最小
最后把任意两个点对的最小割求和即为所求

呼~把题目解释完了。

所以这题转化为:求任意一个点到所有点的距离之和,两两比较求得的这个和取小的那一个,也就是把和排序(从小到大),第i个被选了(n-i)次,所以这题的关键就是求任意一点到所有点的距离之和,可是n2的算法很显然超时,这题要用O(n)或者nlgn的算法

这时学长又提醒我们可以用dp做

二次换根,树上dp很常用的一个技巧学长说的
第一个dfs先求f[x],表示以x为根的子树,x到它所有子孙的距离之和,先dfs再计算(回溯时计算)
第二个dfs求dp[x],表示x到所有结点的距离之和,先计算再dfs(父节点先被计算)

显然,第一个dfs有用的地方在于它的根节点(可能还有计算出的它的子孙数目或者深度之类的也可以在第二次拿来用)
f[root]=dp[root]

然后就完事儿,和网络流一点关系都没有

这题还卡了ll,所以要用__int128,__int128需要自己手动写输入输出,而且进行计算的时候要强制转换不然会爆

贴上弱渣代码

#include <bits/stdc++.h>
using namespace std;
const int MAX=100000;
int cnt,head[MAX+5],sz[MAX+5],t,n;
__int128 f[MAX+5],dp[MAX+5],ans;
struct node{
 int v,w,next;
}edge[MAX*2+5];
void add_edge(int u,int v,int w){
 edge[++cnt].v=v;
 edge[cnt].w=w;
 edge[cnt].next=head[u];
 head[u]=cnt;
}
void dfs1(int uu,int fa){
 __int128 i,vv,ww;
 sz[uu]=1;
 for(i=head[uu];i!=-1;i=edge[i].next){
  vv=edge[i].v;
  ww=edge[i].w;
  if(vv==fa) continue;
  dfs1(vv,uu);
  sz[uu]+=sz[vv];
  f[uu]=(f[uu]+f[vv]+(__int128)(ww*sz[vv]));
 }
}
void dfs2(int uu,int fa){
    int i,vv,ww;
    if(uu==1) dp[uu]=f[uu];
 for(i=head[uu];i!=-1;i=edge[i].next){
     vv=edge[i].v;
     ww=edge[i].w;
     if(vv==fa) continue;
     dp[vv]=(dp[uu]-(__int128)((2*sz[vv]-n)*ww));
     dfs2(vv,uu);
 } 
} 
void _print(__int128 x){
 if(x>9) _print(x/10);
 putchar(x%10+'0');
}
void print(__int128 x){
 if(x<0){
  x=-x;
  putchar('-');
 }
 _print(x);
}
int main(){
 freopen("3.in","r",stdin);
 freopen("3.out","w",stdout);
 int i,u,v,w;
 scanf("%d",&t);
 while(t--){
  cnt=0;
  ans=0;
  memset(head,-1,sizeof(head));
  memset(sz,0,sizeof(sz));
  memset(f,0,sizeof(f));
  scanf("%d",&n);
  for(i=1;i<n;i++){
   scanf("%d%d%d",&u,&v,&w);
   add_edge(u,v,w);
   add_edge(v,u,w);
  }
     dfs1(1,-1);
     dfs2(1,-1);
  sort(dp+1,dp+1+n);
  for(i=1;i<=n;i++){
   ans+=((__int128)dp[i])*((__int128)(n-i));
  }
  print(ans); 
  printf("\n");
 }
 return 0;
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值