树(更新中....)

Problem - C - Codeforces

思路:树的重心,判断一个重心和两个重心的情况。当存在两个重心时,两个重心必相邻。只需把其中一个重心的边连在另一个重心上即可。

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
const int maxn = 1e5+10;
int n;
int sz[maxn];
int sum[maxn];
int fa[maxn];
int r1,r2;
int mx=1e9;
vector <int > g[maxn];
void dfs(int u,int f)
{
    sz[u]=1;
    sum[u]=0;
    for(auto v:g[u])
    {
        if(v==f) continue;
        dfs(v,u);
        sz[u]+=sz[v];
        sum[u]=max(sum[u],sz[v]);
    }
    sum[u]=max(sum[u],n-sz[u]);
    if(sum[u]==mx)
    {
        r2=u;
    }
    if(sum[u]<mx)
    {
        r1=u;
        r2=0;
        mx=sum[u];
    }
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int t;
    cin>>t;
    while(t--)
    {
        r1=r2=0;
        cin>>n;
        for(int i=0;i<=n;i++)
        {
            mx=1e9;
            sz[i]=sum[i]=fa[i]=0;
            g[i].clear();
        }
        for(int i=1;i<n;i++)
        {
            int x,y;
            cin>>x>>y;
            g[x].push_back(y);
            g[y].push_back(x);
        }
        dfs(1,-1);
        if(!r2)
        {
            int u=r1;
            int v=g[u][0];
            cout<<u<<" "<<v<<endl;
            cout<<u<<" "<<v<<endl;
        }
        else
        {
            int v;
            for(int i=0;i<g[r2].size();i++)
            {
                if(g[r2][i]!=r1)
                {
                    v=g[r2][i];
                    break;
                }
            }
            cout<<r2<<" "<<v<<endl;
            cout<<r1<<" "<<v<<endl;
        }
    }
}

核心城市

P5536 【XR-3】核心城市 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:显然求与核心城市的距离最大的城市,其与核心城市的距离最小那么k座城市一定在树的直径上。然后我们以这个直径的中点为根,把其他节点按照以这个节点为根的子树中节点的最大深度-这个点的深度排序选前$k−1$个节点即可

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
const int maxn = 1e5+10;
int n,k;
int c;
int r1,r2;
int d[maxn];
int md[maxn];
int fa[maxn];
int ans[maxn];
vector <int > g[maxn];
vector <int > path;
void dfs(int u,int f)
{
    for(auto v:g[u])
    {
        if(v==f) continue;
        d[v]=d[u]+1;
        fa[v]=u;
        if(d[v]>d[c]) c=v;
        dfs(v,u);
    }
}
void dfs1(int u,int f)
{
    md[u]=d[u];
    for(auto v:g[u])
    {
        if(v==f) continue;
        d[v]=d[u]+1;
        dfs1(v,u);
        md[u]=max(md[u],md[v]);
    }
}
bool cmp(int a,int b)
{
    return a>b;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin>>n>>k;
    for(int i=1;i<n;i++)
    {
        int x,y;
        cin>>x>>y;
        g[x].push_back(y);
        g[y].push_back(x);
    }
    dfs(1,-1);
    r1=c;
    d[c]=0,dfs(c,-1);
    r2=c;
    for(int i=1;i<=(d[c]+1)/2;i++) r2=fa[r2];
    d[r2]=0;
    dfs1(r2,-1);
    for(int i=1;i<=n;i++) ans[i]=md[i]-d[i];
    sort(ans+1,ans+1+n,cmp);
    int sum=0;
    for(int i=k+1;i<=n;i++) sum=max(sum,ans[i]+1);
    cout<<sum<<endl;
}

Three Paths on a Tree

Problem - 1294F - Codeforces

思路:首先这三个点中其中两个点一定是这棵树直径的端点,其次在直径外找一点离两个端点距离最远即可

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
const int maxn = 2e5+10;
vector <int > g[maxn];
int n,c;
int d[maxn];
int d1[maxn];
int d2[maxn];
int fa[maxn];
int vis[maxn];
int ans3;
int ans=0;
void dfs(int u,int f)
{
    for(auto v:g[u])
    {
        if(v==f) continue;
        d[v]=d[u]+1;
        fa[v]=u;
        if(d[v]>d[c]) c=v;
        dfs(v,u);
    }
    return ;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin>>n;
    for(int i=1;i<n;i++)
    {
        int a,b;
        cin>>a>>b;
        g[a].push_back(b);
        g[b].push_back(a);
    }

    dfs(1,-1);
    int ans1=c;
    d[c]=0,dfs(c,-1);
    int ans2=c;
    for(int i=1;i<=n;i++) d1[i]=d[i];
    d[ans2]=0;
    dfs(ans2,-1);
    for(int i=1;i<=n;i++) d2[i]=d[i];

    for(int i=1;i<=n;i++)
    {
        if(i==ans1||i==ans2) continue;
        if(d1[i]+d2[i]>d1[ans3]+d2[ans3]) ans3=i;
    }
    ans=d1[ans3]+d2[ans3]+d1[ans2];
    ans/=2;
    cout<<ans<<endl;
    cout<<ans1<<" "<<ans2<<" "<<ans3<<endl;
}

逃学的小孩

[P4408 NOI2003] 逃学的小孩 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:既然要要求耗时最长,那么就假定AB两点恰好为该树直径的端点,然后遍历找耗时最大的点即可

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
const int maxn = 2e5+10;
typedef long long ll;
struct edge{
    int v,w,net;
}e[maxn*2];
int head[maxn];
int cnt;
int n,m;
int r1,r2;
int c;
ll d[maxn];
ll d1[maxn];
ll d2[maxn];
int fa[maxn];
void add(int u,int v,int w)
{
    e[++cnt].v=v;
    e[cnt].w=w;
    e[cnt].net=head[u];
    head[u]=cnt;
}
void dfs(int u,int ff)
{
    for(int i=head[u];i;i=e[i].net)
    {
        int v=e[i].v;
        if(v==ff) continue;
        d[v]=d[u]+e[i].w;
        if(d[v]>d[c]) c=v;
        dfs(v,u);
    }
    return ;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin>>n>>m;
    for(int i=1;i<n;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        add(u,v,w);
        add(v,u,w);
    }
    dfs(1,-1);
    r1=c;d[c]=0;
    dfs(c,-1);
    r2=c;
    for(int i=1;i<=n;i++) d1[i]=d[i];
    d[c]=0;
    dfs(c,-1);
    for(int i=1;i<=n;i++) d2[i]=d[i];

    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        ans=max(min(d1[i],d2[i])+d1[r2],ans);
    }
    cout<<ans<<endl;
}

树网的核

[P1099 NOIP2007 提高组] 树网的核 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:先找出该树的直径,对该直径进行尺取,计算每条路径的偏心距,取最大值

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <deque>
using namespace std;
const int maxn = 310;
struct edge{
    int v,w,net;
}e[maxn*2];
int head[maxn];
int d[maxn];
int fa[maxn];
int vis[maxn];
int q[maxn];
int c,n,s;
int cnt;
void add(int u,int v,int w)
{
    e[++cnt].v=v;
    e[cnt].w=w;
    e[cnt].net=head[u];
    head[u]=cnt;
}
void dfs(int u,int ff)
{
    for(int i=head[u];i;i=e[i].net)
    {
        int v=e[i].v;
        if(v==ff||vis[v]) continue;
        fa[v]=u;
        d[v]=d[u]+e[i].w;
        if(d[v]>d[c]) c=v;
        dfs(v,u);
    }
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin>>n>>s;
    vector <int > path(n+1);
    for(int i=1;i<n;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        add(u,v,w);
        add(v,u,w);
    }
    dfs(1,-1);
    d[c]=0,fa[c]=0;dfs(c,-1);
    int r=c;
    int ans=1e9;
    for(int i=c,j=c;i;i=fa[i])
    {
        while(d[j]-d[i]>s) j=fa[j];
        int x=max(d[c]-d[j],d[i]);
        ans=min(ans,x);
    }
    for(int i=r;i;i=fa[i]) vis[i]=1;
    for(int i=r;i;i=fa[i])
    {
        c=i;
        d[i]=0;
        dfs(i,fa[i]);
    }
    for(int i=1;i<=n;i++) ans=max(ans,d[i]);
    cout<<ans<<endl;
}

巡逻

350. 巡逻 - AcWing题库

思路:题目意思大致是构建一个或两个环,在一颗树中从一个点开始遍历所有点并返回的代价为$2(n-1)$。当$k=1$时,只要将该树的直径两个端点链接便可的到最小值$2(n-1)-d1+1$。当$k=2$,在$k=1$的操作下,找到除了直接$d1$外最长的边即可。只要将直径$d1$上的所有边权改为-1,然后用树上dp就直径,最后答案为$2*(n-1)-d1+1-d2+1$.

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <cmath>
using namespace std;
const int maxn = 1e5+10;
struct node{
    int v,w,net;
}e[2*maxn];
int head[maxn];
int tot;
int c;
int r1,r2;
int d[maxn];
int vis[maxn];
int d1[maxn],d2[maxn];
int ans;
int fa[maxn];
int fe[maxn];
void add(int u,int v,int w)
{
    e[++tot].v=v;
    e[tot].w=w;
    e[tot].net=head[u];
    head[u]=tot;
}
void dfs(int u,int ff)
{
    for(int i=head[u];i;i=e[i].net)
    {
        int v=e[i].v;
        if(v==ff||vis[v]) continue;
        vis[v]=1;
        fa[v]=u;
        fe[v]=i;
        d[v]=d[u]+e[i].w;
        if(d[v]>d[c]) c=v;
        dfs(v,u);
    }
}
void dfs1(int u,int ff)
{
    d1[u]=0,d2[u]=0;
    for(int i=head[u];i;i=e[i].net)
    {
        int v=e[i].v;
        if(v==ff||vis[v]) continue;;
        vis[v]=1;
        dfs1(v,u);
        int t=d1[v]+e[i].w;
        if(t>d1[u])
        {
            d2[u]=d1[u];
            d1[u]=t;
        }
        else if(t>d2[u])
        {
            d2[u]=t;
        }
    }
    ans=max(ans,d1[u]+d2[u]);
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);

    int n,k;
    cin>>n>>k;
    for(int i=1;i<n;i++)
    {
        int x,y;
        cin>>x>>y;
        add(x,y,1);
        add(y,x,1);
    }
    dfs(1,-1);
    d[c]=0,r1=c,fa[c]=0;
    memset(vis,0,sizeof vis);
    dfs(c,-1);
    r2=c;
    if(k==1)
    {
        int s=2*(n-1)-d[c]+1;
        cout<<s<<endl;
        return 0;
    }
    for(int i=r2;i!=r1;i=fa[i])
    {
        int id=fe[i];
        e[id].w=-1;
        e[id+((id&1)?1:-1)].w=-1;
    }
    fa[1]=0;
    memset(vis,0,sizeof vis);
    dfs1(1,-1);
    int s=0;
    s=2*(n-1)-d[c]+1-ans+1;
    cout<<s<<endl;
}

HXY造公园

P2195 HXY造公园 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:对于问题一,直接dfs找直径即可。对于问题二,用并查集维护连通块,若不在同一个连通快上,则新形成的连通快的直径为$dis[fy]=max(max((dis[fx]+1)/2+(dis[fy]+1)/2+1,dis[fy]),dis[fx]);$

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
using namespace std;
const int maxn = 3e5+10;
vector <int > g[maxn];
int n,m,q;
int s=0;
int v=-1;
int dis[maxn];
int fa[maxn];
int vis[maxn];
int c;
int find(int x)
{
    return fa[x]==x?fa[x]:fa[x]=find(fa[x]);
}
void merge(int x,int y)
{
    int fx=find(x);
    int fy=find(y);
    if(fx==fy) return ;
    fa[fx]=fy;
}
void dfs(int u,int f,int d)
{
    if(d>v) v=d,c=u;
    for(auto v:g[u])
    {
        if(v==f) continue;
        dfs(v,u,d+1);
    }
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);

    cin>>n>>m>>q;
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        int x,y;
        cin>>x>>y;
        g[x].push_back(y);
        g[y].push_back(x);
        merge(x,y);
    }
    for(int i=1;i<=n;i++)
    {
        if(fa[i]!=i) continue;
        v=-1;
        dfs(i,-1,0);
        v=-1;
        dfs(c,-1,0);
        dis[i]=v;
    }
    while(q--)
    {
        int op;
        cin>>op;
        if(op==1)
        {
            int x;
            cin>>x;
            cout<<dis[find(x)]<<endl;
        }
        else
        {
            int x,y;
            cin>>x>>y;
            int fx=find(x);
            int fy=find(y);
            if(fx==fy) continue;
            dis[fy]=max(max((dis[fx]+1)/2+(dis[fy]+1)/2+1,dis[fy]),dis[fx]);
            merge(x,y);
        }
    }
    return 0;
}

直径

389. 直径 - AcWing题库

思路:先找到该树的直径,并记录路径上的点,然后从路径上每一个点再次dfs,然后从每个点再次DFS
显然如果从某个点出发,能找到第二条与当前一边直径长度相等的一条路径,那么这条边就为可行边
分别从左右出发找一下即可。

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <cmath>
using namespace std;
typedef long long ll;
const int maxn = 2e5+10;
struct edge{
    int v;
    ll w;
    int net;
}e[maxn*2];
int head[maxn];
ll sum[maxn];
ll val[maxn];
int arr[maxn];
int vis[maxn];
int tot;
int fa[maxn];
int c,r1,r2;
ll d[maxn];
void add(int u,int v,ll w)
{
    e[++tot].v=v;
    e[tot].w=w;
    e[tot].net=head[u];
    head[u]=tot;
}
void dfs(int u,int ff)
{
    for(int i=head[u];i;i=e[i].net)
    {
        int v=e[i].v;
        if(v==ff||vis[v]) continue;
        d[v]=d[u]+e[i].w;
        fa[v]=u;
        if(d[v]>d[c]) c=v;
        dfs(v,u);
    }
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    
    int n;
    cin>>n;
    for(int i=1;i<n;i++)
    {
        int u,v; ll w;
        cin>>u>>v>>w;
        add(u,v,w);add(v,u,w);
    }
    dfs(1,-1);
    d[c]=0,fa[c]=0,r1=c;
    dfs(c,-1);
    r2=c;
    int cnt=0;
    for(int i=r2;i;i=fa[i])
    {
        vis[i]=1;
        arr[++cnt]=i;
    }
    reverse(arr+1,arr+1+cnt);
    for(int i=1;i<=cnt;i++)
    {
        sum[i]=d[arr[i]];
    }
    for(int i=1;i<=cnt;i++)
    {
        d[arr[i]]=0;
        c=arr[i];
        dfs(arr[i],-1);
        val[i]=d[c];
    }
    int l=1,r=cnt;
    for(int i=1;i<=cnt;i++)
    {
        if(val[i]==sum[i]) l=i;
    }
    for(int i=cnt;i>=1;i--)
    {
        if(val[i]==sum[cnt]-sum[i]) r=i;
    }
    cout<<sum[cnt]<<endl<<r-l<<endl;
}

次小生成树

356. 次小生成树 - AcWing题库

思路:次小生成树一定仅有一条边和最小生成树不同,首先生成一棵最小生成树,然后枚举剩余的边,加到最小生成树中。此时树中会形成一个换环,只要找出路径中的最大的边和最小的边,然后计算结果即可。

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;
#define inf 1e16
const int maxn = 3e5+10;
struct edge{
    int v,w,net;
}e[maxn<<1];
struct node{
    int u,v,w;
}s[maxn];
int n,m;
int head[maxn<<1];
int tot;
int d[maxn];
int vis[maxn<<1];
int f[maxn][33];
int fa[maxn<<1];
ll dp[2][maxn][33];
int dis[maxn];
int deep;
ll val1,val2;
ll ans,ans_max;
bool cmp(node a,node b)
{
    return a.w<b.w;
}
int find(int x)
{
    return fa[x]==x?fa[x]:fa[x]=find(fa[x]);
}
void add(int u,int v,int w)
{
    e[++tot].v=v;
    e[tot].w=w;
    e[tot].net=head[u];
    head[u]=tot;
}
void kruskal()
{
    sort(s+1,s+1+m,cmp);
    for(int i=1;i<=m;i++)
    {
        int x=find(s[i].u),y=find(s[i].v);
        if(x==y) continue;
        vis[i]=1;
        fa[x]=y;
        ans+=s[i].w;
        add(s[i].u,s[i].v,s[i].w);
        add(s[i].v,s[i].u,s[i].w);
    }
}
void dfs(int u,int pre)
{
    for(int i=head[u];i;i=e[i].net)
    {
        int v=e[i].v;
        if(d[v]||v==pre) continue;
        f[v][0]=u;
        dp[0][v][0]=e[i].w;
        dp[1][v][0]=-inf;
        d[v]=d[u]+1;
        dfs(v,u);
    }
}
void st(int n)
{
    for(int j=1;j<=31;j++)
    {
        for(int i=1;i<=n;i++)
        {
            f[i][j]=f[f[i][j-1]][j-1];
            if(dp[0][i][j-1]!=dp[0][f[i][j-1]][j-1])
            {
                dp[0][i][j]=max(dp[0][i][j-1],dp[0][f[i][j-1]][j-1]);
                dp[1][i][j]=min(dp[0][i][j-1],dp[0][f[i][j-1]][j-1]);
            }
            else
            {
                dp[0][i][j]=dp[0][i][j-1];
                dp[1][i][j]=max(dp[1][i][j-1],dp[1][f[i][j-1]][j-1]);
            }
        }
    }
}
void update2(int x)
{
    if(x>val1)
        val2=val1,val1=x;
    else if(x>val2&&x!=val1)
        val2=x;
}
void update(int x,int t)
{
    update2(dp[0][x][t]);
    update2(dp[1][x][t]);
}
void lca(int u,int v)
{
    val1=val2=-inf;
    if(d[u]<d[v])
        swap(u,v);
    for(int i=31;i>=0;i--)
    {
        if(d[f[u][i]]>=d[v])
        {
            update(u,i);
            u=f[u][i];
        }
    }
    if(u==v) return ;
    for(int i=31;i>=0;i--)
    {
        if(f[u][i]!=f[v][i])
        {
            update(u,i);update(v,i);
            u=f[u][i];
            v=f[v][i];
        }
    }
    update(u,0),update(v,0);
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n>>m;
    for(int i=1;i<=m;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        s[i]={u,v,w};
        fa[i]=i;
    }
    kruskal();
    d[1]=1;
    dfs(1,0);
    st(n);
    ans_max=inf;
    for(int i=1;i<=m;i++)
    {
        if(vis[i]) continue;
        lca(s[i].u,s[i].v);
        if(val1!=s[i].w)
            ans_max=min(ans_max,ans-val1+s[i].w);
        else
            ans_max=min(ans_max,ans-val2+s[i].w);
    }
    cout<<ans_max<<endl;
    return 0;
}

闇の連鎖

352. 闇の連鎖 - AcWing题库

思路:

首先主要边刚好为一棵树,当对一棵树加上一条边时便形成了一个环。考虑枚举所有次要边,当加入一条边时,树上形成环的路径都加一,表示需要切掉一条次要边才能变为不连通。这时用树上差分进行修改操作即可

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <cmath>
#include <vector>
using namespace std;
const int maxn = 3e5+10;
struct edge{
    int v,net;
}e[maxn<<1];
int head[maxn];
int tot;
int d[maxn];
int dis[maxn];
int ans=0;
int n,m;
int vis[maxn];
int f[maxn][33];
void add(int u,int v)
{
    e[++tot].v=v;
    e[tot].net=head[u];
    head[u]=tot;
}
void dfs(int u,int pre)
{
    d[u]=d[pre]+1;
    f[u][0]=pre;
    for(int i=head[u];i;i=e[i].net)
    {
        int v=e[i].v;
        if(d[v]||v==pre) continue;
        dfs(v,u);
    }
}
void st(int n)
{
    for(int j=1;j<=16;j++)
    {
        for(int i=1;i<=n;i++)
        {
            f[i][j]=f[f[i][j-1]][j-1];
        }
    }
}
int lca(int x,int y)
{
    if(d[x]<d[y]) swap(x,y);
    for(int i=16;i>=0;i--)
    {
        if(d[f[x][i]]>=d[y]) x=f[x][i];
    }
    if(x==y) return x;
    for(int i=16;i>=0;i--)
    {
        if(f[x][i]!=f[y][i])
        {
            x=f[x][i];
            y=f[y][i];
        }
    }
    return f[x][0];
}
int dfs1(int u,int pre)
{
    int res=dis[u];
    for(int i=head[u];i;i=e[i].net)
    {
        int v=e[i].v;
        if(v==pre) continue;
        int s=dfs1(v,u);
        if(s==0) ans+=m;
        else if(s==1) ans++;
        res+=s;
    }
    return res;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n>>m;
    for(int i=1;i<n;i++)
    {
        int u,v;
        cin>>u>>v;
        add(u,v);add(v,u);
    }
    dfs(1,0);
    st(n);
    for(int i=1;i<=m;i++)
    {
        int u,v;
        cin>>u>>v;
        int p=lca(u,v);
        dis[u]++;dis[v]++;dis[p]-=2;
    }
    dfs1(1,-1);
    printf("%d\n",ans);
}

斐波那契

P3938 斐波那契 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:一眼感觉lca模板题,但是点的数据大小到了1e12。仔细观察该兔子祖先关系图,再联系题目。发现每个节点和父节点的差值正好是一个斐波那契数,且深度越大差值越大。因此先需处理出数据范围内的斐波那契数,只需要找斐波那契中最大的但是比他小的那个数就好了,然后不断减掉,向上找。

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <cmath>
using namespace std;
const int maxn = 3e5+10;
typedef long long ll;
ll dp[maxn];
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int m;
    cin>>m;
    dp[0]=dp[1]=1;
    for(int i=2;i<=60;i++)
        dp[i]=dp[i-1]+dp[i-2];
    while(m--)
    {
        ll x,y;
        cin>>x>>y;
        while(x!=y)
        {
            if(y<x) swap(x,y);
            int l=1,r=60;
            while(l<=r)
            {
                int mid = (l+r)/2;
                if(dp[mid]<y) l=mid+1;
                else r=mid-1;
            }
            y-=dp[l-1];
        }
        printf("%lld\n",x);
    }
}

紧急集合

[P4281 AHOI2008]紧急集合 / 聚会 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:分别找三个点两两间的lca,然后以一个不同个lca点为集合点

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <cmath>
using namespace std;
const int maxn = 5e5+10;
struct edge{
    int v,net;
}e[maxn<<1];
int head[maxn];
int tot;
int d[maxn];
int f[maxn][33];
int ans=0;
void add(int u,int v)
{
    e[++tot].v=v;
    e[tot].net=head[u];
    head[u]=tot;
}
void dfs(int u,int pre)
{
    f[u][0]=pre;
    d[u]=d[pre]+1;
    for(int i=head[u];i;i=e[i].net)
    {
        int v=e[i].v;
        if(v==pre||d[v]) continue;
        dfs(v,u);
    }
}
void st(int n)
{
    for(int j=1;j<=16;j++)
        for(int i=1;i<=n;i++)
            f[i][j]=f[f[i][j-1]][j-1];
}
int lca(int x,int y)
{
    if(d[x]<d[y]) swap(x,y);
    for(int i=16;i>=0;i--)
    {
        if(d[f[x][i]]>=d[y])
        {
            x=f[x][i];
        }
    }
    if(x==y) return x;
    for(int i=16;i>=0;i--)
    {
        if(f[x][i]!=f[y][i])
        {
            x=f[x][i];
            y=f[y][i];
        }
    }
    return f[x][0];
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    int n,m;
    cin>>n>>m;
    for(int i=1;i<n;i++)
    {
        int a,b;
        cin>>a>>b;
        add(a,b),add(b,a);
    }
    dfs(1,0);
    st(n);
    while(m--)
    {
        int x,y,z;
        cin>>x>>y>>z;
        int l1=lca(x,y);
        int l2=lca(x,z);
        int l3=lca(z,y);
        if (l1 == l2) 
        {
            cout << l3 << " ";
            cout << d[x] - 2*d[l1] + d[y] + d[z] -  d[l3] << endl;
            continue;
        } 
        else if (l2 == l3) 
        {
            cout << l1 << " ";
            cout << d[x] + d[y] + d[z] -2* d[l2] -  d[l1] << endl;
            continue;
        } 
        else 
        {
            cout << l2 << " ";
            cout << d[x] + d[y] + d[z] -2* d[l3] -  d[l2] << endl;
            continue; 
        }
    }
}

树殓刨坟

天天爱跑步

[P1600 NOIP2016 提高组] 天天爱跑步 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:首先考虑从起点u到lca的路径上,若路径上存在一点x对答案有贡献,那么$d[s]-d[x]=w[x]$即当$d[x]+w[x]=d[u]$时,$ans[x]++$。这时我们用一个数组统计起点u到lca路径上所有满足$d[x]+w[x]=d[u]$条件的点贡献加1。然后考虑从lca到终点v的路径上,若路径上存在一点x对答案有贡献,那么即当$d[u]+d[x]−2∗d[LCA]=w[x]$时,即$d[x]-w[x]=2d[lca]-d[u]$时,$ans[x]++$。这时我们用一个数组统计lca到终点v上所有满足$d[x]-w[x]=2d[lca]-d[u]$的点贡献加1。即用树上差分进行维护,注意$2*d[lca]-d[u]$可能为负数,我们可以加一个偏移量。最后在dfs每个点记录其对答案的贡献即可。

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
const int maxn = 3e5+10;
int n,m;
struct edge{
    int v,net;
}e[maxn<<1];
int head[maxn];
int tot;
int d[maxn];
int d1[maxn<<1],d2[maxn<<1];
int ans[maxn];
vector <pair <int ,int > > op1[maxn],op2[maxn];
int f[maxn][33];
int w[maxn];
void add(int u,int v)
{
    e[++tot].v=v;
    e[tot].net=head[u];
    head[u]=tot;
}
void dfs(int u,int pre)
{
    d[u]=d[pre]+1;
    f[u][0]=pre;
    for(int i=head[u];i;i=e[i].net)
    {
        int v=e[i].v;
        if(d[v]||v==pre) continue;
        dfs(v,u);
    }
}
void st(int n)
{
    for(int j=1;j<=16;j++)
        for(int i=1;i<=n;i++)
            f[i][j]=f[f[i][j-1]][j-1];
}
int lca(int x,int y)
{
    if(d[x]<d[y]) swap(x,y);
    for(int i=16;i>=0;i--)
    {
        if(d[f[x][i]]>=d[y]) x=f[x][i];
    }
    if(x==y) return x;
    for(int i=16;i>=0;i--)
    {
        if(f[x][i]!=f[y][i])
        {
            x=f[x][i];
            y=f[y][i];
        }
    }
    return f[x][0];
}
void update(int u,int v)
{
    int p=lca(u,v);
    op1[u].push_back(make_pair(d[u],1));
    op1[f[p][0]].push_back(make_pair(d[u],-1));
    op2[v].push_back(make_pair(d[u]-2*d[p]+n,1));
    op2[p].push_back(make_pair(d[u]-2*d[p]+n,-1));
}
void dfs1(int u,int pre)
{
    int v1=w[u]+d[u],v2=w[u]-d[u]+n;
    int res1=d1[v1],res2=d2[v2];
    for(int i=head[u];i;i=e[i].net)
    {
        int v=e[i].v;
        if(v==pre) continue;
        dfs1(v,u);
    }
    for(int i=0;i<op1[u].size();i++)
        d1[op1[u][i].first]+=op1[u][i].second;
    for(int i=0;i<op2[u].size();i++)
        d2[op2[u][i].first]+=op2[u][i].second;
    ans[u]=(d1[v1]-res1)+(d2[v2]-res2);
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n>>m;
    for(int i=1;i<n;i++)
    {
        int u,v;
        cin>>u>>v;
        add(u,v);add(v,u);
    }
    for(int i=1;i<=n;i++) cin>>w[i];
    dfs(1,0);
    st(n);
    while(m--)
    {
        int x,y;
        cin>>x>>y;
        update(x,y);
    }
    dfs1(1,0);
    for(int i=1;i<=n;i++)
        cout<<ans[i]<<" ";
}

运输计划

[P2680 NOIP2015 提高组] 运输计划 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:lca+树上差分+二分。题目是说找最大的最小,很容易可以想到用二分求解。假设在t秒内完成,则二分判断是否可以去掉一条边,使得所有路径在可以在t秒内完成。求每个路径的公共边可以用差分处理,路径上的边加一即可,最后遍历dfs序即可求出每条边出现多少次。同时还要考虑如何求树上某一条路径的权值。

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
const int maxn = 3e5+10;
struct edge{
    int v,w,net;
}e[maxn<<1];
int head[maxn];
int tot;
int dfn[maxn];
int d[maxn];
int d1[maxn];
int dis[maxn];
int f[maxn][21];
int l[maxn];
int cnt;
pair <int ,int> road[maxn];
int n,m;
void add(int u,int v,int w)
{
    e[++tot].v=v;
    e[tot].w=w;
    e[tot].net=head[u];
    head[u]=tot;
}
void dfs(int u,int pre,int dep)
{
    dfn[++cnt]=u;
    d[u]=dep;
    f[u][0]=pre;
    for(int i=head[u];i;i=e[i].net)
    {
        int v=e[i].v;
        if(d[v]||v==pre) continue;
        dis[v]=dis[u]+e[i].w;
        dfs(v,u,dep+1);
    }
}
void st(int n)
{
    for(int j=1;j<=20;j++)
        for(int i=1;i<=n;i++)
            f[i][j]=f[f[i][j-1]][j-1];
}
int lca(int x,int y)
{
    if(d[x]<d[y]) swap(x,y);
    for(int i=20;i>=0;i--)
    {
        if(d[f[x][i]]>=d[y]) x=f[x][i];
    }
    if(x==y) return x;
    for(int i=20;i>=0;i--)
    {
        if(f[x][i]!=f[y][i])
        {
            x=f[x][i];
            y=f[y][i];
        }
    }
    return f[x][0];
}
bool check(int mid)
{
    int s=0,maxx=0;
    memset(d1,0,sizeof d1);
    for(int i=1;i<=m;i++)
    {
        int x=road[i].first,y=road[i].second;
        int p=l[i];
        int dist=dis[x]+dis[y]-2*dis[p];
        if(dist>mid)
        {
            d1[x]++,d1[y]++,d1[p]-=2;
            maxx=max(maxx,dist-mid);
            s++;
        }
    }
    if(!s) return true;
    for(int i=n;i>=1;i--)
    {
        int u=dfn[i];
        d1[f[u][0]]+=d1[u];
    }
    for(int i=1;i<=n;i++)
    {
        if(d1[i]==s&&dis[i]-dis[f[i][0]]>=maxx)
            return true;
    }
    return false;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n>>m;
    for(int i=1;i<n;i++)
    {
        int a,b,t;
        cin>>a>>b>>t;
        add(a,b,t),add(b,a,t);
    }
    dfs(1,0,1);
    st(n);
    for(int i=1;i<=m;i++)
    {
        int u,v;
        cin>>u>>v;
        road[i]={u,v};
        l[i]=lca(u,v);
    }
    int l=0,r=1e9;
    while(l<=r)
    {
        int mid = l+r>>1;
        if(check(mid)) r=mid-1;
        else l=mid+1;
    }
    cout<<r+1<<endl;
}

松鼠的新家

思路:[P3258 JLOI2014]松鼠的新家 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

思路:树上差分修改即可。

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
const int maxn = 3e5+10;
struct edge{
    int v,net;
}e[maxn<<1];
int head[maxn];
int f[maxn][22];
int d[maxn];
int s[maxn];
int ans[maxn];
int tot;
int n;
int a[maxn];
void add(int u,int v)
{
    e[++tot].v=v;
    e[tot].net=head[u];
    head[u]=tot;
}
void dfs(int u,int pre)
{
    d[u]=d[pre]+1;
    f[u][0]=pre;
    for(int i=head[u];i;i=e[i].net)
    {
        int v=e[i].v;
        if(v==pre) continue;
        dfs(v,u);
    }
}
void st(int n)
{
    for(int j=1;j<=20;j++)
        for(int i=1;i<=n;i++)
            f[i][j]=f[f[i][j-1]][j-1];
}
int lca(int x,int y)
{
    if(d[x]<d[y]) swap(x,y);
    for(int i=20;i>=0;i--)
    {
        if(d[f[x][i]]>=d[y])
            x=f[x][i];
    }
    if(x==y) return x;
    for(int i=20;i>=0;i--)
    {
        if(f[x][i]!=f[y][i])
        {
            x=f[x][i];
            y=f[y][i];
        }
    }
    return f[x][0];
}
void dfs1(int u,int pre)
{
    int sum=s[u];
    for(int i=head[u];i;i=e[i].net)
    {
        int v=e[i].v;
        if(v==pre) continue;
        dfs1(v,u);
        s[u]+=s[v];
    }
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    for(int i=1;i<n;i++)
    {
        int u,v;
        cin>>u>>v;
        add(u,v);add(v,u);
    }
    dfs(1,0);
    st(n);
    int x=a[1],y=a[1];
    for(int i=2;i<=n;i++)
    {
        x=y;y=a[i];
        int p=lca(x,y);
        s[x]++;s[y]++;
        s[p]--;s[f[p][0]]--;
    }
    dfs1(1,0);
    s[a[1]]++;
    for(int i=1;i<=n;i++)
    {
        cout<<s[i]-1<<endl;
    }
}

疫情控制

357. 疫情控制 - AcWing题库

思路:题目是要求最小的最大值,因此可以考虑用二分答案求解。可以想到当军队越靠近根节点时,能管辖的叶子结点就越多。考虑军队的两种情况,一种是可以到达根节点,另一种是不能到达根节点。对于不能到达根节点的军队,最好的策略是让他在规定时间内尽可能走到深度越浅的节点。对于可以到达根节点的军队,考虑两种情况,一是是否跨越根节点去管辖其它子树,而是选择驻扎在当前节点。若一直军队当前在节点s,且在剩余的时间内无法从s节点到根节点在返回s节点的话,最优情况是让其驻扎当前节点,而不是由其它子树的军队来管辖s节点(详细证明可见蓝书)。队伍未缺点驻扎的军队求采取这个引理判断。这题思路比较简单,但是代码实现稍微复杂

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
using namespace std;
typedef long long ll;
typedef pair <ll,int> pli;
const int maxn = 5e4+10;
struct edge{
    int v,w,net;
}e[maxn<<1];
int head[maxn];
int tot;
int f[maxn][30];
int dfn[maxn];
int d[maxn];
ll dis[maxn];
pli a[maxn];
int q[maxn];
int cover[maxn];
int son[maxn];
int ones[maxn];
int vis[maxn];
int cnt,snt;
int n,m;
void add(int u,int v,int w)
{
    e[++tot].v=v;
    e[tot].w=w;
    e[tot].net=head[u];
    head[u]=tot;
}
void dfs(int u,int pre)
{
    d[u]=d[pre]+1;
    f[u][0]=pre;
    for(int i=head[u];i;i=e[i].net)
    {
        int v=e[i].v;
        if(d[v]||v==pre) continue;
        dis[v]=dis[u]+e[i].w;
        dfs(v,u);
    }
}
void st(int n)
{
    for(int j=1;j<=20;j++)
        for(int i=1;i<=n;i++)
            f[i][j]=f[f[i][j-1]][j-1];
}
pli up(int x,ll mid)
{
    for(int i=20;i>=0;i--)
    {
        if(f[x][i]>1&&dis[x]-dis[f[x][i]]<=mid)
        {
            mid-=dis[x]-dis[f[x][i]];
            x=f[x][i];
        }
    }
    return {mid,x};
}
void dfs1(int u,int pre)
{
    bool flag1=true;
    bool flag2=true;
    for(int i=head[u];i;i=e[i].net)
    {
        int v=e[i].v;
        if(v==pre) continue;
        dfs1(v,u);

        flag1 &= cover[v];
        flag2=0;
        if(u==1&&!cover[v]) son[++snt]=v;
    }
    cover[u]=ones[u]||(!flag2&&flag1);
}
bool cmp(int a,int b)
{
    return dis[a]<dis[b];
}
bool check(ll mid)
{
    memset(ones,0,sizeof ones);
    memset(cover,0,sizeof cover);
    memset(vis,0,sizeof vis);
    cnt=0,snt=0;
    
    for(int i=1;i<=m;i++)
    {
        pli ar=up(q[i],mid);
        ll rest=ar.first;
        int pos=ar.second;

        if(rest<=dis[pos]) ones[pos]=1;
        else a[++cnt]={rest-dis[pos],pos};
    }

    dfs1(1,-1);
    sort(a+1,a+1+cnt);
    for(int i=1;i<=cnt;i++)
    {
        ll rest=a[i].first;
        int pos=a[i].second;
        if(!cover[pos]&&rest<dis[pos])
        {
            cover[pos]=true;
            vis[i]=true;
        }
    }

    sort(son+1,son+1+snt,cmp);
    for(int i=1,j=1;i<=snt;i++)
    {
        int pos=son[i];
        if(cover[pos]) continue;
        while(j<=cnt&&(vis[j]||a[j].first<dis[pos])) j++;
        if(j>cnt) return false;
        j++;
    }
    return true;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n;
    ll s=0;
    for(int i=1;i<n;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        add(u,v,w);add(v,u,w);
        s+=w;
    }
    dfs(1,0);
    st(n);
    cin>>m;
    for(int i=1;i<=m;i++)
    {
        cin>>q[i];
    }
    ll l=0,r=s;
    int flag=0;
    while(l<r)
    {
        ll mid = l+r>>1;
        if(check(mid))
        {
            flag=1;
            r=mid;
        }
        else l=mid+1;
    }
    if(!flag) cout<<-1<<endl;
    else cout<<r<<endl;
}

异象石

355. 异象石 - AcWing题库

思路:本题有一个引理:对整棵树进行dfs,求出每个点的时间戳(dfs序),然后可以发现如果按照时间戳(dfs序)从小到大的顺序,把节点排成一圈(首尾相连),
然后累加相邻两个节点之间的路径长度,最后得到的结果恰好是所求答案的两倍。数学归纳法可证明。

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <set>
using namespace std;
typedef long long ll;
const int maxn = 1e5+10;
struct edge{
    int v,w,net;
}e[maxn<<1];
int head[maxn];
int tot;
int d[maxn];
int f[maxn][22];
int dfn[maxn];
int pos[maxn];
ll dis[maxn];
int cnt=0;
int n,m;
set <int > s;
void add(int u,int v,int w)
{
    e[++tot].v=v;
    e[tot].w=w;
    e[tot].net=head[u];
    head[u]=tot;
}
void dfs(int u,int pre)
{
    pos[++cnt]=u;
    dfn[u]=cnt;
    f[u][0]=pre;
    d[u]=d[pre]+1;
    for(int i=head[u];i;i=e[i].net)
    {
        int v=e[i].v;
        if(v==pre) continue;
        dis[v]=dis[u]+e[i].w;
        dfs(v,u);
    }
}
void ST(int n)
{
    for(int j=1;j<=20;j++)
        for(int i=1;i<=n;i++)
            f[i][j]=f[f[i][j-1]][j-1];
}
int lca(int x,int y)
{
    if(d[x]<d[y]) swap(x,y);
    for(int i=20;i>=0;i--)
    {
        if(d[f[x][i]]>=d[y])
        {
            x=f[x][i];
        }
    }
    if(x==y) return x;
    for(int i=20;i>=0;i--)
    {
        if(f[x][i]!=f[y][i])
        {
            x=f[x][i];
            y=f[y][i];
        }
    }
    return f[x][0];
}
ll getdis(int x,int y)
{
    int p =lca(x,y);
    return dis[x]+dis[y]-2*dis[p];
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>n;
    for(int i=1;i<n;i++)
    {
        int u,v,w;
        cin>>u>>v>>w;
        add(u,v,w),add(v,u,w);
    }
    dfs(1,0);
    ST(n);
    ll res=0;
    cin>>m;
    while(m--)
    {
        char op;
        cin>>op;
        if(op=='+')
        {
            int x;
            cin>>x;
            s.insert(dfn[x]);
            auto it=s.find(dfn[x]);
            auto lit=it,rit=it;
            int l,r;
            if(lit==s.begin())
            {
                lit=s.end();
                lit--;
                l=pos[*lit];
            }
            else
            {
                lit--;
                l=pos[*lit];
            }
            if((++rit)==s.end())
            {
                rit=s.begin();
                r=pos[*rit];
            }
            else
            {
                r=pos[*rit];
            }
            res=res+getdis(l,x)+getdis(x,r)-getdis(l,r);
        }
        else if(op=='-')
        {
            int x;
            cin>>x;

            auto it=s.find(dfn[x]);
            auto lit=it,rit=it;
            int l,r;
            if(lit==s.begin())
            {
                lit=s.end();
                lit--;
                l=pos[*lit];
            }
            else
            {
                lit--;
                l=pos[*lit];
            }
            if((++rit)==s.end())
            {
                rit=s.begin();
                r=pos[*rit];
            }
            else
            {
                r=pos[*rit];
            }
            s.erase(it);
            res=res-getdis(l,x)-getdis(x,r)+getdis(l,r);
        }
        else
        {
            cout<<res/2<<endl;
        }
    }
}

杰哥,你带我走吧!杰哥

C-杰哥,你带我走吧!杰哥_武汉科技大学第十一届程序设计校赛

思路:lca问题,预处理除不同k值的路径长度。

#include <iostream>
#include <algorithm> 
#include <cstdio>
#include <string>
#include <cstring>
using namespace std;
typedef long long ll;
const int mod = 1e9+7;
const int maxn = 1e5+5;
struct edge{
	int v,net;
}e[maxn<<1];
struct dat {
	int x, y, k;
	int id, ans;
} nod[maxn];
int head[maxn];
int tot;
int d[maxn];
//int dis[51][maxn][18];
ll dis[maxn][51];
int f[maxn][18];
int a[maxn];
int w[51][maxn];
int fpow(ll a,int b)
{
	ll res=1;
	for(;b;b>>=1)
	{
		if(b&1)
		{
			res=res*a%mod;
		}
		a=a*a%mod;
	}
	return res;
}
void add(int u,int v)
{
	e[++tot].v=v;
	e[tot].net=head[u];
	head[u]=tot;
}
void dfs(int u,int pre)
{
	d[u]=d[pre]+1;
	f[u][0]=pre;
	for(int i=head[u];i;i=e[i].net)
	{
		int v=e[i].v;
		if(v==pre) continue;
		dfs(v,u);
	}
}
void dfs(int u,int pre,int k)
{
    // cout<<"AA"<<endl;
    for(int i=head[u];i;i=e[i].net)
    {
        int v=e[i].v;
        if(v==pre) continue;
        dis[v][k]=(dis[u][k]+w[k][v])%mod;
        dfs(v,u,k);
    }
}
void st(int n)
{
	for(int j=1;j<=17;j++)
	{
		for(int i=1;i<=n;i++)
		{
			f[i][j]=f[f[i][j-1]][j-1];
		}
	}
}
int lca(int x,int y)
{
	if(d[x]<d[y])
		swap(x,y);
	for(int i=17;i>=0;i--)
	{
		if(d[f[x][i]]>=d[y])
		{
			x=f[x][i];
		}
	}
	if(x==y) return x;
	for(int i=17;i>=0;i--)
	{
		if(f[x][i]!=f[y][i])
		{
			x=f[x][i];
			y=f[y][i];
		}
	}
	return f[x][0];
}
int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
	}
	for(int i=1;i<=50;i++)
	{
		for(int j=1;j<=n;j++)
		{
			ll x=fpow(a[j],i);
			w[i][j]=x;
		}
	}
	for(int i=1;i<n;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
		add(v,u);
	}
	dfs(1,0);
	st(n);
    for(int i=1;i<=50;i++)
    {
        dfs(1,0,i);
    }
	while(m--)
	{
		int x,y,k;
		scanf("%d%d%d",&x,&y,&k);
		int p=lca(x,y);
        // cout<<"p="<<p<<endl;
        ll ans=(dis[x][k]+dis[y][k]-2*dis[p][k]+w[k][p]+2*mod)%mod;
		cout<<ans<<endl;
	}
}

雨天的尾巴

353. 雨天的尾巴 - AcWing题库

思路:

posted @ 2023-03-19 23:38  Yuuu7  阅读(13)  评论(0编辑  收藏  举报