《算法竞赛进阶指南》------图论篇1

0x08 Picnic Planning k限制最小生成树

Picnic Planning
题意:
在这里插入图片描述
思路: 迪杰特斯拉 限制树 回溯找最大边 得到多个森林 限制k

#include<iostream>
#include<cstring>
#include<map>
#include<queue>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
int a[30][30];
int connect[30][30];
int vis[30];
int fa[30];
int INF;
ll sum=0;
int cnt,use;
map<string,int> name;
int find(int x){
    return (x==fa[x]) ?  x:fa[x]=find(fa[x]);
}
void kruskal(int l,int r)
{
    priority_queue< pair<int,pair<int,int>> > q;
    for(int i=l;i<=cnt;i++){
        for(int j=l+1;j<=cnt;j++){
            q.push(make_pair(-a[i][j],make_pair(i,j)));
        }
    }
    int x,y,fx,fy;
    int w;
    while(!q.empty())
    {
        w=-q.top().first;
        x=q.top().second.first;
        y=q.top().second.second;

        q.pop();
        fx=find(x);
        fy=find(y);
        if(fx==fy)continue;
        fa[fy]=fa[fx];
        sum+=w;
        connect[x][y]=connect[y][x]=1;
        if(x==1 or y==1)  use++; 
    }
    return ;
}
pair<int,int> dfs_max(int u)
{   
    vis[u]=1;
    pair<int,int> edge1=make_pair(0,0);
    if(connect[1][u]==1){
        return edge1=make_pair(1,u);
    }
    pair<int,int> edge2;
    for(int v=2;v<=cnt;v++){
        if(connect[u][v]==0 or vis[v] ) continue;
        edge2=dfs_max(v);
        if(edge2.first==0) continue;
        if(a[u][v]  > a[edge1.first][edge1.second]) edge1=make_pair(u,v);
        if(a[edge2.first][edge2.second] > a[edge1.first][edge1.second]) edge1=edge2;
    }
    return edge1;
}
void release(){
    pair<int,int> edge1,edge2;
    edge1=make_pair(0,0);
    int id=1;
    for(int i=2;i<=cnt;i++){
        if(connect[1][i]) continue;
        memset(vis,0,sizeof(vis));
        edge2=dfs_max(i);
        if(a[edge2.first][edge2.second] - a[1][i]  >a[edge1.first][edge1.second]){
            edge1=edge2;
            id=i;
        }
    }
    // cout<<id<<endl;
    sum=sum-a[edge1.first][edge1.second]+a[1][id];
    connect[1][id]=connect[id][1]=1;
    connect[edge1.first][edge1.second]=connect[edge1.second][edge1.first]=0;
}
void solve(){
    name["Park"]=1;
    cnt=1;
    memset(connect,0,sizeof(connect));
    memset(a,0x3f,sizeof(a));
    INF=a[0][0];
    a[0][0]=0;
    rep(i,1,29) 
    {
        a[i][i]=0;
        connect[i][i]=1;
        fa[i]=i;
    }
    int n;
    cin>>n;
    string s1,s2;
    int  u,v,w;
    rep(i,1,n){
        cin>>s1>>s2;
        // scanf("%s %s",s1,s2);
        scanf("%d",&w);
        if(!name[s1]) name[s1]=++cnt;
        if(!name[s2]) name[s2]=++cnt;
        u=name[s1]; v=name[s2];
        a[u][v]=min(a[u][v],w);
        a[v][u]=a[u][v];
        // connect[u][v]=connect[v][u]=1;
    }
    int k;
    cin>>k;
    // cout<<"case1"<<endl;
    kruskal(2,cnt);
    kruskal(1,cnt);
    // cout<<sum<<endl;
    // cout<<"case2"<<endl;
    for(int i=use+1;i<=min(k,cnt);i++){
        release();
    }
    cout<<"Total miles driven: "<<sum<<endl;
}
int main (){
  //freopen("in.txt","r",stdin);
  //freopen("out.txt","w",stdout);
  solve();
  getchar();
  getchar();
  return 0;
}

0x09 Desert King POJ - 2728 (01规划,prime)

Desert King
题意:n(n<=1000)个村庄处在不同的海拔,现需要连通n个村庄,但不同成环,连通的边有成本和收益。
输入给出了每个村庄的位置(x,y),以及海拔高度。
收益即是两个村庄的距离,成本即是高度差的绝对值。
∑ H e i g h t ∑ d i s \frac{\sum{Height}}{\sum{dis}} disHeight最小?
思路: 二分答案, 朴素prim算法(这道题不用用优先队列能过,用会超时…没想明白留个坑)
∑ H e i g h t ∑ d i s < = k \frac{\sum{Height}}{\sum{dis}}<= k disHeight<=k
( H 1 − d 1 ∗ k ) + ( H 2 − d 2 ∗ k ) + . . . . + ( H n − d n ∗ k ) < = 0 (H_1-d_1*k)+(H_2-d_2*k)+....+(H_n-d_n*k)<=0 (H1d1k)+(H2d2k)+....+(Hndnk)<=0
这只要二分k即可, 将 ( H i − d i ∗ k ) (H_i-d_i*k) (Hidik) 看成两个村庄的权值,用prim算法得到最小结果和(sum)。
如果sum<=0 说明k可行 减小,否则不可行 增大
ACcode

#include<iostream>
#include<cstring>
#include<queue>
#include<cmath>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
const double eps=1e-5;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
struct  zw
{ 
    ll x,y;
    ll h;
}a[1010];
double  dis[1010][1010],H[1010][1010];
int vis[N];
double d[N];
int n;
double getdis(int i,int j){
    return sqrt( (double)(a[i].x-a[j].x)*(a[i].x-a[j].x) + (double)((a[i].y-a[j].y)*(a[i].y-a[j].y)));
}
bool nice(double k){
    priority_queue< pair<double , int> > q;
    double value;
    double sum=0;
    rep(i,0,n){
        d[i]=1e11;
        vis[i]=0;
    }
    d[1]=0;
    int x;
    for(int i=1;i<n;i++){
        x=0;
        for(int j=1;j<=n;j++){
            if(!vis[j] and ( x==0 or d[x] > d[j])) x=j;
        }
        vis[x]=1;
        for(int y=1;y<=n;y++){
            if(!vis[y]) d[y]=min(d[y],H[x][y] - k*dis[x][y]);
        }
    }
    for(int i=1;i<=n;i++) sum+=d[i];
    if(sum<=0) return true; 
    else return false;
}
void init(){

}
void solve(){
    n=read();
    while(n){
        rep(i,1,n){
            a[i].x=read();
            a[i].y=read();
            a[i].h=read();
        }
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                dis[i][j]=getdis(i,j);
                H[i][j]=abs( (double)(a[i].h-a[j].h));
            }
        }
        //二分
        double l=0,r=1e11;
        while(fabs(l-r)>eps){
            double mid=(l+r)/2;
            if(nice(mid) ){
                // cout<<"true"<<endl;
                r=mid;
            }else{
                // cout<<"false"<<endl;
                l=mid;
            }
        }
        printf("%.3f\n",r);
        n=read();
    }
    return ;
}
int main (){
  //freopen("in.txt","r",stdin);
  //freopen("out.txt","w",stdout);
  solve();
//   getchar();
//   getchar();
  return 0;
}

0x0A 黑暗城堡 LibreOJ - 10064 (Dijkstra 生成树的过程)

黑暗城堡
题意:给一无向图,要求
在这里插入图片描述
思路: 模拟Dijkstra ,对于他实现的过程要理解透彻。每次对于一个点vis[u]=1.
问是多少种不同这样的修建方案。
当u被vis了。就知道u最短距离。 v满足dis[u]=dis[v]+edge(u,v)到u的可能就多一种. 其中v为u的邻边的点。
代码如下:

#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=1e6+10;
const ll P=2147483647;      //(1<<31)-1
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
ll head[N],nex[N],var[N],value[N];
ll cnt;
void addedge(ll u,ll v,ll w){
    var[++cnt]=v;
    value[cnt]=w;
    nex[cnt]=head[u];
    head[u]=cnt;
}
ll dis[1010];
bool vis[1010];
pair<ll, int >  p[1010];
ll num[1010];
void djk(ll n){
    // 初始化
    rep(i,1,n){
        dis[i]=0x3f3f3f3f;
        vis[i]=0;
    }
    priority_queue< pair<ll,int> > q;
    q.push(make_pair(0,1));
    dis[1]=0;
    while(!q.empty()){
        // cout<<"cnt"<<endl;
        ll u=q.top().second;
        q.pop();
        if(vis[u]) continue;
        vis[u]=1;
        for(int i=head[u];i;i=nex[i]){
            if(dis[u] == dis[var[i]] + value[i]) num[u]++;
        }
        for(int i=head[u];i;i=nex[i]){
            ll v=var[i];
            ll w=value[i];
            if(vis[v]) continue;
            if(dis[v] > dis[u] + w ){
                dis[v]=dis[u]+w;
                q.push(make_pair(-dis[v],v));
            }
        }
    }
    num[1]=1;
   
    return ;
}
void solve(){
    // cout<<P<<endl;
     int n,m;
     ll u,v,w;
     n=read();
     m=read();
     rep(i,1,m){
         u=read();
         v=read();
         w=read();
        addedge(u,v,w);
        addedge(v,u,w);

    }
    djk(n);
    // for(int i=1;i<=n;i++) printf("i: %d,dis: %lld\n",i,dis[i]);
    //  rep(i,1,n) cout<<num[i]<<" ";
    ll sum=1;
    rep(i,1,n){
        sum=sum*num[i]%P;
    }
    cout<<sum<<endl;
    return ;
}
int main (){
//   freopen("in.txt","r",stdin);
  //freopen("out.txt","w",stdout);
  solve();
//   system("pause");
  getchar();
  getchar();
  return 0;
}

0x0B patrol 巡逻 黑暗爆炸 - 1912 (树的直径)

patrol 巡逻 黑暗爆炸 - 1912
ACwing巡逻
在写这道题之前。
蒟蒻补充一个知识点----树的直径
来源:《算法进阶指南》
树中最远两个点的距离称为树的直径
直径求法:两种方法都是o(n)
1.树形DP
可以适用在权值为正值和负值的树。

int vis[N];
ll D[N], maxdis;
void dfs(int x){
    vis[x]=1;
    for(int i=head[x];i;i=nex[i]){
        int y=var[i];
        if(vis[y]) continue;
        dfs(y);
        maxdis=max(maxdis,D[x]+D[y]+value[i]);
        D[x]=max(D[x],D[y]+value[i]);
     }
     return ;
}

在这里插入图片描述

2.两次BFS实现
特点:能得到直径的距离经过的节点。 实现容易

int dis[N],pre[N];

int bfs(int u)
{   
    rep(i,1,n) dis[i]=0x3f3f3f3f;
    queue<int> q;
    q.push(u);
    dis[u]=0;
    pre[u]=0;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=nex[i]){
            int y=var[i];
            if(dis[y]==0x3f3f3f3f){
                dis[y]=dis[x]+value[i];
                pre[y]=i;
                q.push(y);
            }
        }
    }
    int id=1;
    rep(i,2,n){
        if(dis[i] > dis[id] ) id=i; 
    }
    return id; 
}

在这里插入图片描述
题意
有多个村庄和一警察局编号1,他们道路连接是树形结构,每条道路距离都是1,警察每天要到达各个村庄,然后回局,可以在树中只能添加一条或两条。问从出发到回局能最短距离是多少?
思路
BFS 得到一条最远距离,将该路径距离置为-1,maxdis1
然后用DP求添加第二条路,的最远距离。 maxdis2
结果:2*(n-1)-maxdis1+1-maxdis2+1
ACcode

#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
ll head[N],nex[N],var[N],value[N];
ll pre[N];  // 存放第几条边
ll dis[N];
ll D[N];
int vis[N];
ll cnt=1;
int n,k;
void addedge(int u,int v,ll w){
    var[++cnt]=v;
    value[cnt]=w;
    nex[cnt]=head[u];
    head[u]=cnt;
}
int bfs(int u)
{   
    rep(i,1,n) dis[i]=0x3f3f3f3f;
    queue<int> q;
    q.push(u);
    dis[u]=0;
    pre[u]=0;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=nex[i]){
            int y=var[i];
            if(dis[y]==0x3f3f3f3f){
                dis[y]=dis[x]+value[i];
                pre[y]=i;
                q.push(y);
            }
        }
    }
    int id=1;
    rep(i,2,n){
        if(dis[i] > dis[id] ) id=i; 
    }
    return id; 
}
ll maxdis2;
void dfs(int x){
    vis[x]=1;
    for(int i=head[x];i;i=nex[i]){
        int y=var[i];
        if(vis[y]) continue;
        dfs(y);
        maxdis2=max(maxdis2,D[x]+D[y]+value[i]);
        D[x]=max(D[x],D[y]+value[i]);
    }
}
void solve(){
    n=read();
    k=read();
    ll u,v;
    rep(i,1,n-1){
        u=read();
        v=read();
        addedge(u,v,1);
        addedge(v,u,1);
    }
    // 两次dfs 得出树的最长路径,并且标记-1
    int p=bfs(1);
    int q=bfs(p);
    ll  maxdis1=dis[q];
    for(;pre[q];q=var[pre[q]^1]) value[pre[q]] = value[pre[q]^1] = -1;
    ll sum=2*(n-1);
    sum=sum-maxdis1+1;
    if(k==2){
        dfs(1);
        sum=sum-maxdis2+1;
    }
    printf("%lld\n",sum);
    return ;
}
int main (){
  //freopen("in.txt","r",stdin);
  //freopen("out.txt","w",stdout);
  solve();
  getchar();
  getchar();
  return 0;
}

0x0C Core树网的核 黑暗爆炸 - 1999 (树的直径+最小偏心距)

传送门
题意:在一个无环连通图(树)中,在树直径上选取一小段路径距离<=s。这段路径两端是节点,也可两端是同一个节点。
问:选取一段在直径上的路径,问该路径到树上各个点的最小偏心距是多少。
思路:
1.所有的直径必定相交,而且各直径的终点汇聚于同一处
2.在任意一条直径上求出的最小偏心距都相等。
在这里插入图片描述
然后节点p到节点q距离需要满足<=s.
每一次二分,对p-q路径所有节点dfs,得到一个最大距离mx。
如果mx > bitsum, 那就false,应该最选取比bitsum大的。
代码构造
用前向性建图,
一组反向边 编号 i 和(i^1) (cnt起始1)
pre[节点]=编号
得到一条直径上所有节点和距离。
然后二分细节: l<=r r=mid-1 l=mid+1
ACcode

// submitted  by HNUST26
#include<bits/stdc++.h>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=1e6+10;
const ll P=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
ll n,s;
ll head[N],nex[N],var[N],value[N];
ll cnt=1;
void addedge(ll u,ll v,ll w){
    var[++cnt]=v;
    value[cnt]=w;
    nex[cnt]=head[u];
    head[u]=cnt;
}
ll pre[N],vis[N];
ll dis[N];
ll sum=0;
vector<ll> path;
ll bfs(ll u){
    rep(i,1,n) dis[i]=0x3f3f3f3f;
    queue<ll> q;
    q.push(u);
    dis[u]=0;
    pre[u]=0;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=nex[i]){
            ll y=var[i];
            if(dis[y] == 0x3f3f3f3f){
                dis[y]=dis[x]+value[i];
                pre[y]=i;
                q.push(y);
            } 
        }
    }
    int id=1;
    rep(i,2,n){
        if(dis[i] > dis[id]) id=i;
    } 
    return id;
}
ll mx=0;
void dfs1(ll x,ll fa,ll sm){
    mx=max(mx,sm);
    for(int i=head[x];i;i=nex[i]){
        if(var[i]==fa or vis[var[i]]) continue;
        dfs1(var[i],x,sm+value[i]);
    }
    return ;
}
bool check(ll now){
    ll i=0,j=0;
    ll x,y;
    for(int k=0;k<path.size();k++){
        if(dis[path[k]] <=now) {
            i=k;
            x=path[k];
        }
    }
    ll maxdis=dis[path[(int)path.size()-1]];
    for(int k=(int)path.size()-1;k>=0;k--){
        if(maxdis-dis[path[k]] <= now){
            j=k;
            y=path[k];
        }
    }
    if(maxdis-dis[x]-(maxdis-dis[y]) >s ) return false;
    for(int k=i;k<=j;k++) vis[path[k]]=1;
    // cout<<path[k]<<" ";
    // cout<<endl;
    mx=0;
    for(int k=i;k<=j;k++) dfs1(path[k],0,0);
    for(int k=i;k<=j;k++) vis[path[k]]=0;
    if(mx>now) return false;
    else return true;
}
void solve(){
    n=read();
    s=read();
    ll u,v,w;
    rep(i,1,n-1){
        u=read();
        v=read();
        w=read();
        addedge(u,v,w);
        addedge(v,u,w);
    }
    ll p,q;
    p=bfs(1);
    q=bfs(p);
    sum=dis[q];
    // cout<<sum<<endl;
    // cout<<p<<" "<<q<<endl;
    int q1=q;
    ll ans=0;
    for(;pre[q1];q1=var[pre[q1]^1]){
        path.push_back(q1);
        dis[q1]=ans;
        ans+=value[pre[q1]];
    } 
    path.push_back(p);
    dis[p]=ans;
    // for(int i=0;i<path.size();i++) cout<<path[i]<<" ";
    // cout<<endl;
    // for(int i=0;i<path.size();i++) cout<<dis[path[i]]<<" ";
    ll l,r;
    l=0;
     r=2e9+10;
    // r=26;
    while(l<=r){
        ll mid=(l+r)>>1;
        if(check(mid)){
            ans=mid;
            r=mid-1;
            // cout<<"mid:"<<mid<<endl;
        }else{
            l=mid+1;
        }
    }
    // cout<<"endl"<<endl;
    cout<<ans<<endl;
    return ;
}
int main (){
  //freopen("in.txt","r",stdin);
  //freopen("out.txt","w",stdout);
  solve();
  getchar();
  getchar();
  return 0;
}

LCA(最近公共祖先)

向上倍增 , 一次lCA 时间o(logn)
板子

ll head[N],nex[N],value[N],var[N];
int fa[N][25],d[N];   //深度
ll ans[N];
ll cnt=0;
void addedge(int u,int v,ll val)
{ 
    var[++cnt] = v;
    value[cnt] = val;
    nex[cnt] = head[u];
    head[u] = cnt;
}
void bfs(int u){
     queue<int> q;
     q.push(u);
     d[u]=1;
     while(!q.empty())
     {
         int x=q.front();
         q.pop();
         for(int i=head[x];i;i=nex[i])
         {
             int y=var[i];
             if(d[y]) continue;
             d[y] = d[x] +1;
             fa[y][0] = x;
             for(int i=1;i<=20;i++)
             {   
                // y+2^i  =  y+2^(i-1) + 2^(i-1)
                 fa[y][i] = fa[fa[y][i-1]][i-1];
             }
             q.push(y);
         }
     }
}
int lca(int x,int y)
{
    if(d[x] > d[y] ) swap(x,y);
    for(int i=20;i>=0;i--){
        if(d[fa[y][i]] >= d[x]){
            y=fa[y][i];
        }
    }
    if(x==y) return x;
    for(int i=20;i>=0;i--){
        if(fa[x][i] != fa[y][i])
        {
            x=fa[x][i];
            y=fa[y][i];
        }
    }
    return fa[x][0];
}

LCA 的tarjan算法 (最近公共祖先)

解决的问题给定一颗树,m组LCA查询,时间复杂度在o(n+m)
算法流程:
在下图的一颗树中, 问 (5,6),(1,5)的LCA是哪个?
在遍历这棵树 用深度搜索
在这里插入图片描述在这里插入图片描述

vis 标记节点 0 未访问 1 访问了但没回溯 2访问了也回溯到了

从1节点开始,深搜到6节点,经过路径的节点vis标记1
之后回溯 到 2号节点,现在的vis标记是:1 1 0 2 1 2 (vis[1]=1,指1号节点访问了未回溯到)
然后访问到了 5。 问(5,6),(1,5)的LCA ?
因为6的vis是2。 (5,6)的LCA一定在5到根节点的路径上(1—>2—>5)
最近的是 2 ,所以LCA(5,6)=2;
这里2怎么做到在0(1)就能找出呢?用并查集 回溯到的组成一个集合即是(4,6)共用一个祖先2。
还不明白?没关系 ,看看后面代码~

在这里插入图片描述
板子

int fa[N];
int vis[N];   // 1访问 2范问且回溯 0 为访问
int get(int x)
{
    return x==fa[x] ? x:fa[x]=get(fa[x]);
}
void tarjan(int x)
{
    vis[x]=1;
    for(int i=head[x];i;i=nex[i])
    {
        if(vis[var[i]]) continue;
        
        tarjan(var[i]);
        fa[var[i]] = x;
    }
    for(int i=0;i<query[x].size();i++)
    {
        int y=query[x][i];
        if(vis[y]==2)
        {
            ans[query_id[x][i]] = get(y);
        }
    }
    vis[x]=2;
    return ; 
}

0x0D network POJ3417 (tarjan在线lca+树上差分)

network
题意: 在无向连通图中分为主要边和附加边,N-1条主要边,任意两个节点存在只由主要边构成的路径。第一步切断主要边,第二步切断附加边,图被斩为两部分。现问:一共有多少种方案可以实现。
思路:分析知道主要边构成一颗树,附加边是非树边。现在就看看添加非树边,对形成的环的边+1,即是说明能覆盖了一次。后面以此。
对于边上覆盖0次的边,则第二步可以切断任意一条附加边。
在这里插入图片描述
这里提供了两种解法:

  1. 离线求解的附件边的 形成的环。 然后lca求得公共祖先,树上差分。 复杂度O(n+Mlogn)
    ACcode:
#include<iostream>
#include<vector>
#include<queue>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=4e5+10;
const ll P=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
ll head[N],nex[N],value[N],var[N];
int fa[N][25],d[N];   //深度
ll ans[N];
ll cnt=0;
void addedge(int u,int v,ll val)
{ 
    var[++cnt] = v;
    value[cnt] = val;
    nex[cnt] = head[u];
    head[u] = cnt;
}
void bfs(int u){
     queue<int> q;
     q.push(u);
     d[u]=1;
     while(!q.empty())
     {
         int x=q.front();
         q.pop();
         for(int i=head[x];i;i=nex[i])
         {
             int y=var[i];
             if(d[y]) continue;
             d[y] = d[x] +1;
             fa[y][0] = x;
             for(int i=1;i<=20;i++)
             {   
                // y+2^i  =  y+2^(i-1) + 2^(i-1)
                 fa[y][i] = fa[fa[y][i-1]][i-1];
             }
             q.push(y);
         }
     }
}
int lca(int x,int y)
{
    if(d[x] > d[y] ) swap(x,y);
    for(int i=20;i>=0;i--){
        if(d[fa[y][i]] >= d[x]){
            y=fa[y][i];
        }
    }
    if(x==y) return x;
    for(int i=20;i>=0;i--){
        if(fa[x][i] != fa[y][i])
        {
            x=fa[x][i];
            y=fa[y][i];
        }
    }
    return fa[x][0];
}
int dd[N],vs[N],vis[N];
void dfs(int u){
    int w=dd[u];
    vis[u]=1;
    for(int i=head[u];i;i=nex[i]){
        int v=var[i];
        if(vis[v]) continue;
        dfs(v);
        w+=vs[v];
    }
    vs[u] = w;
    return ;
}
void solve(){
    int n,m;
    n=read();
    m=read();
    int u,v;
    rep(i,1,n-1){
        u=read();
        v=read();
        addedge(u,v,0);
        addedge(v,u,0);
    }
    bfs(1);
    for(int i=1;i<=m;i++){
        u=read();
        v=read();
        dd[u]++;
        dd[v]++;
        dd[lca(u,v)]-=2;
        // cout<<lca(u,v)<<endl;
    }
    dfs(1);
    ll sum=0;
    for(int i=2;i<=n;i++){
        if(vs[i]==0) sum+=m;
        else if(vs[i]==1) sum++; 
    }
    cout<<sum<<endl;
    return ;
}
int main(){
    solve();
    getchar();
    getchar();
    return 0;
}

2.tarjan+树上差分
注意一点: 如果附加边 u==v 特判一下!!! (掉坑里过,de了一天的bug)

#include<iostream>
#include<vector>
#define ll long long
#define ld long double
#define ull unsigned long long
#define rep(i,a,b) for(int i=a;i<=b;i++)
ll gcd(ll a,ll b){ return b? gcd(b,a%b):a;}
const int N=2e5+10;
const ll P=1e9+7;
ll read(){
    ll s = 0, f = 1; char ch = getchar();
    while(!isdigit(ch)){
        if(ch == '-') f = -1;
        ch = getchar();
    }
    while(isdigit(ch)) s = (s << 3) + (s << 1) + (ch ^ 48), ch = getchar();
    return s * f;
}
using namespace std;
int var[N],nex[N],head[N];
int cnt;
int vis[N];
int n,m;
ll sum=0;
int fa[N],d[N];
void addedge(int u,int v){
    var[++cnt]=v;
    nex[cnt] = head[u];
    head[u] = cnt;
}
int head1[N],nex1[N],var1[N];
int cnt1;
void addquery(int u,int v){
    var1[++cnt1] = v;
    nex1[cnt1]  = head1[u];
    head1[u] = cnt1;
}
int get(int x){
    return x==fa[x]? fa[x]:fa[x]=get(fa[x]);
}
void tarjan(int u){
    vis[u] =1; fa[u] = u;
    for(int i=head[u];i;i=nex[i]){
        int v=var[i];
        if(vis[v]) continue;
        tarjan(v);
        fa[v] = u;
    }
    for(int i=head1[u];i;i=nex1[i]){
        int v=var1[i];
        if(vis[v]==2){
            // cout<<"lca"<<get(v)<<endl;
            d[get(v)]-=2;
            // d[u]++;
            // d[v]++;
        }
    }
    vis[u] = 2;
}

void dfs(int u,int dad){
    for(int i=head[u];i;i=nex[i]){
        int v=var[i];
        if(v!=dad){
            dfs(v,u);
            d[u] += d[v];
        }
    }
    if(u==1) return ;
    if(d[u]==1) sum++;
    else if(d[u]==0) sum+=m;
}
void solve(){
    
    n=read();
    m=read();
    rep(i,1,n-1){
        int u=read();
        int v=read();
        addedge(u,v);
        addedge(v,u);
    }
    for(int i=1;i<=m;i++){
        int u=read();
        int v=read();
        if(u==v) continue;
        d[u]++;
        d[v]++;
        addquery(u,v);
        addquery(v,u);
    }
    tarjan(1);
    // for(int i=1;i<=n;i++) printf("i:%d d:%d\n",i,d[i]);
    dfs(1,0);
    cout<<sum<<endl;
    return ;
}
int main (){
  //freopen("in.txt","r",stdin);
  //freopen("out.txt","w",stdout);
  solve();
  getchar();
  getchar();
  return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

axtices

谢谢您的打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值