图论题

牛客 导航系统

题面:

在这里插入图片描述

思路:

在这里插入图片描述

code:

//https://ac.nowcoder.com/acm/contest/3007/I
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=1e3+5;
struct Node{
    int a,b,c;
}e[maxm*maxm];
int g[maxm][maxm];
int d[maxm][maxm];
int pre[maxm];
int ffind(int x){
    return pre[x]==x?x:pre[x]=ffind(pre[x]);
}
bool cmp(Node a,Node b){
    return a.c<b.c;
}
signed main(){
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            cin>>g[i][j];
            if(i!=j)d[i][j]=1e17;
        }
    }
    int cnt=0;
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            if(g[i][j]!=g[j][i]){
                cout<<"No"<<endl;
                return 0;
            }else{
                e[++cnt]={i,j,g[i][j]};
            }
        }
    }
    sort(e+1,e+1+cnt,cmp);
    for(int i=1;i<=n;i++){//init pre
        pre[i]=i;
    }
    vector<int>ans;
    for(int i=1;i<=cnt;i++){//kruskal
        int a=e[i].a;
        int b=e[i].b;
        int x=ffind(a);
        int y=ffind(b);
        if(x!=y){
            pre[x]=y;
            d[a][b]=d[b][a]=e[i].c;
            ans.push_back(e[i].c);
        }
    }
    for(int k=1;k<=n;k++){//floyd
        for(int i=1;i<=n;i++){
            for(int j=1;j<=n;j++){
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
            }
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            if(d[i][j]!=g[i][j]){
                cout<<"No"<<endl;
                return 0;
            }
        }
    }
    cout<<"Yes"<<endl;
    sort(ans.begin(),ans.end());
    for(int v:ans){
        cout<<v<<endl;
    }
    return 0;
}

bzoj1001: [BeiJing2006]狼抓兔子

题面:

在这里插入图片描述

思路:

这类最小成本堵路问题要想到最小割。
根据最大流最小割定理可知答案就是最大流。
注意到是无向边,因此不需要建立反向的0边权虚边。
建图跑dinic计算最大流即可。

code:

#include<bits/stdc++.h>
using namespace std;
const int maxm=2e6+1000;
int head[maxm],nt[maxm<<2],to[maxm<<2],w[maxm<<2],cnt;
int d[maxm];
int n,m;
int st,ed;
queue<int>q;
void add(int x,int y,int z){
    cnt++;nt[cnt]=head[x];head[x]=cnt;to[cnt]=y;w[cnt]=z;
}
bool bfs(){
    while(!q.empty())q.pop();
    q.push(st);
    for(int i=st;i<=ed;i++){
        d[i]=0;
    }
    d[st]=1;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=nt[i]){
            int v=to[i];
            if(!d[v]&&w[i]){
                d[v]=d[x]+1;
                if(v==ed)return 1;
                q.push(v);
            }
        }
    }
    return 0;
}
int dfs(int x,int flow){
    if(x==ed)return flow;
    int res=flow;
    for(int i=head[x];i;i=nt[i]){
        int v=to[i];
        if(w[i]&&d[v]==d[x]+1){
            int k=dfs(v,min(res,w[i]));
            if(!k)d[v]=-1;
            res-=k;
            w[i]-=k;
            w[i^1]+=k;
        }
        if(!res)break;
    }
    return flow-res;
}
int dinic(){
    int maxflow=0;
    while(bfs()){
        maxflow+=dfs(st,2e9);
    }
    return maxflow;
}
int getid(int i,int j){
    return (i-1)*m+j;
}
void init(){
    cnt=1;
}
signed main(){
    init();
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){//横向边
        for(int j=1;j<m;j++){
            int a=getid(i,j);
            int b=getid(i,j+1);
            int c;
            scanf("%d",&c);
            add(a,b,c);
            add(b,a,c);
        }
    }
    for(int i=1;i<n;i++){//纵向边
        for(int j=1;j<=m;j++){
            int a=getid(i,j);
            int b=getid(i+1,j);
            int c;
            scanf("%d",&c);
            add(a,b,c);
            add(b,a,c);
        }
    }
    for(int i=1;i<n;i++){//斜向边
        for(int j=1;j<m;j++){
            int a=getid(i,j);
            int b=getid(i+1,j+1);
            int c;
            scanf("%d",&c);
            add(a,b,c);
            add(b,a,c);
        }
    }
    st=getid(1,1);
    ed=getid(n,m);
    int ans=dinic();
    printf("%d\n",ans);
    return 0;
}

CodeForces1272 E.Nearest Opposite Parity

题意:

给长度为n的数组a
a(i)表示i位置可以走到i+a(i)或者i-a(i),前提是要走到的位置合法(在1到n之间)
现在问每个位置走到和自己奇偶性不同的位置的最少步数是多少

思路:

首先可以发现当a(i)是奇数的时候就能直接走到奇偶性不同的位置

把题目转化为图论模型:每个位置就是一个节点,按a(i)的奇偶性建图
现在要计算每个点到奇偶性不同的节点的最小距离,是多源最短路
我们可以建立超级源点,然后原图改成反向图,计算超级源点到所有点的距离
这样就变成单源最短路了。

建图:
如果i可以到达j就建立一条j到i的反向边
建立超级源点0代表偶数步终点,超级源点n+1代表奇数步源点
偶数步的源点对所有偶数步点建立有向边,奇数步源点对所有奇数步点建立有向边
计算偶数步源点到所有点的最短距离,如果a(i)是奇数那么源点到i的最短距离就是ans(i)
计算奇数步源点到所有点的最短距离,如果a(i)是偶数那么源点到i的最短距离就是ans(i)

注意源点到其他点的边是按步数的奇偶性建图的,详见代码

code:

#include<bits/stdc++.h>
using namespace std;
const int maxm=2e5+5;
vector<int>g[maxm];
int mark[maxm];
int ans[maxm];
int d[maxm];
int a[maxm];
int n;
void spfa(int st){
    for(int i=1;i<=n;i++){
        mark[i]=0;
        d[i]=1e9;
    }
    queue<int>q;
    q.push(st);
    mark[st]=1;
    d[st]=0;
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int v:g[x]){
            if(d[v]>d[x]+1){
                d[v]=d[x]+1;
                if(!mark[v]){
                    mark[v]=1;
                    q.push(v);
                }
            }
        }
    }
}
signed main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    for(int i=1;i<=n;i++){//反向边
        if(i-a[i]>=1){
            g[i-a[i]].push_back(i);
        }
        if(i+a[i]<=n){
            g[i+a[i]].push_back(i);
        }
    }
    for(int i=1;i<=n;i++){
        if(a[i]%2==0){
            g[0].push_back(i);//偶数步超级源点
        }else{
            g[n+1].push_back(i);//奇数步超级源点
        }
    }
    spfa(0);
    for(int i=1;i<=n;i++){
        if(a[i]%2==1&&d[i]!=1e9){
            ans[i]=d[i]-1;//因为源点到其他点的距离为1,因此要减掉
        }
    }
    spfa(n+1);
    for(int i=1;i<=n;i++){
        if(a[i]%2==0&&d[i]!=1e9){
            ans[i]=d[i]-1;//因为源点到其他点的距离为1,因此要减掉
        }
    }
    for(int i=1;i<=n;i++){
        if(!ans[i])ans[i]=-1;
        cout<<ans[i]<<' ';
    }
    return 0;
}

CodeForces1278 D. Segment Tree

题意:

给n个线段(L,R)
保证线段的端点只出现一次
如果线段i和线段j相交,则在i和j之间建立一条边
问最后建出的图是否是一棵树
数据范围:n<=5e5

思路:

先按左端点从小到大排序,这样遍历的时候就能保证当前的l大于之前的所有L了,
然后只需要在之前的点中找到R在当前的(L,R)中的点,然后建边就行了
但是遍历之前的所有点的话是O(n)的,这个地方可以用set维护,set自带的二分能很快找到大于等于当前L的位置
set里面存的是右端点R,但是我们要的是编号,因为题目保证端点不重复,所以用map将R映射成编号就行了(pair也行)
然后暴力建图就行了
为什么可以暴力呢?
因为题目要求是一颗树,最多就建立n-1条边,超过n-1则不成立(其实就是中途如果出现环则不成立)
可以用并查集判环,在最后还要用并查集判断是否只有一个连通块。

总结:
这种有点像二维偏序,先按左端点排序降维,然后set+自带二分就能找到满足条件的元素了。

code:

#include<bits/stdc++.h>
using namespace std;
const int maxm=5e5+5;
struct Node{
    int l,r;
}e[maxm];
map<int,int>mark;
int pre[maxm];
set<int>s;
int ffind(int x){
    return pre[x]==x?x:pre[x]=ffind(pre[x]);
}
bool cmp(Node a,Node b){
    return a.l<b.l;
}
signed main(){
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)pre[i]=i;
    for(int i=1;i<=n;i++){
        scanf("%d%d",&e[i].l,&e[i].r);
        mark[e[i].r]=i;//用右端点记录点的编号
    }
    sort(e+1,e+1+n,cmp);//按l从小到大排序
    for(int i=1;i<=n;i++){//当前l大于等于set里面的所有l
        auto it=s.lower_bound(e[i].l);//找set中r在当前l-r之间的
        for(;it!=s.end();it++){
            if(*it>e[i].r)break;
            int x=ffind(mark[e[i].r]);
            int y=ffind(mark[*it]);
            if(x==y){//如果形成环则不满足
                puts("NO");
                return 0;
            }else{
                pre[x]=y;
            }
        }
        s.insert(e[i].r);
    }
    int sum=0;
    for(int i=1;i<=n;i++){
        if(pre[i]==i)sum++;
    }
    if(sum!=1){
        puts("NO");
    }else{
        puts("YES");
    }
    return 0;
}

发布了430 篇原创文章 · 获赞 36 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_44178736/article/details/104345302
今日推荐