[算法竞赛进阶指南]图论专题训练

A:最优贸易

  • 题意:
    一个国家里有很多个城市,某件物品在所有城市的价格都不同,你可以在一个城市买,另一个城市卖出来获得利益,但是只能进行一次买卖。然后要从1走到n,1到n有单向,也有双向的。
  • 题解:将图分层。邻接表,spfa求出最长路(最大权值)。有三层,一层是不购买也不卖,第二层是买,一到第二层的边权为负。第三层是卖,二到第三层的边权为正。由于有向边的建立,你不能从第二/三层走回第一层图,这保证了你只做一次买卖,而不是无限做买卖。然后再设置一个最终大终点。
  • 其实这题还有很多方法 参考 https://www.luogu.org/problemnew/solution/P1073
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e9+7;
const ll maxn=1e5+7;
const ll inf=1<<18;
struct u
{
    int v,len;
};
int n,m,v[maxn],d[maxn*3+1];
vector<u> vt[maxn*3+1];//邻接表
template<class T>
void read(T &res)
{
    res = 0;
    char c = getchar();
    T f = 1;
    while(c < '0' || c > '9')
    {
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9')
    {
        res = res * 10 + c - '0';
        c = getchar();
    }
    res *= f;
}
template<class T>
void out(T x)
{
    if(x < 0)
    {
        putchar('-');
        x = -x;
    }
    if(x >= 10)
    {
        out(x / 10);
    }
    putchar('0' + x % 10);
}
queue<int>Q;
bool inq[maxn*3+1];
void add(int x,int y)
{
     vt[x].push_back((u){y,0});
     vt[x+n].push_back((u){y+n,0});
     vt[x+2*n].push_back((u){y+2*n,0});
     vt[x].push_back((u){y+n,-v[x]});
     vt[x+n].push_back((u){y+2*n,v[x]});
     return;
}
void spfa()//最短路
{
    for(int i=1;i<=n;i++) d[i]=-inf;
    d[1]=0;
    inq[1]=true;
    Q.push(1);
    while(!Q.empty())
    {
        int tp=Q.front();
        Q.pop();
        inq[tp]=0;
        int len=vt[tp].size();
        for(int i=0;i<len;i++){
            u x=vt[tp][i];
            if(d[x.v]<d[tp]+x.len){
                d[x.v]=d[tp]+x.len;
                if(inq[x.v]==0){
                    Q.push(x.v);
                    inq[x.v]=1;
                }
            }
        }
    }
}
void init()
{
    read(n);
    read(m);
    for(int i=1;i<=n;i++) read(v[i]);
    for(int i=1,x,y,z;i<=m;i++){
        read(x);
        read(y);
        read(z);
        add(x,y);
        if(z==2) add(y,x);
    }
    vt[n].push_back((u){3*n+1,0});
    vt[n*3].push_back((u){n*3+1,0});//将终点和大终点连起来
    n=3*n+1;
}
int main()
{
    init();
    spfa();
    out(d[n]);
    printf("
");
    return 0;
}

B:道路和航线

  • 题意:公路是双向的,航线是单向的,但是航线的花费可能是负的。给定一个起点,求该起点到每个点的最小花费。并且注意不存在那种公路过去又从公路回来坐航线的SB情况。
  • 题解:看上去是一个比较标准的最短路,只是有负边。正解是分开来对每个连通块内做dijkstra 然后外面对于连通块用拓扑序, 细节挺多,而且我不会然后这题可以用spfa来做,朴素的spfa会T,可以用双端队列来优化。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod=1e9+7;
const ll maxn=80010;
const int inf=0x3f3f3f;
int t,r,p,s;
int d[maxn];
template<class T>
void read(T &res)
{
    res = 0;
    char c = getchar();
    T f = 1;
    while(c < '0' || c > '9')
    {
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9')
    {
        res = res * 10 + c - '0';
        c = getchar();
    }
    res *= f;
}
template<class T>
void out(T x)
{
    if(x < 0)
    {
        putchar('-');
        x = -x;
    }
    if(x >= 10)
    {
        out(x / 10);
    }
    putchar('0' + x % 10);
}
struct edge//建边
{
    int v;
    int cost;
    edge(int _v=0,int _cost=0):v(_v),cost(_cost){}
};
vector<edge>e[maxn];//邻接表
void add(int u,int v,int val)
{
    e[u].push_back(edge(v,val));
}
bool vis[maxn];
int cnt[maxn];
int dist[maxn];
void init()
{
    read(t);
    read(r);
    read(p);
    read(s);
    for(int i=1,a,b,c;i<=r;i++){
        read(a);
        read(b);
        read(c);
        add(a,b,c);
        add(b,a,c);
    }
    for(int i=1,a,b,c;i<=p;i++){
        read(a);
        read(b);
        read(c);
        add(a,b,c);
    }
    for(int i=1;i<=t;i++) d[i]=inf;
}
void spfa()
{
    vis[s]=1;
    d[s]=0;
    deque<int>q;
    q.push_front(s);
    memset(cnt,0,sizeof(cnt));
    cnt[s]=1;
    while(!q.empty())
    {
        int u=q.front();
        q.pop_front();
        vis[u]=0;
        for(int i=0;i<e[u].size();i++){
            int v=e[u][i].v;
            if(d[v]>d[u]+e[u][i].cost){
                d[v]=d[u]+e[u][i].cost;
                if(!vis[v]){
                    vis[v]=true;
                    if(!q.empty()){//SLF优化
                        if(d[v]<d[q.front()]) q.push_front(v);
                        else q.push_back(v);
                    }
                    else q.push_back(v);
                }
            }
        }
    }
}
int main()
{
    init();
    spfa();
    for(int i=1;i<=t;i++){
        if(d[i]>=inf)printf("NO PATH
");
        else printf("%d
",d[i]);
    }
    return 0;
}

SPFA算法有两个优化算法 SLF 和 LLL:
SLF:Small Label First 策略,设要加入的节点是(j),队首元素为(i),若(dist(j)<dist(i)),则将j插入队首,否则插入队尾。
LLL:LargLabel Last 策略,设队首元素为i,队列中所有dist值的平均值为x,若(dist(i)>x)则将i插入到队尾,查找下一元素,直到找到某一i使得(dist(i)<=x),则将i出对进行松弛操作。 SLF 可使速度提高 15 ~ 20%;SLF + LLL 可提高约 50%.
在实际的应用中SPFA的算法时间效率不是很稳定,为了避免最坏情况的出现,通常使用效率更加稳定的Dijkstra算法。

C:Cow Relays

  • 题意:
    n个牛做接力运动,t条道路,起点为s,终点为e,求s->e经过n条边的最短路。
  • 题解:
    我们先假设n等于2,相当于从起点到终点要经历一个断点k,这可以联想到floyd算法。先铺垫一下,(从零开始)

路径矩阵
通过一个图的权值矩阵求出它的每两点间的最短路径矩阵。
从图的带权邻接矩阵A=[a(i,j)] n×n开始,递归地进行n次更新,即由矩阵D(0)=A,按一个公式,构造出矩阵D(1);又用同样地公式由D(1)构造出D(2);……;最后又用同样的公式由D(n-1)构造出矩阵D(n)。矩阵D(n)的i行j列元素便是i号顶点到j号顶点的最短路径长度,称D(n)为图的距离矩阵,同时还可引入一个后继节点矩阵path来记录两点间的最短路径。
采用松弛技术(松弛操作),对在i和j之间的所有其他点进行一次松弛。所以时间复杂度为O(n^3);
状态转移方程
其状态转移方程如下: (map[i,j]:=min{map[i,k]+map[k,j],map[i,j]})
map[i,j]表示i到j的最短距离,K是穷举i,j的断点,map[n,n]初值应该为0,或者按照题目意思来做。
当然,如果这条路没有通的话,还必须特殊处理,比如没有map[i,k]这条路。

由此可见,floyd有种矩阵的思想在里面。那当k>1时该怎么办?
参考国家队集训论文 08年的 矩阵乘法在信息学中的应用
01邻接矩阵A的K次方(C=A^K),(C[i][j])表示i点到j点正好经过K条边的路径数
对应于这道题,对邻接图进行K次floyd之后,(C[i][j])就是点i到j正好经过K条边的最短路
进行k次floyd的话复杂度太大,我们可以发现,floyd算法有点像矩阵的乘法,我们可以采用矩阵快速幂来做。
这题可以用map来离散化

#include <bits/stdc++.h>
#define ll long long
using namespace std;
int n,t,s,e,num;
map<int,int>mp;
template<class T>
void read(T &res)
{
    res = 0;
    char c = getchar();
    T f = 1;
    while(c < '0' || c > '9')
    {
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9')
    {
        res = res * 10 + c - '0';
        c = getchar();
    }
    res *= f;
}
template<class T>
void out(T x)
{
    if(x < 0)
    {
        putchar('-');
        x = -x;
    }
    if(x >= 10)
    {
        out(x / 10);
    }
    putchar('0' + x % 10);
}
struct matrix
{
    int mapp[210][210];
    matrix(){
        memset(mapp,0x3f,sizeof(mapp));
    }
};
matrix floyd(matrix a,matrix b)
{
    matrix temp;
    int i,j,k;
    for(k=1;k<=num;k++){
        for(i=1;i<=num;i++){
            for(j=1;j<=num;j++){
                if(temp.mapp[i][j]>a.mapp[i][k]+b.mapp[k][j]){
                    temp.mapp[i][j]=a.mapp[i][k]+b.mapp[k][j];
                }
            }
        }
    }
    return temp;
}
matrix solve(matrix a,int k)
{
    matrix ans=a;
    while(k){
        if(k&1){
            ans=floyd(ans,a);
        }
        a=floyd(a,a);
        k>>=1;
    }
    return ans;
}
int main()
{
    matrix a;
    while(~scanf("%d%d%d%d",&n,&t,&s,&e)){
        num=0;
        mp.clear();
        int u,v,w;
        while(t--){
            read(w);
            read(u);
            read(v);
            if(mp[u]==0) mp[u]=++num;
            if(mp[v]==0) mp[v]=++num;
            if(a.mapp[mp[u]][mp[v]]>w)
                a.mapp[mp[u]][mp[v]]=a.mapp[mp[v]][mp[u]]=w;
        }
        a=solve(a,n-1);
        out(a.mapp[mp[s]][mp[e]]);
    }
    return 0;
}

  • 这里有一个矩阵快速幂的模板:
matrix floyd(matrix a,matrix b)
{
    matrix temp;
    int i,j,k;
    for(k=1;k<=num;k++){
        for(i=1;i<=num;i++){
            for(j=1;j<=num;j++){
                if(temp.mapp[i][j]>a.mapp[i][k]+b.mapp[k][j]){
                    temp.mapp[i][j]=a.mapp[i][k]+b.mapp[k][j];
                }
            }
        }
    }
    return temp;
}
matrix solve(matrix a,int k)
{
    matrix ans=a;
    while(k){
        if(k&1){
            ans=floyd(ans,a);
        }
        a=floyd(a,a);
        k>>=1;
    }
    return ans;
}

D:Sorting It All Out

  • 题意:给你几个英文字母的大小关系,1.是否能从小到大排序 2.是否有环 3.看不出来的话输出....
  • 题解:拓扑排序的模板题。in[]数组代表入度,即有多少个边以它为终点。拓扑排序就是每次以入度为0的点开始,放入队列中,并加入sorr。现在问题是如何判断有环,如何判断已排好。当有环的时候,举个例子:a<b,b<a。很明显找不到入度为0的点,所以当有环,pos要小于n。如果可以找到这个顺序,那么队列里每次都只有一个
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=120;
int n,m,in[maxn],sorr[maxn],temp[maxn];
int t,pos,num,flag,ok,stop;
char X,O,Y;
vector<int>e[maxn];
queue<int>q;
inline void init(){
    memset(in,0,sizeof(in));
    for(int i=0;i<=n;i++) e[i].clear();
    ok=false;
    flag=2;
}
int topsort(){
    while(!q.empty()) q.pop();
    for(int i=0;i<n;i++) if(in[i]==0){
            q.push(i);
        }
    pos=0;
    bool unsure=false;
    while(!q.empty()){
        if(q.size()>1) unsure=true;
        int u=q.front();
        q.pop();
        sorr[pos++]=u;
        int len=e[u].size();
        for(int v=0;v<len;v++){
            if(--in[e[u][v]]==0){
                q.push(e[u][v]);//入度为0放入队列中
            }
        }
    }
    if(pos<n) return 1;
    if(unsure) return 2;
    return 3;
}
int main(){
    int x,y;
    while(~scanf("%d%d",&n,&m)){
        if(n==0||m==0) break;
        init();
        for(int i=1;i<=m;i++){
            scanf(" %c%c%c%*c",&X,&O,&Y);
            if(ok) continue;
            x=X-'A',y=Y-'A';
            if(O=='<'){
                e[y].push_back(x);
                ++in[x];//入度
            }
            else if(O=='>'){
                e[x].push_back(y);
                ++in[y];
            }
            memcpy(temp,in,sizeof(in));
            flag=topsort();
            memcpy(in,temp,sizeof(temp));
            if(flag!=2){
                stop=i;
                ok=true;
            }
        }
        if(flag==3){
            printf("Sorted sequence determined after %d relations: ", stop);
            for(int i=pos-1;i>=0;i--)
                printf("%c",sorr[i]+'A');
            printf(".
");
        }
        else if(flag==1){
            printf("Inconsistency found after %d relations.
",stop);
        }
        else{
            printf("Sorted sequence cannot be determined.
");
        }
    }
}

E:Telephone Lines

  • 题意:题意很妖,要使1与n通信,求最少花费。而通信公司会免费前k个最贵的,你只要付第k+1个最贵的就可以.
  • 题解:这题我们可以通过二分法,即给定一个候选距离,跑dijkstra,边权比这候选距离大,则cost为1,否则为0。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+7;
const int inf=0x3f3f3f3f;
const double eps=1e-12;
int n,p,k;
int dist[maxn];
bool vis[maxn];
template<class T>
void read(T &res)
{
    res = 0;
    char c = getchar();
    T f = 1;
    while(c < '0' || c > '9')
    {
        if(c == '-') f = -1;
        c = getchar();
    }
    while(c >= '0' && c <= '9')
    {
        res = res * 10 + c - '0';
        c = getchar();
    }
    res *= f;
}
template<class T>
void out(T x)
{
    if(x < 0)
    {
        putchar('-');
        x = -x;
    }
    if(x >= 10)
    {
        out(x / 10);
    }
    putchar('0' + x % 10);
}
struct qnode
{
    int v;
    int c;
    qnode(int _v=0,int _c=0):v(_v),c(_c){}
    bool operator <(const qnode &r)const
    {
        return c>r.c;
    }
};
struct edge
{
    int v,cost;
    edge(int _v=0,int _cost=0) :v(_v),cost(_cost){}
};
vector<edge>e[maxn*2];
void add(int u,int v,int w)
{
    e[u].push_back(edge(v,w));
}
int dijkstra(int x,int y)
{
    memset(dist,inf,sizeof(dist));
    dist[x]=0;
    memset(vis,0,sizeof(vis));
    qnode temp;
    priority_queue<qnode>q;
    while(!q.empty()) q.pop();
    q.push(qnode(x,0));
    int cost;
    while(!q.empty()){
        temp=q.top();
        q.pop();
        int u=temp.v;
        if(vis[u]) continue;
        vis[u]=true;
        int len=e[u].size();
        for(int i=0;i<len;i++){
            int v=e[u][i].v;
            if(e[u][i].cost>=y) cost=1;//>=稍微使结果偏大一点
            else cost=0;
            if(!vis[v]&&dist[v]>dist[u]+cost){
                dist[v]=dist[u]+cost;
                q.push(qnode(v,dist[v]));
            }
        }
    }
    return dist[n];
}
int main()
{
    read(n);
    read(p);
    read(k);
    int a,b,c,maxx=0;
    for(int i=1;i<=p;i++){
        read(a);
        read(b);
        read(c);
        add(a,b,c);
        add(b,a,c);
        maxx=max(maxx,c);
    }
    int low=0;
    int high=maxx,mid,ans;
    while(low<=high){
        mid=(high+low)>>1;
        if(dijkstra(1,mid)<=k){//不可能出现结果的区间
            high=mid-1;
        }
        else{//可能出现结果的区间
            ans=mid;
            low=mid+1;
        }
    }
    if(low>maxx) out(-1);
    else out(ans);
    return 0;
}

F:走廊泼水节

  • 题意:给一个最小生成树,添加最少花费的边,变成完全图,即每个点都连在一起。
  • 题解:我们需要高速确定点对之间是否有边以及点对之间新建边的最小权值
    可以把输入给的边来一次kruskal的过程,按权值排序,忽视之前的所有边,用并查集连接两个集合。
    用kruskal算法build最小生成树的过程中,计算最小添加边的权值((x∗y−1)∗(w+1))
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=6010;
const int maxm=601000;
struct Edge
{
    int u,v,w;
}edge[maxm];
int tol,fa[maxn],s[maxn];
void add(int u,int v,int w)
{
    edge[++tol].u=u;
    edge[tol].v=v;
    edge[tol].w=w;
}
bool cmp(Edge a,Edge b)
{
    return a.w<b.w;
}
int findd(int x)
{
    if(fa[x]==-1) return x;
    else return fa[x]=findd(fa[x]);
}
int getfa(int x){
   if(fa[x]==x) return x;
   return fa[x]=getfa(fa[x]);
}
void init(int n)
{
    for(int i=0;i<=n;i++) fa[i]=i,s[i]=1;
    tol=0;
}
void Union(int x,int y)
{
    fa[x]=y;
    s[y]+=s[x];
}
int main()
{
    int t;
    int x,y,w,n;
    scanf("%d",&t);
    while(t--){
        ll ans=0;
        scanf("%d",&n);
        init(n);
        for(int i=1;i<n;i++){
            scanf("%d%d%d",&x,&y,&w);
            add(x,y,w);
        }
        sort(edge+1,edge+n,cmp);
        for(int i=1;i<n;i++){
            x=getfa(edge[i].u);
            y=getfa(edge[i].v);
            if(x==y) continue;
            ans+=1ll*(edge[i].w+1)*(s[x]*s[y]-1);
            Union(x,y);
        }
        printf("%lld
",ans);
    }
    return 0;
}
  • kruskal描述:

假设 WN=(V,{E}) 是一个含有 n 个顶点的连通网,则按照克鲁斯卡尔算法构造最小生成树的过程为:先构造一个只含 n 个顶点,而边集为空的子图,若将该子图中各个顶点看成是各棵树上的根结点,则它是一个含有 n 棵树的一个森林。之后,从网的边集 E 中选取一条权值最小的边,若该条边的两个顶点分属不同的树,则将其加入子图,也就是说,将这两个顶点分别所在的两棵树合成一棵树;反之,若该条边的两个顶点已落在同一棵树上,则不可取,而应该取下一条权值最小的边再试之。依次类推,直至森林中只有一棵树,也即子图中含有 n-1条边为止。

G:黑暗城堡

  • 题意:题目要求源点到其余点的最短路径d[i],并且求树上路径s[i]等于d[i]的生成树
  • 题解:最短路模板题。然后不只求的d[i] 还要路径还原,即d[i](1到i)有多少路径。然后用乘法原理即可。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e3+7;
const int maxx=5e5+10;
const int inf=0x3f3f3f3f;
const ll mod=(1<<31)-1;
int mp[maxn][maxn];
struct qnode{
    int v;
    int c;
    qnode(int _v=0,int _c=0):v(_v),c(_c){}
    bool operator <(const qnode &r) const
    {
        return c>r.c;
    }
};
struct edge
{
    int v,cost;
    edge(int _v=0,int _cost=0):v(_v),cost(_cost){}
};
vector<edge>e[maxx];
bool vis[maxn];
struct dist
{
    int v;
    int w;
}d[maxn];
bool cmp(dist a,dist b)
{
    return a.w<b.w;
}
void dijkstra(int n,int start)
{
    memset(vis,false,sizeof(vis));
    for(int i=1;i<=n;i++) d[i].w=inf,d[i].v=i;
    priority_queue<qnode>q;
    while(!q.empty()) q.pop();
    d[start].w=0;
    q.push(qnode(start,0));
    qnode tmp;
    while(!q.empty())
    {
        tmp=q.top();
        q.pop();
        int u=tmp.v;
        if(vis[u]) continue;
        vis[u]=true;
        for(int i=0;i<e[u].size();i++)
        {
            int v=e[tmp.v][i].v;
            int cost=e[u][i].cost;
            if(!vis[v]&&d[v].w>d[u].w+cost)
            {
                d[v].w=d[u].w+cost;
                q.push(qnode(v,d[v].w));
            }
        }
    }
}
inline void add(int u,int v,int w)
{
    e[u].push_back(edge(v,w));
}
int main(){
    int n,m;
    int u,v,w;
    scanf("%d%d",&n,&m);
    memset(mp,inf,sizeof(mp));
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&u,&v,&w);
        add(u,v,w);
        add(v,u,w);
        mp[u][v]=mp[v][u]=min(mp[u][v],w);
    }
    dijkstra(n,1);
    sort(d+1,d+1+n,cmp);
    ll ans=1,temp;
    for(int i=2;i<=n;i++){
            temp=0;
        for(int j=1;j<i;j++){
            if(d[i].w==mp[d[i].v][d[j].v]+d[j].w) temp++;
        }
        ans=temp*ans%mod;
    }

    printf("%lld
",ans);
    return 0;
}

H:疫情控制

  • 题意:中文没什么好说

H国有n个城市,这n个城市用n-l条双向道路相互连通构成一棵树,1号城市是首都,也是树中的根节点。
H国的首都爆发了一种危害性极高的传染病。当局为了控制疫清,不让疫情扩散到边境城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,首都是不能建立检查点的。
现在,在H国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军
队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。
请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。

  • 题解

https://www.luogu.org/problemnew/solution/P1084

RMQ预处理 然后越往上越好 就让军队往上提

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int M=50005;
inline int read(){
    int q=0;char ch=' ';
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9')q=q*10+ch-'0',ch=getchar();
    return q;
}
struct Edge
{
    int to,next;
    ll dis;
}e[M<<1];
struct Amey
{
    int rest;
    int id;
}ame[M],bme[M];
bool cmpmax(Amey a,Amey b){return a.rest>b.rest;}
int n,m,head[M],tol,na,nb;
int a[M],dp[M][20];
ll dis[M][20];
int vis[M],restar[M],used[M],restmin[M];
inline void add_edge(int u,int v,int val){
    tol++;
    e[tol].to=v;
    e[tol].dis=val;
    e[tol].next=head[u];
    head[u]=tol;
}
void dfs(int u,int fa,int val){
    dp[u][0]=fa;
    dis[u][0]=val;
    for(int i=head[u];i;i=e[i].next){
        int v=e[i].to;
        if(v!=fa){
            dfs(v,u,e[i].dis);
        }
    }
}
void rmq_init(){
    for(int j=1;j<=19;j++){
        for(int i=1;i<=n;i++){
            dp[i][j]=dp[dp[i][j-1]][j-1];
            dis[i][j]=dis[dp[i][j-1]][j-1]+dis[i][j-1];
        }
    }
}
bool checkok(int u,int fa)//检查未封住的子树
{
    int flag1=0,flag2=1;
    if(vis[u]) return 1;
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(v==fa) continue;
        flag1=1;
            if(!checkok(v,u)){
                flag2=0;
                if(u==1){
                    bme[++nb].id=v;
                    bme[nb].rest=e[i].dis;
                }
                else return 0;
            }
        }
    if(!flag1) return 0;
    else return flag2;
}
bool judge(int mid)
{
    ll num;na=nb=0;
    ll now=1;
    memset(vis,0,sizeof(vis));
    memset(used,0,sizeof(used));//军队是否被使用
    memset(restar,0,sizeof(restar));//驻扎在某点 rest 最小的军队
    for(int i=1;i<=m;i++){//上提军队
        int x=a[i];num=0;
        for(int j=19;j>=0;j--){
            if(dp[x][j]>1&&num+dis[x][j]<=mid)
                num+=dis[x][j],x=dp[x][j];
        }
        if(dp[x][0]==1&&num+dis[x][0]<=mid){
                ame[++na].rest=mid-num-dis[x][0],ame[na].id=i;
                if(!restar[x]||ame[na].rest<restmin[x]){
                    restmin[x]=ame[na].rest,restar[x]=i;
                }
            }
        else vis[x]=1;
    }
    if(checkok(1,1)) return 1;
    sort(ame+1,ame+na+1,cmpmax);
    sort(bme+1,bme+nb+1,cmpmax);
    used[0]=1;
    for(int i=1;i<=nb;i++){//贪心
        if(!used[restar[bme[i].id]]){
            used[restar[bme[i].id]]=1;
            continue;
        }
        while(now<=na&&(used[ame[now].id]||ame[now].rest<bme[i].rest))++now;
        if(now>na) return 0;
        used[ame[now].id]=1;
    }
    return 1;
}
int main()
{
    n=read();
    int u,v,w;
    ll l=0,r=500000;
    for(int i=1;i<n;i++){
        u=read();
        v=read();
        w=read();
        add_edge(u,v,w);
        add_edge(v,u,w);
    }
    dfs(1,1,0);
    rmq_init();
    m=read();
    for(int i=1;i<=m;i++){
        a[i]=read();
    }
    int ans=-1;
    while(l<=r){
        int mid=(l+r)>>1;
        if(judge(mid)) ans=mid,r=mid-1;
        else l=mid+1;
    }
    printf("%d
",ans);
    return 0;
}

L:次小生成树

  • 题意:求严格次小生成树
  • 题解:LCA+kruscal
    先求一个最小生成树:模板题
    建树 dfs lca预处理
    然后就可以知道u到v最大的边,然后用uv的非树边换了它。 可以证明 u->v的最小生成树的距离大于等于非树边的u->v。但是要是这条边跟非树边相等,那我们就把u->v的次小边换了。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const ll inf=0x3f3f3f3f3f3f;
const int maxn=1e5+10;
struct Edge{
    int u,v,w;
    int next;
    bool operator <(const Edge&c)const{
        return w<c.w;
    }
}e[maxn*8],dp[maxn*4];
int tol=0,head[maxn];
int fa[maxn];
bool vis[maxn];
void add(int u,int v,int w){
    tol++;e[tol].u=u;
    e[tol].v=v;e[tol].w=w;
    e[tol].next=head[u];
    head[u]=tol;
}
int f[maxn][21],dep[maxn];
ll maxi[maxn][19],mini[maxn][19];
void dfs(int u,int fa){
    f[u][0]=fa;
    for (int i = head[u]; i ; i=e[i].next) {
        int v=e[i].v;
        if(v==fa) continue;
        dep[v]=dep[u]+1;
        maxi[v][0]=e[i].w;
        mini[v][0]=-inf;
        dfs(v,u);
    }
}
int n,m;
void cal(){
    for (int i = 1; i <=18 ; ++i) {
        for (int j = 1; j <= n; ++j) {
            f[j][i]=f[f[j][i-1]][i-1];
            maxi[j][i]=max(maxi[j][i-1],maxi[f[j][i-1]][i-1]);
            mini[j][i]=max(mini[j][i-1],mini[f[j][i-1]][i-1]);
            if(maxi[j][i-1]<maxi[f[j][i-1]][i-1]&&mini[j][i]<maxi[j][i-1]){
                mini[j][i]=maxi[j][i-1];
            }
            else if(maxi[j][i-1]>maxi[f[j][i-1]][i-1]&&mini[j][i]<maxi[f[j][i-1]][i-1]){
                mini[j][i]=maxi[f[j][i-1]][i-1];
            }
        }
    }
}
inline int getfather(int a){
    if(fa[a]==a) return a;
    else return fa[a]=getfather(fa[a]);
}
ll sum;
inline ll qmax(int u,int v,int maxx){
    ll ans=-inf;
    for (int i = 18; i >=0; --i) {
        if(dep[f[u][i]]>=dep[v]){
            if(maxx!=maxi[u][i]){
                 ans=max(ans,maxi[u][i]);
            }
            else if(maxx==maxi[u][i]){
                ans=max(ans,mini[u][i]);
            }
        }
    }
    return ans;
}
inline int lca(int u,int v){
    if(dep[u]<dep[v]) swap(u,v);
    for(int i=19;i>=0;i--){
        if(dep[u]>=dep[v]+(1<<i)){
            u=f[u][i];
        }
    }
    if(u==v){
        return u;
    }
    for(int i=19;i>=0;i--){
        if(f[u][i]!=f[v][i]){
            u=f[u][i];
            v=f[v][i];
        }
    }
    return f[u][0];
}
inline void kruscal(){
    sort(dp+1,dp+1+m);
    int temp=0;
    for (int i = 1; i <= m; ++i) {
        int a=getfather(dp[i].u),b=getfather(dp[i].v);
        if(a!=b){
            vis[i]=true;
            fa[a]=b;temp++;sum+=dp[i].w;
            add(dp[i].u,dp[i].v,dp[i].w);
            add(dp[i].v,dp[i].u,dp[i].w);
        }
        if(temp==n-1) break;
    }
}
ll ans=inf;
inline void solve(){
    for (int i = 1; i <=m ; ++i) {
        if(!vis[i]){
            int u=dp[i].u;
            int v=dp[i].v;
            int w=dp[i].w;
            int gf=lca(u,v);
            ll m1=qmax(u,gf,w);
            ll m2=qmax(v,gf,w);
            ans=min(ans,sum-max(m1,m2)+w);
        }
    }
}
int main() {
    scanf("%d%d",&n,&m);
    for (int i = 0; i <= n; ++i) {
        fa[i]=i;
    }
    for(int i=1;i<=m;i++){
        scanf("%d%d%d",&dp[i].u,&dp[i].v,&dp[i].w);
    }
    kruscal();
    mini[1][0]=-inf;
    dep[1]=1;
    dfs(1,1);
    cal();
    solve();
    printf("%lld
",ans);
    return 0;
}

V:银河

  • 题解待写
#include <bits/stdc++.h>
template<class T>
void read(T &res) {
    res = 0;
    char c = getchar();
    T f = 1;
    while (c < '0' || c > '9') {
        if (c == '-') f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9') {
        res = res * 10 + c - '0';
        c = getchar();
    }
    res *= f;
}

#define ll long long
using namespace std;
const int maxn = 1e5 + 7;
#define pi pair<int,int>
int n;
namespace Tarjan {
    vector<pi > G[maxn];
    vector<pi > scc[maxn];
    int dfn[maxn], low[maxn];
    int stack[maxn], num = 0, top = 0, cnt = 0, col[maxn];
    bool ins[maxn];
    bool du[maxn];
    int dp[maxn];
    int sum[maxn];

    void add(int u, int v, int w) { G[u].emplace_back(make_pair(v, w)); }

    void add_c(int u, int v, int w) { scc[u].emplace_back(make_pair(v, w)); }

    void solve(int x) {
        dfn[x] = low[x] = ++num;
        stack[++top] = x, ins[x] = 1;
        for (auto v:G[x]) {
            if (!dfn[v.first]) {
                solve(v.first);
                low[x] = min(low[x], low[v.first]);
            } else if (ins[v.first]) {
                low[x] = min(low[x], low[v.first]);
            }
        }
        if (dfn[x] == low[x]) {
            ++cnt;
            int y;
            do {
                y = stack[top--], ins[y] = 0;
                col[y] = cnt;
                sum[cnt]++;
                //scc[cnt].push_back(y);
            } while (x != y);
        }
    }

    void dfs(int x) {
        for (auto u:scc[x]) {
            if (dp[x] + u.second >= dp[u.first]) {
                dp[u.first] = dp[x] + u.second;
                dfs(u.first);
            }
        }
    }

    void cal() {
        for (int i = 0; i <= n; ++i) {
            if (!dfn[i]) solve(i);
        }
        for (int i = 0, tmp; i <= n; ++i) {
            tmp = 0;
            for (auto u:G[i]) {
                if (col[u.first] == col[i]) {
                    tmp += u.second;
                } else {
                    add_c(col[i], col[u.first], u.second);
                    du[col[u.first]] = 1;
                }
            }
            if (tmp) {
                puts("-1");
                return;
            }
        }

        for (int i = 0; i <= cnt; ++i) {
            if (!du[i]) {
                dfs(i);
            }
        }
        ll ans = 0;
        for (int i = 0; i <= cnt; ++i) {
            ans += 1ll * dp[i] * sum[i];
        }
        printf("%lld
", ans);
    }
}

int main() {
    int m;
    read(n);
    read(m);
    int u, v, w;
    for (int i = 1; i <= m; ++i) {
        read(w);
        read(u);
        read(v);
        if (w == 1) Tarjan::add(u, v, 0), Tarjan::add(v, u, 0);
        else if (w == 2) Tarjan::add(u, v, 1);
        else if (w == 3) Tarjan::add(v, u, 0);
        else if (w == 4) Tarjan::add(v, u, 1);
        else Tarjan::add(u, v, 0);
    }
    for (int i = 1; i <= n; ++i) Tarjan::add(0, i, 1);
    Tarjan::cal();
    return 0;
}
不要忘记努力,不要辜负自己 欢迎指正 QQ:1468580561
原文地址:https://www.cnblogs.com/smallocean/p/9413337.html