[树的直径] Codechef March Cook-Off 2018. Maximum Tree Path

计蒜之道图论题解
本文介绍了一道计蒜之道中的图论题目解法,通过枚举最大公约数(gcd),并使用并查集维护图中连通块的直径来求解。详细解释了如何对边进行排序和处理,以便于高效地更新答案。

这个套路好像是计蒜之道里的一题

考虑枚举gcd

把两端点都是gcd的倍数的边存下来,按照两段点较小值从大到小排序

枚举每一条边,把这条边加入图中,可以用并查集维护出所有联通块的直径,然后就好了

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
#define fi first
#define se second

using namespace std;

typedef long long ll;
typedef pair<int,int> pii;

const int N=200010;

inline char nc(){
  static char buf[100000],*p1=buf,*p2=buf;
  return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}

inline void read(int &x){
  char c=nc(); x=0;
  for(;c>'9'||c<'0';c=nc());for(;c>='0'&&c<='9';x=x*10+c-'0',c=nc());
}

int T,n,cnt,G[N],a[N];
struct edge{
  int t,nx,w;
}E[N<<1];

inline void addedge(int x,int y,int w){
  E[++cnt].t=y; E[cnt].nx=G[x]; E[cnt].w=w; G[x]=cnt;
  E[++cnt].t=x; E[cnt].nx=G[y]; E[cnt].w=w; G[y]=cnt;
}

vector<pii> ed[N];

ll cst[N];
int dpt[N],dfsl[N],p[N],t;

void dfs(int x,int f){
  dpt[x]=dpt[f]+1;
  dfsl[++t]=x; p[x]=t;
  for(int i=G[x];i;i=E[i].nx)
    if(E[i].t!=f){
      cst[E[i].t]=cst[x]+E[i].w;
      dfs(E[i].t,x);
      dfsl[++t]=x;
    }
}

#define Min(x,y) (dpt[x]<dpt[y]?x:y)

int lgt[N],st[N][20];

inline void Pre(){
  for(int i=1;i<=t;i++) lgt[i]=lgt[i-1]+((1<<lgt[i-1]+1)==i);
  for(int i=1;i<=t;i++) st[i][0]=dfsl[i];
  for(int k=1;k<=lgt[t];k++)
    for(int i=1;i+(1<<k)-1<=t;i++)
      st[i][k]=Min(st[i][k-1],st[i+(1<<k-1)][k-1]);
}

inline int lca(int x,int y){
  x=p[x]; y=p[y];
  if(x>y) swap(x,y);
  int t=lgt[y-x+1];
  return Min(st[x][t],st[y-(1<<t)+1][t]);
}

inline ll dist(int x,int y){
  return cst[x]+cst[y]-2*cst[lca(x,y)];
}

inline bool cmp(pii x,pii y){
  return a[x.fi]<a[y.fi];
}

int fa[N],l[N],r[N];

int getfa(int x){
  return x==fa[x]?x:fa[x]=getfa(fa[x]);
}

inline void Merge(int x,int y){
  x=getfa(x); y=getfa(y);
  if(x==y) return ;
  int u=l[x],v=r[x];
#define fix(x,y) if(dist(x,y)>dist(u,v)) u=x,v=y;
  fix(l[y],r[y]); fix(l[x],l[y]); fix(l[x],r[y]);
  fix(r[x],l[y]); fix(r[x],r[y]);
  fa[y]=x; l[x]=u; r[x]=v;
}

int main(){
  freopen("1.in","r",stdin);
  freopen("1.out","w",stdout);
  read(T);
  while(T--){
    read(n);
    for(int i=1;i<=n;i++) read(a[i]);
    for(int i=1,x,y,w;i<n;i++){
      read(x),read(y),read(w),addedge(x,y,w);
      if(a[x]>a[y]) swap(x,y);
      for(int j=1;j*j<=a[x];j++)
    if(a[x]%j==0){
      if(a[y]%j==0)
        ed[j].push_back(pii(x,y));
      if(a[y]%(a[x]/j)==0)
        ed[a[x]/j].push_back(pii(x,y));
    }
    }
    dfs(1,0); Pre();
    ll ans=0;
    for(int i=1;i<=10000;i++){
      if(!ed[i].size()) continue;
      sort(ed[i].begin(),ed[i].end(),cmp);
      for(int j=0;j<ed[i].size();j++){
    fa[ed[i][j].fi]=l[ed[i][j].fi]=r[ed[i][j].fi]=ed[i][j].fi;
    fa[ed[i][j].se]=l[ed[i][j].se]=r[ed[i][j].se]=ed[i][j].se;
      }
      int mn=1<<30; ll mx=0;
      for(int j=ed[i].size()-1;~j;j--){
    mn=min(mn,a[ed[i][j].fi]);
    Merge(ed[i][j].fi,ed[i][j].se);
    int u=getfa(ed[i][j].fi);
    mx=max(mx,dist(l[u],r[u]));
    ans=max(ans,mx*mn*i);
      }
      ed[i].clear();
    }
    printf("%lld\n",ans);
    for(int i=1;i<=n;i++) G[i]=dpt[i]=cst[i]=0;
    t=cnt=0;
  }
  return 0;
}
你提供的是一组使用 Clang 编译器工具链(LLVM)来编译 CUDA 风格代码的命令,目标是将 `dpc_t.cu` 编译成一个包含 GPU 二进制和主机端逻辑的可执行程序。我们逐条分析这些命令是否正确。 --- ### **步骤解析与问题分析** #### 1. 生成 LLVM IR(中间表示) ```bash clang++ -S -emit-llvm --cuda-gpu-arch=ivcore10 --cuda-device-only -std=c++11 -Wall -x ivcore dpc_t.cu -o dpc.ll ``` - `-S -emit-llvm`: 生成 LLVM IR 汇编代码。 - `--cuda-gpu-arch=ivcore10`: 假设这是你自定义的 GPU 架构名称(类似 sm_50 或 gfx906)。 - `--cuda-device-only`: 只处理设备端代码。 - `-x ivcore`: 表示输入文件是 CUDA 风格的 `.cu` 文件。 - ✅ 这个命令用于提取设备端的 LLVM IR,是正确的。 #### 2. 使用 llc 将 LLVM IR 编译为对象文件 ```bash llc -march=bi -filetype=obj dpc.ll -o dpc.cuda.o ``` - `-march=bi`: 指定目标架构为 bi(可能是你自定义的 backend)。 - `-filetype=obj`: 输出为对象文件。 - ✅ 正确,前提是 `bi` 是支持的后端,并能处理前面生成的 IR。 #### 3. 使用 lld 链接设备端对象文件为 cubin ```bash lld -flavor ld.lld --no-warn-missing-entry - -no-undefined dpc.cuda.o -o dpc.cubin ``` - `-flavor ld.lld`: 使用 LLD 的链接器。 - `--no-warn-missing-entry`: 不警告缺少入口点。 - `-no-undefined`: 不允许未定义符号。 - ⚠️ 问题:这里 `-` 参数似乎多余或错误,可能应删除。 - 否则整体是合理的,用于将设备端对象链接为 `.cubin`。 #### 4. 使用 fatbinary 打包 fatbin ```bash fatbinary --cuda --64 --image=profile=ivcore10,file=dpc.cubin --create dpc.fatbin ``` - `fatbinary` 是 NVIDIA 工具,用于打包多个 GPU 架构的目标。 - `--cuda --64`: 64位 CUDA 构建。 - `--image=...`: 指定构建配置和文件。 - ✅ 如果 `fatbinary` 支持你自定义的 `ivcore10` 架构,这个命令是正确的。 #### 5. 编译主机端代码并嵌入 GPU 二进制 ```bash clang++ -c -std=c++11 -Wall -x ivcore --cuda-host-only -Xclang -fcuda-include-gpubinary -Xclang dpc.fatbin dpc_t.cu -o dpc.o ``` - `--cuda-host-only`: 只处理主机端代码。 - `-Xclang -fcuda-include-gpubinary`: 嵌入 GPU 二进制到最终可执行文件中。 - ✅ 正确地将 GPU 二进制嵌入到主机端对象中。 #### 6. 编译 C++ 主程序 ```bash clang++ -c -std=c++11 -Wall -x c++ -I/usr/local/corex/include dpc.cpp -o dpc.cpp.o ``` - `-x c++`: 强制作为 C++ 编译。 - `-I/usr/local/corex/include`: 添加头文件路径。 - ✅ 标准的 C++ 编译步骤,没问题。 #### 7. 最终链接阶段 ```bash clang++ -std=c++11 -Wall -x ivcore -lcudart -L/usr/local/corex/lib64 dpc.o dpc.cpp.o -o dpc ``` - `-lcudart`: 链接 CUDA 运行时库。 - `-L/usr/local/corex/lib64`: 添加库搜索路径。 - ❗**问题**:缺少了 `-Xclang -fcuda-include-gpubinary` 参数,导致 GPU 二进制没有被正确链接进最终可执行文件。 - ❗**问题**:`-x ivcore` 在链接阶段意义不大,应该去掉。 --- ### ✅ 正确流程建议 以下是修正后的完整命令: ```bash # Step 1: 提取设备端 LLVM IR clang++ -S -emit-llvm --cuda-gpu-arch=ivcore10 --cuda-device-only -std=c++11 -Wall -x cuda dpc_t.cu -o dpc.ll # Step 2: 编译为设备端对象文件 llc -march=bi -filetype=obj dpc.ll -o dpc.cuda.o # Step 3: 链接设备端对象文件为 cubin lld -flavor ld.lld --no-warn-missing-entry -no-undefined dpc.cuda.o -o dpc.cubin # Step 4: 打包为 fatbin fatbinary --cuda --64 --image=profile=ivcore10,file=dpc.cubin --create dpc.fatbin # Step 5: 编译主机端代码并嵌入 GPU 二进制 clang++ -c -std=c++11 -Wall -x cuda --cuda-host-only -Xclang -fcuda-include-gpubinary -Xclang dpc.fatbin dpc_t.cu -o dpc.o # Step 6: 编译主程序 clang++ -c -std=c++11 -Wall -x c++ -I/usr/local/corex/include dpc.cpp -o dpc.cpp.o # Step 7: 最终链接 clang++ -std=c++11 -Wall -Xclang -fcuda-include-gpubinary -Xclang dpc.fatbin dpc.o dpc.cpp.o -lcudart -L/usr/local/corex/lib64 -o dpc ``` --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值