ACwing算法提高课-第三章图论笔记(2)

1.负环

(1)虫洞

#include<bits/stdc++.h>
using namespace std;
const int N=510;
const int M=5210;
int n,m1,m2;
int h[N],e[M],w[M],ne[M],idx;
int dist[N];
int cnt[N];
bool st[N];
void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
bool spfa()
{
    memset(dist,0,sizeof(dist));
    memset(st,0,sizeof(st));
    memset(cnt,0,sizeof(cnt));
    queue<int>q;
    //起点不一定在1,所以所有点可以作为起点入队
    for(int i=1;i<=n;i++)
    {
        q.push(i);
        st[i]=1;
    }
    while(q.size())
    {
        int t=q.front();
        q.pop();
        st[t]=0;
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];
            if(dist[j]>dist[t]+w[i])
            {
                dist[j]=dist[t]+w[i];
                //从t到j多了一条边w[t][j]
                cnt[j]=cnt[t]+1;
                if(cnt[j]>=n)return true;
                if(!st[j])
                {
                    st[j]=1;
                    q.push(j);
                }
            }
        }
    }
    return 0;
}
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        memset(h,-1,sizeof(h));
        idx=0;
        cin>>n>>m1>>m2;
        //双向正边
        while(m1--)
        {
            int a,b,c;
            cin>>a>>b>>c;
            add(a,b,c);
            add(b,a,c);
        }
        //单向负边
        while(m2--)
        {
            int a,b,c;
            cin>>a>>b>>c;
            add(a,b,-c);
        }
        //存在负环则是YES
        if(spfa())cout<<"YES"<<endl;
        else cout<<"NO"<<endl;
    }
    return 0;
}

(2)观光奶牛

#include<bits/stdc++.h>
using namespace std;
//L个点,P条有向边,每个点有个权值f[i],每条边都有一个权值t[i]
//求图中的一个环,使环上各点的权值之和除以环上各边的权值之和最大
//输出最大值
const int N=1000+10;
const int M=5000+10;
//1000*1000/(1*1000)最大答案-->1000
//可以对答案进行二分l=0,r=1000
//每二分一次mid,check(mid)->判断是否存在一个环使mid<=(fi之和)/(ti之和)
//即(mid*ti-fi)之和<=0(注意找最大答案就要mid<=ans)
//每次check,一条u指向v,边权为w的边权变为:w*mid-fu,只需检查这个图是否存在负环
int n,m,f[N];
int h[N],e[M],ne[M],w[M],idx;
double dist[N];
bool st[N];
int cnt[N];
void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
bool check(double mid)
{
    memset(dist,0,sizeof(dist));
    memset(st,0,sizeof(st));
    memset(cnt,0,sizeof(cnt));
    queue<int>q;
    for(int i=1;i<=n;i++)
    {
        q.push(i);
        st[i]=1;
    }
    while(q.size())
    {
        int t=q.front();
        q.pop();
        st[t]=0;
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];
            double ww=w[i]*mid-(double)f[t];
            if(dist[j]>dist[t]+ww)
            {
                dist[j]=dist[t]+ww;
                cnt[j]=cnt[t]+1;
                if(cnt[j]>=n)return true;
                if(!st[j])
                {
                    st[j]=1;
                    q.push(j);
                }
            }
        }
    }
    return false;
}
int main()
{
    memset(h,-1,sizeof(h));
    cin>>n>>m;
    for(int i=1;i<=n;i++)cin>>f[i];
    for(int i=1;i<=m;i++)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
    }
    double l=0,r=1000,eps=1e-4;
    //浮点数二分直接取二分取mid
    while(r-l>eps)
    {
        double mid=(l+r)/2;
        if(check(mid))l=mid;
        else r=mid;
    }
    printf("%.2lf\n",r);
    return 0;
}

 (3)单词环

#include<bits/stdc++.h>
using namespace std;
const int N=700,M=1e5+10;
int n;
int h[N],e[M],w[M],ne[M],idx;
double dist[N];
int cnt[N];
bool st[N];
void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
bool check(double mid)
{
    memset(st,0,sizeof(st));
    memset(cnt,0,sizeof(cnt));
    queue<int>q;
    for(int i=0;i<676;i++)
    {
        q.push(i);
        st[i]=1;
    }
    int count=0;
    while(q.size())
    {
        int t=q.front();
        q.pop();
        st[t]=0;
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];
            if(dist[j]<dist[t]+w[i]-mid)
            {
                dist[j]=dist[t]+w[i]-mid;
                cnt[j]=cnt[t]+1;
                if(++count>10000)return true;
                if(cnt[j]>=N)return true;
                if(!st[j])
                {
                    q.push(j);
                    st[j]=1;
                }
            }
        }
    }
    return false;
}
int main()
{
    char str[1010];
    while(cin>>n)
    {
        if(n==0)break;
        memset(h,-1,sizeof(h));
        idx=0;
        for(int i=1;i<=n;i++)
        {
            cin>>str;
            int len=strlen(str);
            if(len>=2)
            {
                int left=(str[0]-'a')*26+str[1]-'a';
                int right=(str[len-2]-'a')*26+str[len-1]-'a';
                add(left,right,len);
            }
        }
        if(!check(0))puts("No solution");
        else
        {
            double l=0,r=1000;
            while(r-l>1e-4)
            {
                double mid=(l+r)/2;
                if(check(mid))l=mid;
                else r=mid;
            }
            printf("%lf\n",r);
        }
    }
    return 0;
}

2.二分图

二分图(在一个连通块中):
(1)图中不存在奇数环(边的数目为奇数)
(2)染色法过程中不存在矛盾
(两个结论等价)
可以把所有的点划分成两边(一条边所连两个点颜色不同)
若某个连通块中一个点确定,即整个连通块都可以确定

(1)关押罪犯

#include<bits/stdc++.h>
using namespace std;
const int N=2e4+10;
const int M=2e5+10;
int h[N],e[M],ne[M],w[M],idx;
int n,m;
int cor[M];//0未染,再染1和2
//两个罪犯有怨气值就连边
void add(int a,int b,int c)
{
    e[idx]=b;
    w[idx]=c;
    ne[idx]=h[a];
    h[a]=idx++;
}
bool dfs(int x,int co,int mid)
{
    cor[x]=co;
    for(int i=h[x];~i;i=ne[i])
    {
        int j=e[i];
        if(w[i]<=mid)continue;//在一个监狱内
        if(cor[j])//已染但出现矛盾
        {
            if(cor[j]==co)return false;
        }
        else if(!dfs(j,3-co,mid))return false;//未染
    }
    return true;
}
bool check(int mid)
{
    //每次check都重新染图
    memset(cor,0,sizeof(cor));
    for(int i=1;i<=n;i++)
    {
        if(!cor[i])
        {
            //未染过色
            if(!dfs(i,1,mid))return false;//染1失败
        }
    }
    return true;
}
int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof(h));
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c);
        add(b,a,c);
        //二分图为无向图
    }
    //最大值最小化
    int l=0,r=1e9;
    while(l<r)
    {
       int mid=(l+r)/2;
       if(check(mid))r=mid;
       else l=mid+1;       
    }
    cout<<r<<endl;
    return 0;
}

(2)棋盘覆盖

匈牙利算法
二分图的最大匹配
最大匹配等价于不存在增广路径

每个卡片塞2个格子 
把格子看成点
把卡片看成边
则只要能放卡片的相邻两个格子就连一条边

🔺问题转化为->最多取多少条边,满足卡片不重叠--所有选出的边没有公共点--二分图
              放最多的牌--找到最多的边--二分图上最大匹配

图是不是二分图 <=> 能不能把图染成两种颜色 <=> 每个边上的两个点的颜色不同
=> 把棋盘按1间隔染色 => 二分图

每个格子横纵坐标之和分奇数偶数染

#include<bits/stdc++.h>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
const int N = 110;
int n, m;
PII match[N][N];
bool g[N][N], st[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
bool find(int x, int y)
{
    for (int i = 0; i < 4; i ++ )//枚举邻点
    {
        int a = x + dx[i], b = y + dy[i];
        if (a && a <= n && b && b <= n && !g[a][b] && !st[a][b])//不是坏点 没遍历过
        {
            // 则男[x,y] 和 女[a,b]能够配对 
            st[a][b] = true;
            PII t = match[a][b];// 
            //1 t.x==-1说明女[a,b]还没和其他人配对 则男[x,y]和女[a,b]可以直接配对
            //2 女[a,b]已经有人配对,但和女[a,b]配对的男t还有其他选项
            //  男t放弃和女[a,b]配对 让女[a,b]给男[x,y]配对
            if (t.x == -1 || find(t.x, t.y))
            {
                match[a][b] = {x, y};
                return true;
            }
        }
    }

    return false;
}

int main()
{
    cin >> n >> m;
    while(m--)
    {
        int x,y;
        cin >> x >> y;
        g[x][y] = true;
    }
    memset(match,-1,sizeof match);
    int res = 0;
    // 枚举所有和为奇数的点
    for(int i=1;i<=n;i++)
    {
        for(int j = 1;j<=n;j++)
        {
            if((i+j)%2 && !g[i][j])
            {
                memset(st,0,sizeof st);//每次都需要清空st数组,因为匹配好的一对可能会有下家
                if(find(i,j))res++;//如果[i,j]能配对
            }
        }
    }
    cout << res << endl;
    return 0;
}

(3)机器任务

在二分图中:最小点覆盖==最大匹配数

最小点覆盖:能覆盖整个图所需要的点数(一边两个点,选一个点即是覆盖这条边)

#include<bits/stdc++.h>
using namespace std;
//某个任务为模式:a[i]==0||b[i]==0(直接完成不重启)
//剩下的任务a[i]>0&&b[i]>0
//(a+b)N+M-2个模式最少选择多少个模式,可以把所有任务做掉
//每个任务连一条边a[i]--b[i],最少选多少个点能覆盖所有的边(最小点覆盖问题=最大匹配数(匈牙利))
const int N=110;
bool g[N][N],st[N];
int n,m,k;
int match[N];
bool find(int x)
{
    for(int i=1;i<=m-1;i++)
    {
        if(!st[i]&&g[x][i])
        {
            st[i]=1;
            if(match[i]==-1||find(match[i]))
            {
                match[i]=x;
                return true;
            }
        }
    }
    return false;
}
int main()
{
    while(cin>>n,n)
    {
        cin>>m>>k;
        memset(g,0,sizeof(g));
        memset(match,-1,sizeof(match));
        while(k--)
        {
            int t,a,b;
            cin>>t>>a>>b;
            if(!a||!b)continue;
            g[a][b]=1;
        }
        int res=0;
        //枚举a,匹配b
        for(int i=1;i<=n-1;i++)
        {
            memset(st,0,sizeof(st));
            if(find(i))res++;
        }
        cout<<res<<endl;
    }
    return 0;
}

3.拓扑序列

(1)家谱树

#include<bits/stdc++.h>
using namespace std;
//有向无环图(拓扑序列)
//输出一个序列,使得每个人的孩子都比那个人后列出。
const int N=400+10;
int h[N],e[N],ne[N],idx;
int n;
int d[N];
int ans[N],cnt;
void add(int a,int b)
{
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
void topsort()
{
    queue<int>q;
    for(int i=1;i<=n;i++)
    {
        if(!d[i])
        {
            q.push(i);
        }
    }
    while(q.size())
    {
        int t=q.front();
        ans[++cnt]=t;
        q.pop();
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];
            d[j]--;
            if(d[j]==0)q.push(j);
        }
    }
}
int main()
{
    cin>>n;
    memset(h,-1,sizeof(h));
    for(int i=1;i<=n;i++)
    {
        int son;
        while(cin>>son,son)
        {
            add(i,son);
            d[son]++;
        }
    }
    topsort();
    for(int i=1;i<=cnt;i++)
    {
        cout<<ans[i]<<" ";
    }
    cout<<endl;
    return 0;
}

(2)奖金

#include<bits/stdc++.h>
using namespace std;
const int N=2e4+10;
int n,m;
int h[N],e[N],ne[N],idx;
int d[N];
int ans[N],cnt;
int sal[N];
void add(int a,int b)
{
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
//入度为0的点为最低工资100元
void topsort()
{
    queue<int>q;
    for(int i=1;i<=n;i++)
    {
        if(!d[i])
        {
            q.push(i);
            sal[i]=100;
        }
    }
    while(q.size())
    {
        int t=q.front();
        ans[++cnt]=t;
        q.pop();
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];
            d[j]--;
            if(!d[j])
            {
                sal[j]=sal[t]+1;
                q.push(j);
            }
        }
    }
}
int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof(h));
    while(m--)
    {
        int a,b;
        cin>>a>>b;
        //a的工资比b高,b->a
        add(b,a);
        d[a]++;
    }
    topsort();
    if(cnt<n)puts("Poor Xed");
    else
    {
        int res=0;
        for(int i=1;i<=cnt;i++)
        {
            res+=sal[i];
        }
        cout<<res<<endl;
    }
    return 0;
}

(3)可达性统计(bitset)

#include<bits/stdc++.h>
using namespace std;
const int N=3e4 + 10;
bitset<N>f[N];//f[i]表示i能到达所有点的数量
//先拓扑排序,倒序求每个集合
//f[i]={i}Uf(j1)Uf(j2)....或运算
int h[N],e[N],ne[N],idx;
int d[N];
int n,m;
int ans[N],cnt;
void add(int a,int b)
{
    e[idx]=b;
    ne[idx]=h[a];
    h[a]=idx++;
}
void topsort()
{
    queue<int>q;
    for(int i=1;i<=n;i++)
    {
        if(!d[i])q.push(i);
    }
    while(q.size())
    {
        int t=q.front();
        ans[++cnt]=t;
        q.pop();
        for(int i=h[t];~i;i=ne[i])
        {
            int j=e[i];
            d[j]--;
            if(!d[j])q.push(j);
        }
    }
}
void cal()
{
    //倒序枚举每一个
    for(int j=cnt;j>=1;j--)
    {
        int x=ans[j];
        f[x][x]=1;
        for(int i=h[x];~i;i=ne[i])
        {
            int y=e[i];
            f[x]|=f[y];//求出集合
        }
    }
}
int main()
{
    cin>>n>>m;
    memset(h,-1,sizeof(h));
    while(m--)
    {
        int a,b;
        cin>>a>>b;
        add(a,b);
        d[b]++;
    }
    topsort();
    cal();
    for(int i=1;i<=n;i++)
    {
        cout<<f[i].count()<<endl;//f[i].count()返回f[i]中1的个数
    }
    return 0;
}

(4)车站

#include<bits/stdc++.h>
using namespace std;

const int N = 2007, M = 5000007;

int n, m;
int ver[M], nex[M], edge[M], head[N], tot;
int din[N];
int vis[N];
int q[N];
int dist[N];

void add(int x, int y, int z){
    ver[tot] = y;
    edge[tot] = z;
    nex[tot] = head[x];
    head[x] = tot ++ ;
    din[y] ++ ;
}

void toposort(){
    int hh = 0, tt = -1;
    for(int i = 1;i <= n + m;++i)
    {//一共n + m个点,要遍历所有的点
        if(!din[i]){
            q[++ tt] = i;
            if(tt == N)tt = 0;
        }
    }

    while(hh <= tt){

        int x = q[hh ++ ];
        if(hh == N)hh = 0;

        for(int i = head[x];~i;i = nex[i])
        {
            int y = ver[i];
            if(-- din[y] == 0){
                q[++ tt] = y;
                if(tt == N)tt = 0;
            }
        }
    }
}

int main(){
    scanf("%d%d", &n,&m);
    memset(head,-1,sizeof head);

    for(int i = 1;i <= m;++i){
        memset(vis,0,sizeof vis);
        int t,stop;
        scanf("%d",&t);
        int start = n, end = 1;
        while(t -- ){
            scanf("%d",&stop);//输入的是编号
            start = min(start, stop);
            end = max(end, stop);
            vis[stop] = true;//代表该站要停靠.
        }
        int source = n + i;//n + 1
        for(int j = start;j <= end;++j)
        {//该线路上的所有经过的站点的编号一定在始发站和终点站之间
            if(vis[j])//要停靠,说明是右部点
                add(source, j, 1);
            else add(j, source, 0);
        }
    }

    toposort();

    for(int i = 1;i <= n;++i)//A ≥ 1
        dist[i] = 1;
    for(int i = 0;i < n + m;++i){//手写的队列是从0开始的
        int x = q[i];
        for(int j = head[x];~j;j = nex[j]){
            int y = ver[j], z = edge[j]; 
            dist[y] = max(dist[y], dist[x] + z);
        }
    }
    int res = 0;
    for(int i = 1;i <= n;++i)//满足所有约束条件的解中最大值既是题目要求的最高的级别
        res = max(res, dist[i]);

    printf("%d\n",res);
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值