atcoder agc002 D[克魯斯卡爾重構樹+倍增]

D - Stamp Rally


Time limit : 2sec / Memory limit : 256MB

Problem Statement

We have an undirected graph with N vertices and M edges. The vertices are numbered 1 through N, and the edges are numbered 1 through M. Edge i connects vertices ai and bi. The graph is connected.

On this graph, Q pairs of brothers are participating in an activity called Stamp Rally. The Stamp Rally for the i-th pair will be as follows:

  • One brother starts from vertex xi, and the other starts from vertex yi.
  • The two explore the graph along the edges to visit zi vertices in total, including the starting vertices. Here, a vertex is counted only once, even if it is visited multiple times, or visited by both brothers.
  • The score is defined as the largest index of the edges traversed by either of them. Their objective is to minimize this value.

Find the minimum possible score for each pair.

Constraints

  • 3≤N≤105
  • N−1≤M≤105
  • 1≤ai<bi≤N
  • The given graph is connected.
  • 1≤Q≤105
  • 1≤xj<yj≤N
  • 3≤zj≤N

Input

The input is given from Standard Input in the following format:

N M
a1 b1
a2 b2
:
aM bM
Q
x1 y1 z1
x2 y2 z2
:
xQ yQ zQ

Output

Print Q lines. The i-th line should contain the minimum possible score for the i-th pair of brothers.


Sample Input 1

5 6
2 3
4 5
1 2
1 3
1 4
1 5
6
2 4 3
2 4 4
2 4 5
1 3 3
1 3 4
1 3 5

Sample Output 1

1
2
3
1
5
5


以上原題


題目大意

給出一張無向連通圖,每次詢問X,Y,Z,對於每次詢問使得從X,Y兩點出發按邊走滿Z個不同點(包括X,Y)所經過的邊的最大值最小,輸出這個最小的最大值。


題解

基本上的題解都是整體二分+并查集,然而我不會整體二分,於是就有了克魯斯卡爾重構樹+倍增的想法。

首先我們要到達某一點並且經過邊權最小,這顯然是最小生成樹上的邊。然後又有經過邊權大小的限制,很容易想到克魯斯卡爾重構樹。最大最小一般想到二分答案,而重構樹又滿足單調性,可以二分答案。為了加速在樹上的查找可以在樹上倍增。

具體做法:首先建好克魯斯卡爾重構樹,再dfs得到每個點為根的子樹下的原節點個數倍增父親,對於每次詢問我們二分答案,判定時倍增上跳到能到達的最淺的祖先,比較兩個點跳到的父親(若是同一點不要重複算)所含的原節點個數是否大於等於Z。

雖然多了一個log,但實測還可以,可以把題目搞成強制在線,嘿嘿嘿。


代碼

#include <bits/stdc++.h>
using namespace std;
const int N=5e5;

#define C getchar()-48
int read()
{
  int s=0,t=1,k=C;
  for (;k<0||9<k;k=C) if (k==-3) t=-1;
  for (;0<=k&&k<=9;k=C) s=(s<<1)+(s<<3)+k;
  return s*t;
}

  int n,m;
  struct hh
    {
      int x,y,n;
    }a[N];
void in()
{
  cin>>n>>m;
  for (int i=0;++i<=m;)
    a[i]=(hh){read(),read(),i};//個人習慣
}

int cmp(hh a,hh b)
{
  return a.n<b.n;
}

  struct zz
    {
      int r,v;
    }b[N];
int gf(int x)
{
  return x^b[x].r ? b[x].r=gf(b[x].r) : x;
}   
  
  int t;
  int h[N];
  struct gg
    {
      int n,r;
    }e[N];
void ins(int x,int y)//連樹邊
{
  e[++t]=(gg){h[x],y};h[x]=t;
}

  int R;
void rek()//重構樹
{
  sort(a+1,a+m+1,cmp);
  for (int i=0;++i<=n;)
    b[i]=(zz){i,0};
  R=n;
  for (int i=0;++i<=m;)
    {
      int x=gf(a[i].x),
          y=gf(a[i].y);
      if (x==y) continue;
      b[++R]=(zz){R,a[i].n};
      ins(R,x);ins(R,y);
      b[x].r=b[y].r=R;
    }
  b[0].v=N;//細節,邊界
}

  int s[N];
  int f[N][20];
#define P(i) e[i].r
void dfs(int k,int F)//處理 原節點個數 和 倍增父親
{
  f[k][0]=F;
  for (int i=0;++i<20;)
    f[k][i]=f[f[k][i-1]][i-1];
  for (int i=h[k];i;i=e[i].n)
    dfs(P(i),k),
	s[k]+=s[P(i)];
  if (k<=n) s[k]=1;
}

void wor()
{
  dfs(R,0);
}

int get(int v,int x)//向上跳
{
  for (int i=20;~--i;)
    if (b[f[x][i]].v<=v)
      x=f[x][i];
  return x;
}

int que()
{
  int A=m;
  int x=read(),y=read(),z=read();
  for (int i=20;~--i;)//二分答案。個人喜歡寫倍增。
    {
      int p=get(A-(1<<i),x),
          q=get(A-(1<<i),y);
      if ((p!=q&&s[p]+s[q]>=z)||s[p]>=z)
        A-=1<<i;
    }
  return A;
}

void sol()
{
  for (int Q=read();Q;--Q)
    printf("%d\n",que());
}

int main()
{
  in();
  rek();
  wor();
  sol();
  exit(0);
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值