图论/树 专题

就是一道树剖的裸题 实话说树剖细节挺多的

我打这个代码的时候遗漏的细节

1;有重儿子才先进行重儿子遍历 比如叶节点没有重儿子(默认为0) 是不能遍历的 不然会死循环

2;初始赋值的时候 是对1-n这有序的线段树进行赋值 所以有个rk[] 这个非常易错!!!! 出题人比较狡诈 样例能过

因为每个节点有个初始id 给出val的时候是有序的 让我们误以为直接赋值线段树就好 但是我们赋值是新的dfs序

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
const int maxn=1e5+5;
vector<int>Q[maxn];
int cnt,n,m;
struct node{
	int l,r,maxx,sum;
}tr[maxn<<2];
int val[maxn],dfn[maxn],fa[maxn],top[maxn],sz[maxn],son[maxn],dp[maxn],rk[maxn]; 
void up(int);
void bd(int,int,int);
int qmax(int,int,int,int,int);
int qsum(int,int,int,int,int);
void upd(int,int,int,int,int);
int Qmax(int,int);
int Qsum(int,int);
void Upd(int,int);
void dfs1(int,int);
void dfs2(int,int);
int main(){
	cin>>n;
	for(int i=1;i<n;i++){
		int a,b;cin>>a>>b;
		Q[a].push_back(b);
		Q[b].push_back(a);
	}
	for(int i=1;i<=n;i++)cin>>val[i];
	fa[1]=1;
	dfs1(1,1);
	dfs2(1,1);
	bd(1,1,n);
	cin>>m;
	while(m--){
		string s;int a,b;
		cin>>s>>a>>b;
		if(s=="QMAX")cout<<Qmax(a,b)<<endl;
		else if(s=="QSUM")cout<<Qsum(a,b)<<endl;
		else upd(1,1,n,dfn[a],b);
	}
     return 0;
}
void dfs1(int u,int f){
	sz[u]=1;
	for(int i=0;i<Q[u].size();i++){
		int to=Q[u][i];
		if(to==f)continue;
		dp[to]=dp[u]+1;fa[to]=u;
		dfs1(to,u);
		sz[u]+=sz[to];
		if(sz[son[u]]<sz[to])son[u]=to;
	}
}
void dfs2(int u,int tp){
	top[u]=tp;dfn[u]=++cnt;rk[cnt]=u;
	if(son[u])    //////
	dfs2(son[u],tp);
	for(int i=0;i<Q[u].size();i++){
		int to=Q[u][i];
		if(to==son[u]||to==fa[u])continue;
		dfs2(to,to);
	}
}
void up(int k){
	tr[k].sum=tr[k<<1].sum+tr[k<<1|1].sum;
	tr[k].maxx=max(tr[k<<1].maxx,tr[k<<1|1].maxx);
	return;
}
void bd(int k,int l,int r){
	if(r<l)return;
	if(l==r){
		tr[k].l=l,tr[k].r=r;
		tr[k].sum=tr[k].maxx=val[rk[l]];/////
		return;
	}
	int mid=l+r>>1;
	bd(k<<1,l,mid);
	bd(k<<1|1,mid+1,r);
	up(k);
}
int qmax(int k,int l,int r,int L,int R){
	if(L<=l&&r<=R)return tr[k].maxx;
	int mid=l+r>>1;
	int res=-1e9;
	if(mid>=L)res=max(res,qmax(k<<1,l,mid,L,R));
	if(mid<R)res=max(res,qmax(k<<1|1,mid+1,r,L,R));
	up(k);
	return res;
}
int qsum(int k,int l,int r,int L,int R){
	if(L<=l&&r<=R)return tr[k].sum;
	int mid=l+r>>1;
	int res=0;
	if(mid>=L)res+=qsum(k<<1,l,mid,L,R);
	if(mid<R)res+=qsum(k<<1|1,mid+1,r,L,R);
	up(k);
	return res;
}
void upd(int k,int l,int r,int pos,int w){
	if(l==r){
		tr[k].maxx=tr[k].sum=w;
		return;
	}
	int mid=l+r>>1;
	if(pos<=mid)upd(k<<1,l,mid,pos,w);
	else upd(k<<1|1,mid+1,r,pos,w);
	up(k);
}
int Qsum(int u,int v){
	int res=0;
	while(top[u]!=top[v]){
		if(dp[top[u]]<dp[top[v]])swap(u,v);
		res+=qsum(1,1,n,dfn[top[u]],dfn[u]);
		u=fa[top[u]];
	}
	if(dp[u]<dp[v])swap(u,v);
	res+=qsum(1,1,n,dfn[v],dfn[u]);
	return res;
}
int Qmax(int u,int v){
	int res=-1e9;
	while(top[u]!=top[v]){
		if(dp[top[u]]<dp[top[v]])swap(u,v);
		res=max(res,qmax(1,1,n,dfn[top[u]],dfn[u]));
		u=fa[top[u]];
	}
	if(dp[u]<dp[v])swap(u,v);
	res=max(res,qmax(1,1,n,dfn[v],dfn[u]));
	return res;
}

RMQ上维护最大和次大还是要注意一下的

点击查看代码
#include<bits/stdc++.h>
#define N 400010
#define M 900010
#define INF 2147483647000000
#define ll long long

using namespace std;

struct edge{
    ll u,v,d;
    ll next;
}G[N<<1];
ll tot=0;
ll head[N];
inline void addedge(ll u,ll v,ll d)
{
    G[++tot].u=u,G[tot].v=v,G[tot].d=d,G[tot].next=head[u],head[u]=tot;
    G[++tot].u=v,G[tot].v=u,G[tot].d=d,G[tot].next=head[v],head[v]=tot;
}

ll bz[N][19];
ll maxi[N][19];
ll mini[N][19];
ll deep[N];
inline void dfs(ll u,ll fa)
{
    bz[u][0]=fa;
    for(ll i=head[u];i;i=G[i].next)
    {
        ll v=G[i].v;
        if(v==fa)continue;
        deep[v]=deep[u]+1ll;
        maxi[v][0]=G[i].d;
        mini[v][0]=-INF;
        dfs(v,u);
    }
}

ll n;
inline void cal()
{
    for(ll i=1;i<=18;++i)
        for(ll j=1;j<=n;++j)
        {
            bz[j][i]=bz[bz[j][i-1]][i-1];
            maxi[j][i]=max(maxi[j][i-1],maxi[bz[j][i-1]][i-1]);
            mini[j][i]=max(mini[j][i-1],mini[bz[j][i-1]][i-1]);
            if(maxi[j][i-1]>maxi[bz[j][i-1]][i-1])mini[j][i]=max(mini[j][i],maxi[bz[j][i-1]][i-1]);
            else if(maxi[j][i-1]<maxi[bz[j][i-1]][i-1])mini[j][i]=max(mini[j][i],maxi[j][i-1]);
        }
}

inline ll LCA(ll x,ll y)
{
    if(deep[x]<deep[y])swap(x,y);
    for(ll i=18;i>=0;--i)
        if(deep[bz[x][i]]>=deep[y])
            x=bz[x][i];
    if(x==y)return x;
    for(ll i=18;i>=0;--i)
        if(bz[x][i]^bz[y][i])
            x=bz[x][i],y=bz[y][i];
    return bz[x][0];
}

inline ll qmax(ll u,ll v,ll maxx)
{
    ll Ans=-INF;
    for(ll i=18;i>=0;--i)
    {
        if(deep[bz[u][i]]>=deep[v])
        {
            if(maxx!=maxi[u][i])Ans=max(Ans,maxi[u][i]);
            else Ans=max(Ans,mini[u][i]);
            u=bz[u][i];
        }
    }
    return Ans;
}

inline void read(ll &x)
{
    x=0;
    char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+(ch^48),ch=getchar();
}

ll m;

edge A[M<<1];

inline bool cmp(edge x,edge y)
{
    return x.d<y.d;
}

ll Father[N];
inline ll Get_Father(ll x)
{
    return (x==Father[x]) ? x : Father[x]=Get_Father(Father[x]);
}

bool B[M<<1];

int main()
{
    read(n),read(m);
    for(ll i=1;i<=m;++i)
    {
        read(A[i].u),read(A[i].v),read(A[i].d);
    }

    sort(A+1,A+m+1,cmp);

    for(ll i=1;i<=n;++i)
        Father[i]=i;

    ll Cnt=0ll;
    for(ll i=1;i<=m;++i)
    {
        ll Father_u=Get_Father(A[i].u);
        ll Father_v=Get_Father(A[i].v);
        if(Father_u!=Father_v)
        {
            Cnt+=A[i].d;
            Father[Father_u]=Father_v;
            addedge(A[i].u,A[i].v,A[i].d);
            B[i]=true;
        }
    }

    mini[1][0]=-INF;
    deep[1]=1;
    dfs(1,-1);
    cal();

    ll Ans=INF;

    for(ll i=1;i<=m;++i)
    {
        if(!B[i])
        {
            ll u=A[i].u;
            ll v=A[i].v;
            ll d=A[i].d;
            ll lca=LCA(u,v);
            ll maxu=qmax(u,lca,d);
            ll maxv=qmax(v,lca,d);
            Ans=min(Ans,Cnt-max(maxu,maxv)+d);
        }
    }

    printf("%lld",Ans);

    return 0;
}

针对瓶颈生成树 根据其充分性可以直接用最小生成树来解决

https://www.luogu.com.cn/problem/P3761

分析:

首先要删的边一定是在树的直径上 但这个题目枚举每一条边是没问题的

删去一条边后将一颗树变成了两颗树

对于每颗树内对答案的贡献为树的直径

现在问题变成了两棵树分别找一个点 <u,v> 使得对答案贡献最小

我们希望u为 u所在树中以u为终点 最长链最短 同理v

就是分别找两个树的半径 用换根dp即可

struct edge{
    int v, w, id;
};
struct node{
    int u, v, w;
};
vector<edge>g[N];
node e[N];
int n;
int del = 0;
int mx1[N], mx2[N], mx_child[N];

int maxd = 0;
void dfs_d(int u, int fa) {
    mx1[u] = mx2[u] = 0;
    for (auto ed : g[u]) {
        if (ed.v == fa)continue;
        if (ed.id == del)continue;
        dfs_d(ed.v, u);
        int val = mx1[ed.v] + ed.w;
        if (val > mx1[u])mx2[u] = mx1[u], mx1[u] = val, mx_child[u] = ed.v;
        else if (val > mx2[u]) mx2[u] = val;
    }
    maxd = max(maxd, mx1[u] + mx2[u]);
}
int maxr = 0;
void dfs_r(int u, int from, int fa) {
    maxr = min(maxr, max( mx1[u],from ));
    for (auto ed : g[u]) {
        if (ed.v == fa)continue;
        if (ed.id == del)continue;
        if (ed.v == mx_child[u])
            dfs_r(ed.v, max(from + ed.w, mx2[u] + ed.w), u);
        else
            dfs_r(ed.v, max(from + ed.w, mx1[u] + ed.w),u);
    }
}

int get_d(int u) {
    maxd = 0;
    dfs_d(u, 0);
    return maxd;
}

int get_r(int u) {
    maxr = 1e9;
    dfs_r(u, 0, 0);
    return maxr;
}
void slove() {
    cin >> n;
    for (int i = 1; i <= n - 1; i++) {
        int u, v, w; cin >> u >> v >> w;
        e[i] = { u,v,w };
        g[u].push_back({ v,w,i });
        g[v].push_back({ u,w,i });
    }
    int ans = 1e9;
    for (del = 1; del <= n - 1; del++) {
        int u = e[del].u, v = e[del].v, w = e[del].w;
         int res = max({
                get_d(u),
                get_d(v),
                get_r(u) + get_r(v) + w
            });
        ans = min(ans, res);
      
    }
    cout << ans << endl;
}
posted @ 2022-04-26 11:18  wzx_believer  阅读(35)  评论(0)    收藏  举报