差分约束+最小生成树+次小生成树+闭包传递

差分约束

引入例题:
给定n个变量和m个不等式,每个不等式形如 x[i] - x[j] <= a[k] (0 <= i, j < n, 0 <= k < m, a[k] 已知),求 x[n-1] - x[0] 的最大值。例如当n = 4,m = 5,不等式组如图一-1-1所示的情况,求x3 - x0的最大值。
在这里插入图片描述
观察x3 - x0的性质,我们如果可以通过不等式的两两加和得到c个形如 x3 - x0 <= Ti 的不等式,那么 min{ Ti | 0 <= i < c } 就是我们要求的x3 - x0的最大值。于是开始人肉,费尽千辛万苦,终于整理出以下三个不等式:
1. (3) x3 - x0 <= 8
2. (2) + (5) x3 - x0 <= 9
3. (1) + (4) + (5) x3 - x0 <= 7
这里的T等于{8, 9, 7},所以min{ T } = 7,答案就是7。
在这里插入图片描述
所以就是求最短路径

解的存在性:有解 无解 无限多解

有解
在这里插入图片描述
无解
在这里插入图片描述
无限多解
在这里插入图片描述

最大值=>最小值

将原先的<=变成 >=
运用数学知识使得不等号开口朝一个方向

问题 A: 这个不等式组很眼熟吧
时间限制: 1 Sec 内存限制: 128 MB
题目描述
给定n个变量和m个不等式,每个不等式形如 x[i] - x[j] <= a[k] (0 <= i j < n 0 <= k < m, a[k]已知),求 x[n-1] - x[0] 的最大值。
输入
第一行 nm 表示的是n个变量和 m个不等式
接下来的 m行 会输入三个数abc 表示Xa-Xb<=c;
输出
输出一行结果 表示 Xn-1-X0的最大值。
样例输入
4 5
1 0 2
2 0 7
3 0 8
2 1 3
3 2 2
样例输出
7

用Dijkstra
#include<bits/stdc++.h>
using namespace std;
const int maxn=2500+5;
#define INF 0x3f3f3f3f
int Map[maxn][maxn],dis[maxn],check[maxn];

int Dis(int n,int m,int start,int end){
  memset(dis,0,sizeof(dis));
  memset(check,0,sizeof(check));
  for(int i=0;i<n;i++)
     dis[i]=Map[start][i];
  check[start]=1,dis[start]=0;
  int minx=0,pos;
  for(int i=1;i<n;i++){
    minx=INF;
    for(int j=0;j<n;j++){
      if(!check[j]&&minx>dis[j]){
        minx=dis[j];
        pos=j;
      }
    }
    check[pos]=1;
    for(int j=0;j<n;j++){
        if(!check[j]&&dis[j]>dis[pos]+Map[pos][j])
           dis[j]=dis[pos]+Map[pos][j];
    }
  }
  return dis[end];
}

int main(){
   int n,m;
   cin>>n>>m;
   for(int i=0;i<n;i++)
     for(int j=0;j<n;j++)
       Map[i][j]=INF;
    int a,b,c;
  for(int i=1;i<=m;i++){
    cin>>a>>b>>c;
      Map[a][b]=Map[b][a]=c;  //邻接矩阵建图
    }
     cout<<Dis(n,m,n-1,0)<<endl;
}

用SPFA
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn=1005;
const int inf = 0x3f3f3f3f;
int dis[maxn];
int vis[maxn];
int head[maxn];
struct edge{
    int to,w,next;
};
edge e[maxn];
void init(){
    memset(vis,0,sizeof(vis));
    memset(head,-1,sizeof(head));
    memset(dis,inf,sizeof(dis));
}
int spfa(int b,int n){
    queue<int>q;
    vis[b]=1;
    dis[b]=0;
    q.push(b);
    while(!q.empty()){
        int now=q.front();
        q.pop();
        vis[now]=0;
        for(int i=head[now];i!=-1;i=e[i].next){  //找出以now节点的所有边
            int v=e[i].to;
            if(dis[v]>dis[now]+e[i].w){
                dis[v]=dis[now]+e[i].w;
                if(!vis[v]){
                    q.push(v);
                    vis[v]=1;
                }
            }
        }
    }
    return dis[n-1];
}
int main()
{
    int n,m;
    int cnt=0;//边的编号
    scanf("%d%d",&n,&m);
    init();
    for(int i=0;i<m;i++){
        int a,b,c;
        scanf("%d%d%d",&a,&b,&c);
        e[++cnt].to=a;//链式前向星建图  
        e[cnt].w=c;
        e[cnt].next=head[b];
        // head[u]:表示以u为起点的最后一条边  
        /*比如节点1为起点的边有编号分别为1,2,3的边  向节点1插入边1的时候,这时的初始head[1]是等于-1的,然后进行e[1].next=head[1](这时边1的指向-1),然后进行head[1]=1(1为起点的最后一条边为边1,再插入以节点1为起点的边的时候,比如边2,然后进行e[2].next=head[1] (此时的head[1]存的是边1,进行e[2].next=head[1]时边2的指向边1,然后在遍历时的那个i=e[i].next就变成了边2跳向了边1),插入以1为起点的边3时也是如此,边3就指向了边2,head[1]就更新成了3。    因为最后存的是边3,遍历时就是从边3开始,然后指向边2,然后指向边1,然后就指向-1,-1时退出遍历*/
        head[b]=cnt; //更新以u为起点的最后一条边
      
    }
    cout<<spfa(0,n)<<endl;
    return 0;
}

问题 B: 牛也是有脾气的哦
题目描述
看似可爱的哞哞怪,其实脾气也不小呢,这一点在他们吃草的时候体现的淋漓尽致。这群牛从1~n依次编号并且依次站立吃草但是他们中间有些牛就是相互不喜欢,牛主人也很为难,只有满足他们之间的距离不小于某个值这两头牛才不会相互影响,有的牛之间相互很喜欢如果见到彼此在周围,估计今天胃口就没了,他们之间的距离必须不大于某个值。所以在满足所有牛的要求的情况下,牛主人想让你算算第一头牛到最后一头牛的最大的可能距离是多少。
输入
所有输入样例满足有解。
第一行输入N,ML,MD三个数分别代表的是牛的数目,互相喜欢的对数,互相不喜欢的对数。(2<=NMLMD<=200)
接下来的ML行输入abc三个数代表第a头牛和第b头牛之间距离不超过c;(1<=abc<=1000)
接下来的MD行输入abc三个数代表第a头牛和第b头牛之间距离不小于c; (1<=abc<=1000)
输出
输出一个数表示可能的结果.
样例输入
4 2 1
1 3 10
2 4 20
2 3 3
样例输出
27

因为这个题有>=所以建图的时候只需权值变为负,起点和终点变一下

用的是链式前向星建图
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
const int INF=0x3f3f3f3f;
int vis[maxn],dis[maxn],visticnt[maxn];
struct node{
   int u,v,w,next;
}e[maxn*maxn];//边数组
int head[maxn];
void init(){
    memset(visticnt,0,sizeof(visticnt));
    memset(vis,0,sizeof(vis));
    memset(head,-1,sizeof(head));
    memset(dis,INF,sizeof(dis));
}

int spfa(int n,int b){
    queue<int> q;
    dis[b]=0;
    vis[b]=1;
    q.push(b);
    while(!q.empty()){
        int now=q.front();
        visticnt[now]++;//判负环
        if(visticnt[now]>n){
          return -1;
        }
        q.pop();
        vis[now]=0;
        for(int i=head[now];i!=-1;i=e[i].next){//
            int v=e[i].v;
            if(dis[v]>dis[now]+e[i].w){
              dis[v]=dis[now]+e[i].w;
              if(!vis[v]){
                q.push(v);
                vis[v]=1;
              }
            }
        }
    }
    return dis[n];
}


int main(){
int n,ml,md,cnt;
while(cin>>n>>ml>>md){
    int a,b,c;
    init();
    cnt=0;//记边的数
    for(int i=0;i<ml;i++){//不超过的时候,也就是a-b<=c 是编号大的节点减去编号小的节点,也就是编号小的节点到编号大的节点的最短路径  
      cin>>a>>b>>c;
      int v=max(a,b); //选取大的
      int u=min(a,b);//选取小的
      //小的的到大
      e[++cnt].u=u; e[cnt].v=v;
      e[cnt].w=c;   e[cnt].next=head[u];
      head[u]=cnt;
    }
    for(int i=0;i<md;i++){//不小于,也就是a-b>=c 也就是编号大的节点到编号小的节点的最短路径  权值变为负
      cin>>a>>b>>c;
      int u=max(a,b);
      int v=min(a,b);
      //大的到小的  
      e[++cnt].u=u; e[cnt].v=v;
      e[cnt].w=-c; e[cnt].next=head[u];
      head[u]=cnt;
    }
    int ans=spfa(n,1);
    if(ans==-1)printf("-1\n");
    else if(ans==INF)printf("-2\n");
    else printf("%d\n",ans);
}
  return 0;
}

最小生成树

prime算法

此算法可以称为“加点法”,每次迭代选择代价最小的边对应的点,加入到最小生成树中。算法从某一个顶点s开始,逐渐长大覆盖整个连通网的所有顶点。

1.图的所有顶点集合为VV;初始令集合u={s}, v=V−uu={s}, v=V−u;
2.在两个集合u,vu,v能够组成的边中,选择一条代价最小的边(u0,v0)(u0,v0),加入到最小生成树中,并把v0v0并入到集合u中。
3.重复上述步骤,直到最小生成树有n-1条边或者n个顶点为止。
由于不断向集合u中加点,所以最小代价边必须同步更新;需要建立一个辅助数组closedge,用来维护集合v中每个顶点与集合u中最小代价边信息.

在这里插入图片描述

Kruskal算法

此算法可以称为“加边法”,初始最小生成树边数为0,每迭代一次就选择一条满足条件的最小代价边,加入到最小生成树的边集合里。

  1. 把图中的所有边按代价从小到大排序;
  2. 把图中的n个顶点看成独立的n棵树组成的森林;
  3. 按权值从小到大选择边,所选的边连接的两个顶点ui,viui,vi,应属于两颗不同的树,则成为最小生成树的一条边,并将这两颗树合并作为一颗树。
  4. 重复(3),直到所有顶点都在一颗树内或者有n-1条边为止
    在这里插入图片描述
    并集合时用到并查集

问题 D: Jungle Roads
题目描述
在这里插入图片描述
The Head Elder of the tropical island of Lagrishan has a problem. A burst of foreign aid money was spent on extra roads between villages some years ago. But the jungle overtakes roads relentlessly so the large road network is too expensive to maintain. The Council of Elders must choose to stop maintaining some roads. The map above on the left shows all the roads in use now and the cost in aacms per month to maintain them. Of course there needs to be some way to get between all the villages on maintained roads even if the route is not as short as before. The Chief Elder would like to tell the Council of Elders what would be the smallest amount they could spend in aacms per month to maintain roads that would connect all the villages. The villages are labeled A through I in the maps above. The map on the right shows the roads that could be maintained most cheaply for 216 aacms per month. Your task is to write a program that will solve such problems.

输入
The input consists of one to 100 data sets followed by a final line containing only 0. Each data set starts with a line containing only a number n which is the number of villages 1 < n < 27 and the villages are labeled with the first n letters of the alphabet capitalized. Each data set is completed with n-1 lines that start with village labels in alphabetical order. There is no line for the last village. Each line for a village starts with the village label followed by a number k of roads from this village to villages with labels later in the alphabet. If k is greater than 0 the line continues with data for each of the k roads. The data for each road is the village label for the other end of the road followed by the monthly maintenance cost in aacms for the road. Maintenance costs will be positive integers less than 100. All data fields in the row are separated by single blanks. The road network will always allow travel between all the villages. The network will never have more than 75 roads. No village will have more than 15 roads going to other villages (before or after in the alphabet). In the sample input below the first data set goes with the map above.
输出
The output is one integer per line for each data set: the minimum cost in aacms per month to maintain a road system that connect all the villages. Caution: A brute force solution that examines every possible set of roads will not finish within the one minute time limit.
样例输入
9
A 2 B 12 I 25
B 3 C 10 H 40 I 8
C 2 D 18 G 55
D 1 E 44
E 2 F 60 G 38
F 0
G 1 H 35
H 1 I 35
3
A 2 B 10 C 40
B 1 C 20
0
样例输出
216
30

本题一看图就知道求最小生成树
然后用Kruskal
麻烦的是本题的节点是字符,建图的时候不好处理,所以在建图的时候就把字符变为Int型就行,然后用链式前向星建图就行

#include<bits/stdc++.h>
using namespace std;
const int maxn=50;
const int maxv=2500;
struct node{
    int u,v,w;
 }e[maxv];
int pre[maxn];

bool cmp(node a,node b){
     return a.w<b.w;
}

int find(int x){
   if(pre[x]==x) return pre[x];
   return pre[x]=find(pre[x]);
}

int Union(int x,int y){
   int fx=find(x);
   int fy=find(y);
   if(fx!=fy){
     pre[fy]=fx;
     return 1;
   }
   return 0;
}

int kruskal(int en,int n){
    int cont=0,ans=0;
    for(int i=1;i<=en;i++){
       if(Union(e[i].u,e[i].v)){
         cont++;
         ans+=e[i].w;
       }
       if(cont==n-1) break;
    }
    return ans;
}

int main(){
  int n;
  while(cin>>n){
      if(n==0) break;
      for(int i=0;i<maxn;i++){
        pre[i]=i;
      }
      char s;
      int a,cnt=0;
      for(int i=1;i<n;i++){
        cin>>s>>a;
        for(int j=1;j<=a;j++){
          int w;
          cin>>s>>w;
          e[++cnt].u=i;e[cnt].v=s-'A'+1;  //字符节点变为int节点
          //cout<<e[cnt].u<<" "<<e[cnt].v<<" "<<endl;
          e[cnt].w=w;
          e[++cnt].u=s-'A'+1;e[cnt].v=i;
          e[cnt].w=w;
        }
      }
      sort(e+1,e+1+cnt,cmp);//排序
      cout<<kruskal(cnt,n)<<endl;
  }
}

次小生成树

次小生成树:
定义:
设 G=(V,E,w)是连通的无向图,T 是图G 的一个最小生成树。如果有另一棵树T1,满足不存在树T’,ω(T’)<ω(T1) ,则称T1是图G的次小生成树。
说白了也就是只比最小生成树大的一颗生成树。
算法(这里就不在去说那些定理了~,直接说对算法的理解):
怎么去找到一颗次小生成树呢?首先很容易想到的就是,每次删除最小生成树中的一条边,然后跑一遍最小生成数的算法,在新生成的所有树里面选择一颗最小的,就是要求的答案。这种方法的正确性很显然,但是时间复杂度确是O(nlognm)的级别(跑了m次最小生成树的算法)(这在稠密图里面是相当致命的)。
基于最小生成树的算法演变出次小生成树,其实基本的思想就是连入一条不在最小生成树上的边,从而形成一个环,去掉在环中并且在最小生成树上最大的边,遍历所有不在最小生成树上的边并进行同样的操作最小值即为次小生成树,简单证明就是连入一条边后去掉一个最大值相当于比原来的值增加的值最小(增加量=添加的边-环上的某一条边(并且这条边在最小生成树上),添加的边的权值一定,因此使环上的边最大),因次去掉最大的边,kruskal的复杂度是O(mlogm),求出环上的最大值复杂度是O(n
n),因此次小生成树的复杂度是O(mlogm+n*n)

千年老二
题目描述
雷婷与万钧是青梅竹马,无论是考试还是玩游戏,雷婷总是第一,而万钧总是第二,尽管万钧有做第一的实力,但他每次都会把第一让给雷婷,仅因为每次读榜单时雷霆万钧听起来是那么顺耳。这天,雷婷参加了 acm 选拔,万钧也跟着雷婷参加。题目是这样的:
有 n 个节点,编号为 1~n,有 m 条边,每条边都有一个距离。两点之间最多只有 1 条边。现在你需要选取 n-1 条边,使得所有点都连接起来都有通路。n-1 条边距离之和越小分数越高。
万钧立马意识到这道题是求最小生成树的,并且每个人的答案不能相同,万钧根据瞪眼法立马瞪出了答案,然而他还是等待雷婷先做完。现在雷婷已经找到了距离最短的1种方案,不过他俩太心有灵犀了,答案一模一样,万钧想获得第 2 名,请你帮万钧想一种方案,距离之和越短越好,但不能和雷婷的结果相同。一条边不同即可认为不同。如果找不到输出 -1。当然存在一种情况,如果雷婷的方案是没有方案求出最短距离,即表示该图没有最小生成树,即输出 -1。总之雷婷的方案是最优解的一种。
输入
存在多组数据,第一行一个正整数 t,表示有 t 组数据。
每组数据第一行有两个整数 n 和 m(2 ≤ n ≤ 100,1 ≤ m ≤ 1000),之后 m 行,每行三个正整数 s,e,w,表示 s 到 e 的双向路的权值为 w。
输出
输出次小生成树的值(如果存在多个最小生成树或仅有一个树,则次小生成树就是最小生成树,输出-1),如果不存在输出 -1。
样例输入
1
3 3
3 1 3
1 2 1
2 3 2
样例输出
4

次小生成树,用prim
#include<bits/stdc++.h>
using namespace std;
const int maxn=0x3f3f3f3f;
int Map[105][105];  //邻接矩阵
int vis[105];   //形成最小树过程的标记数组
int dis[105];  //记录最短路
int pre[105];  //记录前驱
int Max[105][105];//记录形成环的最大值
int mapvis[105][105];  //记录最小生成树

int ansz,ansc;  //ansz记录最小生成树的权值,ansc记录次小生成树的权值
void init(){
  memset(Map,maxn,sizeof(Map));
  memset(vis,0,sizeof(vis));
  memset(mapvis,0,sizeof(mapvis));
  ansz=0;
  ansc=maxn;
}
void prim(int n){
    vis[1]=1;
    for(int i=1;i<=n;i++){
        dis[i]=Map[1][i];//初始化最短路
        pre[i]=1;  //初始化前驱
    }
    for(int i=1;i<n;i++){  //n-1次循环找最小生成树
          int temp,Min=maxn;
          for(int j=1;j<=n;j++){
            if(!vis[j]&&Min>dis[j]){
               Min=dis[j];
               temp=j;
            }
          }
          vis[temp]=1;
          ansz+=Min;
          mapvis[pre[temp]][temp]=mapvis[temp][pre[temp]]=1;//标记temp加入最小生成树
          for(int k=1;k<=n;k++){  //遍历其他结点
                  if(vis[k]&&k!=temp){//寻找除在最小生成树里除刚加入的temp结点外的其他节点
                       Max[k][temp]=Max[temp][k]=max(Max[pre[temp]][k],dis[temp]);  //动态规划记录环的最大权值
                  }
                  if(!vis[k]){  //更新操作,更新未加入最小生成树的最短路
                      if(Map[temp][k]<dis[k]){
                        dis[k]=Map[temp][k];
                        pre[k]=temp;
                      }
                  }
          }
    }
    for(int i=1;i<n;i++){
        for(int j=i+1;j<=n;j++){
               if(!mapvis[i][j]&&Map[i][j]!=maxn){//寻找没有在最小生成树上的边,然后加上这条边就会形成环,在此环上寻找最大的那条边,删除最大的那条边。
                   ansc=min(ansz+Map[i][j]-Max[i][j],ansc); 
               }
        }
    }
}

int main(){
  int t;
  cin>>t;
  while(t--){
    int n,m;
    init();
    cin>>n>>m;
    int s,e,w;
    for(int i=0;i<m;i++){
      cin>>s>>e>>w;
      Map[s][e]=Map[e][s]=w;  //用邻接矩阵存图
    }
    prim(n);
    if(ansc==ansz||ansc==maxn)  printf("-1\n");  //两种情况特判,会存在次小生成树和最小生成树相同的情况,或者次小生成树无法生成
    else printf("%d\n",ansc);
  }
}

闭包传递

Warshall算法:
在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述

问题 B: 牛也是有脾气的哦
时间限制: 1 Sec 内存限制: 128 MB
题目描述
看似可爱的哞哞怪,其实脾气也不小呢,这一点在他们吃草的时候体现的淋漓尽致。这群牛从1~n依次编号并且依次站立吃草但是他们中间有些牛就是相互不喜欢,牛主人也很为难,只有满足他们之间的距离不小于某个值这两头牛才不会相互影响,有的牛之间相互很喜欢如果见到彼此在周围,估计今天胃口就没了,他们之间的距离必须不大于某个值。所以在满足所有牛的要求的情况下,牛主人想让你算算第一头牛到最后一头牛的最大的可能距离是多少。
输入
所有输入样例满足有解。
第一行输入N,ML,MD三个数分别代表的是牛的数目,互相喜欢的对数,互相不喜欢的对数。(2<=NMLMD<=200)
接下来的ML行输入abc三个数代表第a头牛和第b头牛之间距离不超过c;(1<=abc<=1000)
接下来的MD行输入abc三个数代表第a头牛和第b头牛之间距离不小于c; (1<=abc<=1000)
输出
输出一个数表示可能的结果.
样例输入
4 2 1
1 3 10
2 4 20
2 3 3
样例输出
27

这个题闭包传递,根据初始矩阵求出他的传递闭包就可以了。求出来之后我们可以根据每个点的出度和入度来判度这个点是否可以被准确定位。只要自己可以击败的人数和可以击败自己的人数加起来为n-1,就证明这个牛的位次是可以被确定的。
#include<bits/stdc++.h>
using namespace std;
const int maxn=105;
int N,M;
int guanxi[maxn][maxn];

int warshall(){
    for(int i=1;i<=N;i++){
      for(int j=1;j<=N;j++){
         if(guanxi[j][i]){
             for(int k=1;k<=N;k++){
                guanxi[j][k]=guanxi[j][k]|guanxi[i][k];
             }
         }
      }
    }
}

int main(){
    cin>>N>>M;
    int a,b;
    for(int i=1;i<=N;i++)
       for(int j=1;j<=N;j++)
       guanxi[i][j]=0;

    for(int i=0;i<M;i++){
           cin>>a>>b;
           guanxi[a][b]=1;
       }
    warshall();
//    for(int i=1;i<=N;i++){
  //     for(int j=1;j<=N;j++)
    //   cout<<guanxi[i][j];
  //     cout<<endl;
//     }
     int cont=0;//确定位次牛的数目
    for(int i=1;i<=N;i++){
      int ans=0;
            for(int j=1;j<=N;j++){
              ans+=guanxi[i][j]+guanxi[j][i];  //入度 出度加起来
            }
      if(ans==N-1) cont++; //入度+出度=N-1  确定位次牛的数目+1
    }
    cout<<cont<<endl;

}

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值