Graph theory 0x01【树上问题】

size of a subtree

CF div2 D Maximum Distributed Tree
题意:给一棵n节点树,给一个大数m,所有边权的积要等于m。要求边权为1的边最少, ∑ i = 1 n − 1 ( w i , z i ) \sum_{i=1}^{n-1}(w_i,z_i) i=1n1(wi,zi)最大。 w i w_i wi是边权; z i z_i zi是该边经过次数(当计算distribution index时(见图))
在这里插入图片描述
思路:贪心+计算一条边两边各连接了多少节点(借助子树(DFS))。
贪心:

  1. For four positive integers a,b,c,d (a≥b,c≥d), ac+bd≥ad+bc
  2. For five positive integers a,b,c,d,e (a≥b,d≥e), acd+be≥bcd+ae
#include<bits/stdc++.h> 
using namespace std;
const int maxn = 1e5+5;
const int mod = 1e9+7;
typedef long long ll;
int sz[maxn],x[maxn],y[maxn];
ll p[maxn],q[maxn];
vector<int>G[maxn];
void dfs(int u){
	sz[u] = 1;
	for(auto i:G[u]) if(!sz[i]) dfs(i),sz[u] += sz[i];
}
void init(int n){
	for(int i=1;i<=n;++i) G[i].clear();
	memset(sz,0,sizeof(sz));
}
int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		int n,m;
		scanf("%d",&n);
		init(n);
		for(int i=1;i<n;++i)
			scanf("%d%d",&x[i],&y[i]),
			G[x[i]].push_back(y[i]),
			G[y[i]].push_back(x[i]);
		scanf("%d",&m);
		for(int i=1;i<=m;++i)
			scanf("%d",&p[i]);
		sort(p+1,p+1+m);
		for(;m>n-1;--m) p[m-1]=p[m]*p[m-1]%mod;
		for(;m<n-1;++m) p[m+1] = 1;
		if(p[m]==1) sort(p+1,p+1+m);
		dfs(1);
		for(int i=1;i<n;++i)
			q[i] = min(sz[x[i]],sz[y[i]]),
			q[i] = 1ll*(n-q[i])*q[i];
		sort(q+1,q+n);
		ll ans = 0;
		for(int i=1;i<n;++i,ans%=mod)
			ans += q[i]%mod*p[i]%mod;
		printf("%d\n",ans);
	}
} 

diameter of a tree

用途/性质

  • 最远距离
  • 两次dfs:第一次任一点为根,找最远;第二次以当前为根,找最远。

题目

CF Tree Destruction
问题:给一棵树,每次选两个叶子,答案加上他们的距离,然后销毁两个叶子中的一个;求最大的答案。
sol:最优方案:找到直径,然后依次销毁直径以外的叶子(第一部分)。最后只剩下直径的时候,销毁直径的叶子(第二部分)。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
vector<int>G[maxn];
vector<pair<int,int>>a;
int dep[maxn],f[maxn],in[maxn];
int dia,d;
ll ans;

void dfs(int u,int fa){
	dep[u]=dep[f[u]=fa]+1;
	if(dep[u]>dia) dia = dep[u],d = u;
	for(auto i:G[u]) if(i-fa) dfs(i,u);
}

void dfs2(int u,int fa,int dis,int flg = 0){
	in[u] = 1;
	for(auto i:G[u]) if(!in[i]) dfs2(i,fa,dis+1,1);
	if(flg) ans += dis,a.push_back(make_pair(fa,u));
}      //回溯加上第一部分的答案


int main(){
	int n,x,y;
	scanf("%d",&n);
	for(int i=1;i<n;++i) scanf("%d%d",&x,&y),G[x].push_back(y),G[y].push_back(x);
	dfs(1,0);
	x = d;
	dia = 0,dfs(x,0);
	y = d;
	ans = (ll)(dia-1)*dia/2;       //第二部分的答案
	for(int i=y;i;i=f[i]) in[i] = 1;
	for(int i=y;i;i=f[i]) 
		if(dep[i]-1>dep[y]-dep[i]) dfs2(i,x,dep[i]-1);
		else dfs2(i,y,dep[y]-dep[i]);
	printf("%lld\n",ans);
	for(auto i:a) printf("%d %d %d\n",i.first,i.second,i.second);
	for(int i=y;i-x;i=f[i]) printf("%d %d %d\n",x,i,i);
} 

LCA

algorithm

  • 倍增binary lifting
  • Tarjan
  • 用欧拉序列转化为 RMQ 问题
  • 树链剖分
  • 动态树
  • 标准RMQ

用途/性质

  • 树上距离

题目

洛谷 3379 LCA模板

  • 倍增:处理两个地方:(1)达到同一深度(2)找lca
#include<iostream>
#include<string.h>
#include<vector>
using namespace std;
const int maxn=500005;
int n,m,rt;
int d[maxn],f[maxn][22];
int lg[maxn];
vector<int>G[maxn];

void dfs(int u,int fa){
	d[u]=d[fa]+1;
	f[u][0]=fa;
	//f[u][0]是父亲,然后是第二个祖先,第四个祖先,。。。以此类推 
	for(int i=0;i<lg[d[u]];++i) f[u][i+1]=f[f[u][i]][i];
	for(auto i:G[u]) if(i!=fa) dfs(i,u);
}

int lca(int x,int y){
	if(d[x]<d[y]) swap(x,y);            //x的深度大
	//倍增【1】:先跳到同一深度。只要前者深度大于后者,前者跳到第2^【log2(深度差)-1】(即:深度差 - 1)个祖先 
	while(d[x]>d[y]) x = f[x][lg[d[x]-d[y]]-1]; 
	if(x == y) return x;               //此时深度相同。两者相等的话说明原先的y是x的祖先。此时的x或y就是lca
	/*
	倍增【2】:找lca。
	【二进制思维】从大到小尝试往上跳 如果两者祖先不相等就往上跳:
	x跳到第2^【log2(x的深度)-1】(即:x的深度-1)个祖先 
	鉴于x和y的深度一直一样,因此如果祖先相等,说明可能超过了lca的位置。
	*/
	for(int k = lg[d[x]]-1;k>=0;--k)           
		if(f[x][k]!=f[y][k]) x = f[x][k],y = f[y][k]; 
	return f[x][0];                   
}

int main(){
	scanf("%d%d%d",&n,&m,&rt);
	for(int i=1;i<=n;++i) lg[i] = lg[i-1] + (1<<lg[i-1] == i);
	int x,y,k;
	for(int i=1;i<n;++i) scanf("%d%d",&x,&y),G[x].push_back(y),G[y].push_back(x);
	// 常数优化,求出 log2(x) + 1 
	dfs(rt,0);
	for(int i=1;i<=m;++i) scanf("%d%d",&x,&y),printf("%d\n",lca(x,y));
}

HDU 2586 How far away? 两点距离

#include<iostream>
#include<string.h>
#include<vector>
using namespace std;
const int maxn=40005;
int n,m,rt;
int d[maxn],f[maxn][22];
int lg[maxn];
int l[maxn];
vector<pair<int,int>>G[maxn];

void dfs(int u,int fa){
	d[u]=d[fa]+1;
	f[u][0]=fa;
	for(int i=0;i<lg[d[u]];++i) f[u][i+1]=f[f[u][i]][i];
	for(auto i:G[u]) if(i.first!=fa) l[i.first]=l[u]+i.second,dfs(i.first,u);
}

int lca(int x,int y){
	if(d[x]<d[y]) swap(x,y);
	while(d[x]>d[y]) x = f[x][lg[d[x]-d[y]]-1]; 
	if(x == y) return x;
	for(int k = lg[d[x]]-1;k>=0;--k)           
		if(f[x][k]!=f[y][k]) x = f[x][k],y = f[y][k]; 
	return f[x][0];                   
}

void init(int n){
	memset(d,0,sizeof(d));
	memset(f,0,sizeof(f));
	for(int i=1;i<=n;++i) G[i].clear();
} 

int main(){
	int t;
	scanf("%d",&t);
	while(t--){
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;++i) lg[i] = lg[i-1] + (1<<lg[i-1] == i);
		init(n);
		int x,y,k;
		for(int i=1;i<n;++i) scanf("%d%d%d",&x,&y,&k),G[x].push_back(make_pair(y,k)),G[y].push_back(make_pair(x,k));
		dfs(1,0);
		for(int i=1;i<=m;++i) scanf("%d%d",&x,&y),printf("%d\n",l[x]+l[y]-2*l[lca(x,y)]);
	}
}

树形DP

基于DFS

换根DP

原理

父节点与直系儿子节点之间存在一个递推关系。
在dfs过程中进行递推。

例题

牛客Tree 对一棵树,找一个根,到达所有节点的距离和最小

此处,dp[i]=dp[u]-pt[i]+(n-pt[i])
意为,以i为根的树到达所有节点的距离和是他的父节点到达所有结点的距离和减掉i的子树大小*1(距离都缩短了1),又对于其他节点距离延长了1.

#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define U unsigned
#define P std::pair<int,int>
#define LL long long
#define pb push_back
#define MP std::make_pair
#define V std::vector<int>
#define all(x) x.begin(),x.end()
#define CLR(i,a) memset(i,a,sizeof(i))
#define FOR(i,a,b) for(int i = a;i <= b;++i)
#define ROF(i,a,b) for(int i = a;i >= b;--i)
#define DEBUG(x) std::cerr << #x << '=' << x << std::endl
#define inf 0x3f3f3f3f
const int N=1e6+5;
V G[N];int n;
int dp[N],pt[N],dis[N];

void dfs(int u,int fa){
    for(auto i:G[u]) if(i-fa) dfs(i,u),dis[u]+=dis[i]+pt[i],pt[u]+=pt[i];
    pt[u]+=1;
}

void DP(int u,int fa){
    for(auto i:G[u]) if(i-fa) dp[i]=dp[u]-pt[i]+(n-pt[i]),DP(i,u);
}

int main(){
    int u,v;scanf("%d",&n);
    FOR(i,1,n-1) scanf("%d%d",&u,&v),G[u].pb(v),G[v].pb(u);
    dfs(1,-1);
    dp[1]=dis[1];
    DP(1,-1);
    int ans=inf;
    FOR(i,1,n) ans=min(ans,dp[i]);
    printf("%d\n",ans);
}

点分治

洛谷板子

//
// Created by Artist on 2021/9/11.
//

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
#define all(x) x.begin(),x.end()
#define DB1(args...) do { cout << #args << " : "; dbg(args); } while (0)

void dbg() { std::cout << "  #\n"; }

template<typename T, typename...Args>
void dbg(T a, Args...args) {
    std::cout << a << ' ';
    dbg(args...);
}

void io() {ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);}

const int maxn = 1e4+4;
const int maxm = 1e2+4;
vector<pair<int,int> > G[maxn];

int rt;
int que[maxm],m;
int res[maxm];
/*
 *
6 5
1 2 1
1 3 2
1 4 3
3 5 4
3 6 2
1
3
6
10
7
 */
int maxsz[maxn],siz[maxn],mnmxsz,vis[maxn]; // 每个点的最大子树大小
void getroot(int u,int fa,int sz){
    siz[u]=1;
    maxsz[u] = 0;
    for(auto o:G[u]){
        int v=o.fi;
        if(v==fa||vis[v]) continue;
        getroot(v,u,sz);
        maxsz[u] = max(siz[v],maxsz[u]);
        siz[u] += siz[v];
    }
    maxsz[u] = max(sz-siz[u],maxsz[u]);
    if(maxsz[u]<mnmxsz) rt = u,mnmxsz = maxsz[u];
}

int nod[maxn],val[maxn],tot,bel[maxn];

void getdis(int u,int pa,int fa,int dis){
    nod[++tot] = u;
    bel[u] = pa;
    val[u] = dis;
    for(auto o:G[u]){
        int v=o.fi;
        if(vis[v]||v==fa) continue;
        getdis(v,pa,u,dis+o.se);
    }
}

bool cmp(int x,int y){
    return val[x]<val[y];
}

void calc(int u){
    tot = 0;
    nod[++tot] = u;
    val[u] = 0;
    bel[u] = u; // 属于哪棵子树
    for(auto o:G[u]){
        int v=o.fi;
        if(vis[v]) continue;
        getdis(v,v,u,o.se);
    }
    sort(nod+1,nod+tot+1,cmp);
    for(int i=1;i<=m;++i){
        int l=1,r=tot;
        while(l<r){
            if(val[nod[l]]+val[nod[r]]>que[i]) r--;
            else if(val[nod[l]]+val[nod[r]]<que[i]) l++;
            else if(bel[nod[l]]==bel[nod[r]]) {
                if(val[nod[r]]==val[nod[r-1]]) r--;
                else l++;
            }
            else{
                res[i] = 1;
                break;
            }
        }
    }
}

void solve(int u){
    vis[u] = 1;
    calc(u);
    for(auto o:G[u]){
        int v=o.fi;
        if(vis[v]) continue;
        mnmxsz = maxn+3;
        getroot(v,0,siz[v]);
        solve(rt);
    }
}

signed main() {
    io();int n;cin>>n>>m;
    for(int i=1;i<n;++i){
        int u,v,w;cin>>u>>v>>w;
        G[u].pb(mkp(v,w));
        G[v].pb(mkp(u,w));
    }
    for(int i=1;i<=m;++i){
        cin>>que[i];
        if(!que[i]) res[i]=1;
    }
    mnmxsz = maxn+3;
    getroot(1,0,n);
    solve(rt); //
    for(int i=1;i<=m;++i){
        cout<<(res[i]?"AYE":"NAY")<<endl;
    }
}

点分治2 tree

//
// Created by artist on 2021/9/12.
//


#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
#define all(x) x.begin(),x.end()
#define DB1(args...) do { cout << #args << " : "; dbg(args); } while (0)
#define endl '\n'
#define pii pair<int,int>
#define int long long

void dbg() { std::cout << "  #\n"; }

template<typename T, typename...Args>
void dbg(T a, Args...args) {
    std::cout << a << ' ';
    dbg(args...);
}

void io() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); }

const int maxn = 4e4+4;

vector<pair<int,int> > G[maxn];

int rt,mxsz[maxn],sz[maxn],vis[maxn],K;
ll ans;

void getrt(int u,int fa,int siz){
    sz[u]=1;
    mxsz[u]=0;
    for(auto o:G[u]){
        int v=o.fi;
        if(v==fa||vis[v]) continue;
        getrt(v,u,siz);
        sz[u] += sz[v];
        mxsz[u] = max(mxsz[u],sz[v]);
    }
    mxsz[u] = max(mxsz[u],siz-sz[u]);
    if(mxsz[u]<mxsz[rt]) rt=u;
}

int nod[maxn],tot;

void getdis(int u,int fa,int dis){
    nod[++tot] = dis;
    for(auto o:G[u]){
        int v=o.fi;
        if(vis[v]||v==fa) continue;
        getdis(v,u,dis+o.se);
    }
}

ll calc(int u,int dis){
    tot = 0;
    getdis(u,0,dis);
    sort(nod+1,nod+1+tot);
    int l=1,r=tot;
    ll res = 0;
    while(l<=r){
        if(nod[l]+nod[r]<=K) res += r-l,l++;
        else r--;
    }
    return res;
}
void solve(int u){
    vis[u]=1;
    ans += calc(u,0);
    for(auto o:G[u]){
        int v=o.fi;
        if(vis[v]) continue;
        ans -= calc(v,o.se);
        rt = 0;
        getrt(v,0,sz[v]);
        solve(rt);
    }
}

signed main() {
    io();int n;cin>>n;
    for(int i=1;i<n;++i){
        int u,v,w;cin>>u>>v>>w;
        G[u].pb(mkp(v,w));
        G[v].pb(mkp(u,w));
    }
    cin>>K;
    mxsz[0] = maxn;
    getrt(1,0,n);
    solve(rt);
    cout<<ans<<endl;
}

树上差分

洛谷板子

//
// Created by artist on 2021/9/22.
//


#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
#define all(x) x.begin(),x.end()
#define DB1(args...) do { cout << #args << " : "; dbg(args); } while (0)
#define endl '\n'
#define pii pair<int,int>

void dbg() { std::cout << "  #\n"; }

template<typename T, typename...Args>
void dbg(T a, Args...args) {
    std::cout << a << ' ';
    dbg(args...);
}

void io() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); }

const int maxn = 6e4;
const int LOG = __lg(maxn)+1;
int d[maxn];
vector<int> G[maxn];
int dep[maxn],fa[maxn][LOG+3];
int ans;

void init(int u,int f) {
    fa[u][0] = f;
    for(auto v:G[u]) {
        if(v==f) continue;
        dep[v] = dep[u] + 1;
        init(v,u);
    }
}

int lca(int x,int y) {
    if(dep[x]<dep[y]) swap(x,y);
    for(int i=LOG;~i;--i)
        if(dep[fa[x][i]]>=dep[y] && fa[x][i]) x=fa[x][i];
    if(x==y) return x;
    for(int i=LOG;~i;--i)
        if(fa[x][i]!=fa[y][i] && fa[x][i]) x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}

int dfs(int u,int f) {
    int cur = d[u];
    for(auto v:G[u]) {
        if(v==f) continue;
        cur += dfs(v,u);
    }
    ans = max(ans,cur);
    return cur;
}

signed main() {
    io();
    int n,k;cin>>n>>k;
    for(int i=1;i<n;++i) {
        int x,y;cin>>x>>y;
        G[x].pb(y);
        G[y].pb(x);
    }
    init(1,0);
    for(int j=1;j<=LOG;++j) for(int i=1;i<=n;++i) fa[i][j] = fa[fa[i][j-1]][j-1];
    for(int i=1;i<=k;++i) {
        int x,y;cin>>x>>y;
        int LCA = lca(x,y);
        ++d[x],++d[y];
        --d[LCA],--d[fa[LCA][0]];
    }
    dfs(1,0);
    cout<<ans<<endl;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值