洛谷 P2680 运输计划 (二分,前向星,tarjan LCA,树上差分)

题目大意:

有一棵带权值的树,树上有n个节点,m条路径(树上任意两点的路径唯一)。 现在我们可以使得任意一条边路径长度为0,问我们修改哪条边可以使得最长路径最短。即:

min \ \underset{i} {max} \ d_i 其中d_i是每条边的距离。

解题思路:最大值最小优先考虑使用二分。这里的答案满足 false false true true结构。我们预处理每个路径的长度和LCA。接着我们每次在做二分枚举mid完成 check()函数,check()函数中看哪个路径的长度大于mid,大于mid我们把这条路径上的边权全部加一(原图的边权值先置为0),假设有cnt个路径的长度大于mid。最后我们讨论每条边的权值,若权值等于cnt(表明这cnt条路径都需要用到这条边),我们用最长路径减去这个边的权值,若结果小于 等于 mid返回true. 否则返回false.

这里需要用到一些知识,首先树上路径长度的预处理,LCA的处理。

这里我们使用前向星+tarjan LCA,前向星简介:

前向星就是一个存储图的结构,和邻接表这种结构是参不多的,网上很多讲的很抽象,这里我结合代码(前向星DFS)和我自己的理解来讲。首先我们最容意理解的一种结构是:

通过这种结构我们可以遍历从1号节点出来的所有节点,这应该是我们最常用的功能了(图的遍历以及各种图的算法都需要用到这种结构),现在我们看一下前向星怎么实现这种结构。

首先我们定义一种数据结构.

typedef struct{
    int dis,next,to;
}link;    
//前向星.

我们再通过增加边的代码,来理解前向星的结构。

扫描二维码关注公众号,回复: 10400333 查看本文章
///////////////////////
const int MAXN=1e5+10;
link gra[MAXN];
int head[MAXN];
int cnt=0;
void add_edge(int u,int v,int w){
    gra[++cnt].next=head[u];
    gra[cnt].to=v;
    gra[cnt].dis=w;
    head[u]=cnt;
}

首先我们要弄清楚两个关键变量,一个是cnt,一个是u。

cnt承载的信息是最丰富的,通过cnt信息,我们可以直到cnt代表的节点可以走去哪个节点(结构体link中的to决定),去这个节点的长度为多少(结构体中的dis决定),他的兄弟是谁(结构体中的next决定),那么我们怎么获取这个cnt信息呢?这里有一个关键的变量head,通过这个head,我们可以获取某个节点对应的cnt的信息。我们看一下对应的DFS应该怎么写。

int flag[MAXN];

void DFS(int u){
    flag[u]=1;    //避免重复走
    for(int cnt=head[u];cnt;cnt=gra[cnt].next){    //通过head获取cnt信息
        //gra[cnt].next 开始遍历它的兄弟,根节点没有兄弟
        int to=gra[cnt].to;
        int dis=gra[cnt].dis;
        if(flag[to])continue;    
        DFS(to);    //去它下一个节点.
    }
}

LCA求法很多,之前用倍增T了,网上找了个tarjan的就过了,具体原理我也不懂,所以就不说了。

另外,我们还需要用到树上路径权值加1操作,naive做法会导致复杂度过了,这里需要用树上差分,具体的讲解可以看之前的洛谷第一节课的博客。简要来说对于u,v路径加1,我们

dif[u]+=1 \; dif[v]+=1 \ ; dif[lca(u,v)]-=2

之后用DFS遍历来恢复,即可达到O(n) 所有路径加1的操作。 另外,本题需要用快读。

// Sparse Matrix DP approach to find LCA of two nodes 
#include <bits/stdc++.h> 
#define OPEN 0
#define RG register
using namespace std;
const int MAXN = 3e5 + 10;
typedef struct{
    int to,nx,w;
}gra;
typedef struct{
    int u,v,lca,len;
}mys;
int n, m;
mys len[MAXN];
int cnt=0;
gra ori[MAXN];
gra que[MAXN];
int dif[MAXN];
int head[MAXN];
int f[MAXN];
int dis[MAXN];
int maxlen=-1;
bool vis[MAXN];
int back_edge_w[MAXN];
void add_edge(int u,int v,int w=0){
    ori[++cnt].to=v;
    ori[cnt].nx = head[u];
    ori[cnt].w=w;
    head[u]=cnt;
}
int mycount;
int qcnt;
int headq[MAXN];
void qadd_edge(int u,int v){
    que[++qcnt].nx=head[qcnt];
    que[qcnt].to=v;
    headq[u]=qcnt;
}
int dis_find(int u){
    if(f[u]==u)return u;else return f[u]=dis_find(f[u]);
}
int suc;
int dfs(int u,int prev,int tar){
    for(int i=head[u];i;i=ori[i].nx){
        int nx=ori[i].to;
        if(nx==prev)continue;
        dif[u]+=dfs(nx,u,tar);
    }
    if(dif[u]>=mycount && maxlen - back_edge_w[u]<=tar){
        suc=1;
    }
    return dif[u];
}
bool check(int mid) {
	memset(dif, 0, sizeof(dif));
	mycount = 0;
    
    
	for (int i = 1; i<=m; i++) {
		if (len[i].len>mid) {
			mycount++;
			dif[len[i].u] += 1;
			dif[len[i].v] += 1;
			dif[len[i].lca] -= 2;
		}
	}
	suc=0;
    dfs(1,0,mid);
    if(suc)return true;
    else return false;
}
inline int read()
{
    RG char c = getchar(); RG int x = 0;
    while (c<'0' || c>'9')  c = getchar();
    while (c >= '0'&&c <= '9')  x = (x << 3) + (x << 1) + c - '0', c = getchar();
    return x;
}
void tarjan(int u,int pre)      //tarjan������ 
{
    for(int i=head[u];i;i=ori[i].nx){
        int v=ori[i].to;
        if(v==pre)
            continue;
        dis[v]=dis[u]+ori[i].w;
        tarjan(v,u);
        back_edge_w[v]=ori[i].w;
        int f1=dis_find(v);
        int f2=dis_find(u);
        if(f1!=f2)
            f[f1]=dis_find(f2);
        vis[v]=1;
    }
    for(int i=headq[u];i;i=que[i].nx)
        if(vis[que[i].to])
        {
            int p=(i+1)>>1;
            len[p].lca=dis_find(que[i].to);
            len[p].len=dis[u]+dis[que[i].to]-2*dis[len[p].lca];
            maxlen=max(maxlen,len[p].len);
        }
}
int main()
{
#if OPEN
	freopen("vsin.txt", "r", stdin);
#endif
	 n=read();m=read();
    cnt=0;
    for(int i=0;i<MAXN;i++)f[i]=i;
	for (int i = 0; i<n - 1; i++) {
		int a, b, c; 
        a=read();b=read();c=read();
		add_edge(a,b,c);
        add_edge(b,a,c);
	}
	int x = 0; int y = -1;
    qcnt=0;
	for (int i = 1; i<=m; i++) {
		
        int u=read();
        int v=read();
        qadd_edge(u,v);
        qadd_edge(v,u);
		len[i].u=u;
        len[i].v=v;
    }
    tarjan(1,0);
	y =maxlen+1;
	while (x <y) {
		int mid = x + (y - x) / 2;
		if (check(mid))y = mid;
		else x = mid+1;
	}
	printf("%d\n",y);
	return 0;
}
发布了171 篇原创文章 · 获赞 4 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/FrostMonarch/article/details/104097645
今日推荐