SUST 2019暑期集训题解(差分约束+生成树+传递闭包)

A 这个不等式组很眼熟吧

这道题的话上课讲过就是根据不等式建图然后跑一下最短路就可以了。

#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){
            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[b]=cnt;
    }
    cout<<spfa(0,n)<<endl;
    //for(int i=0;i<n;i++)
    //cout<<dis[i]<<endl;
    return 0;
}

B 牛也是有脾气的
这道题也是一个差分约束的题目,只是说的比较含蓄了一点,根据牛之间的关系就可以确定牛与牛之间的约束方程,根据约束方程建图跑最短路就可以了。
需要注意的是牛的位置坐标必须按他的序号依次排列所以每次建边的时候要注意牛的编号的大小再确定方向。

//差分约束+spfa
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn = 1005;
const int inf =0x3f3f3f3f;
int vis[maxn];
int dis[maxn];
int visitcnt[maxn];
struct node{
    int u,v,w,next;
};
int head[maxn];
node e[maxn*maxn];
void init(){
    memset(visitcnt,0,sizeof(visitcnt));
    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();
        visitcnt[now]++;
        if(visitcnt[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(~scanf("%d %d %d",&n,&ml,&md)){
        int a,b,c;
        init();
        cnt=0;
        for(int i=0;i<ml;i++){
            scanf("%d%d%d",&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[a];
            head[a]=cnt;
        }
        for(int i=0;i<md;i++){
            scanf("%d%d%d",&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[b];
            head[b]=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;
}

C 畅通工程
这道题大家前面应该做过,简单的并查集就可以搞定。
我主要是觉得会了并查集kruskal不在话下。

#include<iostream>
using namespace std;
int a[1005];
int main()
{
    int Max;
    int n,m;//城镇数n  道路数m
    while(scanf("%d",&n)&&n!=0)
    {
        scanf("%d",&m);
        int find(int x);
        int i,j,t1,t2,r,s;
        s=0;
        for(i=1;i<=n;i++)
        a[i]=i;
        for(i=1;i<=m;i++)
        {
            scanf("%d %d",&t1,&t2);
            if(find(t1)!=find(t2))
            {
                a[find(t1)]=find(t2);
             }
             
             
        }
        for(i=1;i<=n;i++)
        {
            if(a[i]==i)
            s++;
        } 
    /*  for(i=1;i<=n;i++)
        {
            printf("%d   ",a[i]);
        } */
        printf("%d\n",s-1);
         
     } 
     return 0;
}
int find(int x)
{
    int r=x;
    while(r!=a[r])
    r=a[r];
    return r;
}

D Jungle Roads
这道题是一道裸的最小生成树的题目 ,求出最小生成树即可。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn = 50;
const int maxv=2500;
int per[maxn];
struct edge{
    int u,v,w;
}e[maxv];
bool compare(edge a,edge b){
    return a.w<b.w;
}
int find(int x){
    if(per[x]==x)return x;
    per[x]=find(per[x]);
    return per[x];
}
int join(int x,int y){
    int fx=find(x);
    int fy=find(y);
    if(fx!=fy){
        per[fy]=fx;
        return 1;
    }
    return 0;
}
int kruskal(int nn,int n){
    int cont=0;
    int ans=0;
    for(int i=1;i<=nn;i++){
        if(join(e[i].u,e[i].v)){
            cont++;
            ans+=e[i].w;
        }
        if(cont==n-1)break;
    }
    return ans;
}
int main()
{
    int n;
    while(scanf("%d",&n)&&n){
        for(int i=0;i<maxn;i++){
            per[i]=i;
        }
        char s[2];
        int a;
        int cnt=0;
        for(int i=1;i<n;i++){
            scanf("%s %d",s,&a);
            for(int j=1;j<=a;j++){
                int b;
                scanf("%s %d",s,&b);
                int uu=i;int vv=s[0]-'A'+1;
                e[++cnt].u=uu;e[cnt].v=vv;
                e[cnt].w=b;
                e[++cnt].u=vv;e[cnt].v=uu;
                e[cnt].w=b;
            }
        }
        sort(e+1,e+1+cnt,compare);
        int ans=kruskal(cnt,n);
        printf("%d\n",ans);
    }
}

E 千年老二
这是一道次小生成树的题目,在最小生成树的基础上加一些操作就好了。
注意次小生成树不存在的情况主是,可能这个图连最小生成树都没有,还有一种可能就是有两棵权值一样的最小生成树。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=0x3f3f3f3f;
int map[105][105];
int vis[105];
int dis[105];
int per[105];
int mapvis[105][105];
int max_ab[105][105];
int ans,ans1;
void init(){
    for(int i=0;i<105;i++){
        vis[i]=0;
        for(int j=0;j<105;j++){
            map[i][j]=maxn;
            mapvis[i][j]=0;
        }
    }
    ans=0;ans1=maxn;
}
void prim(int n){
    vis[1]=1;
    for(int i=1;i<=n;i++){
        dis[i]=map[1][i];
        per[i]=1;
    }
    for(int i=1;i<n;i++){
        int minn=maxn,g;
        for(int j=1;j<=n;j++){
            if(!vis[j]&&minn>dis[j]){
                minn=dis[j];
                g=j;
            }
        }
        ans+=minn;
        vis[g]=1;
        mapvis[g][per[g]]=mapvis[per[g]][g]=1;
        for(int k=1;k<=n;k++){
            if(vis[k]&&k!=g){
                max_ab[k][g]=max_ab[g][k]=max(max_ab[per[g]][k],dis[g]);
            }
            if(!vis[k]){
                if(map[g][k]<dis[k]){
                    dis[k]=map[g][k];
                    per[k]=g;
                }
            }
        }
    }
    for(int i=1;i<n;i++){
        for(int j=i+1;j<=n;j++){
            if(!mapvis[i][j]&&map[i][j]!=maxn){
                ans1=min(ans+map[i][j]-max_ab[i][j],ans1);
            }
        }
    }
}
int main()
{
    int t,n,m;
    scanf("%d",&t);
    while(t--){
        init();
        scanf("%d%d",&n,&m);
        for(int i=1;i<=m;i++){
            int xx,yy,vv;
            scanf("%d%d%d",&xx,&yy,&vv);
            map[xx][yy]=map[yy][xx]=vv;
        }
        prim(n);
        if(ans==ans1||ans1==maxn)printf("-1\n");
        else printf("%d\n",ans1);
    }
}

F row也喜欢打算法比赛
这道题目就是简单的闭包传递,根据初始矩阵求出他的传递闭包就可以了。求出来之后我们可以根据每个点的出度和入度来判度这个点是否可以被准确定位。只要自己可以击败的人数和可以击败自己的人数加起来为n-1,就证明这个牛的位次是可以被确定的。

#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
using namespace std;
int mp[105][105];
int in[105];
int out[105];
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    memset(mp,0,sizeof(mp));
    for(int i=1;i<=m;i++){
        int a,b;
        scanf("%d%d",&a,&b);
        mp[a][b]=1;
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(mp[j][i]){
                for(int k=1;k<=n;k++){
                    mp[j][k]=mp[j][k]|mp[i][k];
                }
            }
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(mp[i][j]){
                out[i]++;
                in[j]++;
            }
        }
    }
    int ans=0;
    for(int i=1;i<=n;i++){
        if(in[i]+out[i]==n-1){
            ans++;
        }
    }
    cout<<ans<<endl;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值