1.3 神奇的其他图论算法

1.3.1 拓扑排序

用于有向无环图,做一些跟点层数有关的事情。
Eg1.神经网络
    先把u[i]预处理好,重点要好好读题
#include<bits/stdc++.h>
using namespace std;

const int maxn = 110;

int n, p, c[maxn], u[maxn], tot, st[maxn], ind[maxn], out[maxn];
bool vis[maxn];

queue<int> q;

struct node{
    int v, w, nxt;
} edge[maxn*maxn];

inline void in(int x, int y, int z){
    edge[++tot].v = y;
    edge[tot].w = z;
    edge[tot].nxt = st[x];
    st[x] = tot;
}

inline void TopSort(){
    for(int i = 1; i <= n; i++)
        if(!ind[i]){
            q.push(i);
            //vis[i] = 1;
        }
    while(!q.empty()){
        int now = q.front(); q.pop();
        for(int i = st[now]; i; i = edge[i].nxt){
            int to = edge[i].v;
            ind[to]--;
            if(c[now] > 0)  c[to] += edge[i].w * c[now];
            if(!ind[to]){
            q.push(to);
                //  vis[to] = 1;
            }    
        }
    }
}

int main(){
    scanf("%d%d", &n, &p);
    for(int i = 1; i <= n; i++)
        scanf("%d%d", &c[i], &u[i]);
    for(int i = 1; i <= n; i++)
        if(!c[i])   c[i] -= u[i];
    for(int i = 1, x, y, z; i <= p; i++){
        scanf("%d%d%d", &x, &y, &z);
        ind[y]++;
        out[x]++;
        in(x, y, z);
    }
    TopSort();
    bool flag = 0;
    for(int i = 1; i <= n; i++)
        if(!out[i] && c[i] > 0){
            flag = 1;
            printf("%d %d\n", i, c[i]);
        }
    if(!flag)   printf("NULL\n");
    return 0;
}
Eg2.车站分级
    很有意思的题目。容易看出两个停靠车站之间的车站的等级都是严格小于停靠车站的,因此可以看作是一个有向无环图,求拓扑排序的级数即可。
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1001;
queue<int> q;
int n, m, tot, final;
int ans[maxn], rudu[maxn], a[maxn], st[maxn];
bool v[maxn], mp[maxn][maxn];

struct node{
    int v, nxt;
} edge[maxn*maxn];

inline void init(){
    for(int i = 1; i <= n; i++)
        v[i] = 0;
} 

inline void in(int x, int y){
    edge[++tot].v = y;
    edge[tot].nxt = st[x];
    st[x] = tot;
}

inline void topsort(){
    for(int i = 1; i <= n; i++)
        if(!rudu[i])    q.push(i);
    while(!q.empty()){
        int now = q.front(); q.pop();
        for(int i = st[now]; i; i = edge[i].nxt){
            int to = edge[i].v;
            rudu[to]--;
            if(!rudu[to]){
                q.push(to);
                ans[to] = ans[now] + 1;
                final = max(final, ans[to]);
            }
        }
    }
}

int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1, si, x; i <= m; i++){
        init();
        scanf("%d", &si);
        for(int j = 1; j <= si; j++){
            scanf("%d", &a[j]); 
            v[a[j]] = 1;
        }
        for(int j = a[1]; j <= a[si]; j++)
            if(!v[j])
                for(int k = 1; k <= si; k++)
                    if(!mp[a[k]][j]){
                        mp[a[k]][j] = 1;
                        in(a[k], j);
                        rudu[j]++;
                    }
    }
    topsort();
    printf("%d\n", final+1);
    return 0;
}

1.3.2 二分图染色

用来表示点与点之间的二元关系,非常好懂好写。
Eg1.封锁阳光大学
    暴力染色计数即可
#include<bits/stdc++.h>
using namespace std;
const int maxn = 10010;
int n, m, tot = 0;
int cnt[3], st[maxn], color[maxn];
struct node{
    int v, nxt;
} edge[200010];

inline void in(int x, int y){
    edge[++tot].v = y;
    edge[tot].nxt = st[x];
    st[x] = tot;
}

inline void dfs(int x, int co){
    if(color[x])    return;
    color[x] = co;
    cnt[co]++;
    for(int i = st[x]; i; i = edge[i].nxt){
        int to = edge[i].v;
        if(color[to] == co){
            //printf("%d %d", x, to);
            printf("Impossible\n");
            exit(0);
        }
        if(!color[to])
            dfs(to, 3-co);
    }
}

int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1, x, y; i <= m; i++){
        scanf("%d%d", &x, &y);
        in(x, y);
        in(y, x);
    }
    int ans = 0;
    for(int i = 1; i <= n; i++)
        if(!color[i]){
            cnt[1] = cnt[2] = 0;
            dfs(i, 1);
            ans += min(cnt[1], cnt[2]);
        }

    for(int i = 1; i <= n; i++)
        cnt[color[i]]++;

    printf("%d\n", ans);
    return 0;
}
Eg2.双栈排序
    隐密的图论。。由于不能存在i < j < k,a[j] > a[i] > a[i]。所以要不符合关系的点对不能扔一起,因此用到了二分图染色,如果染色冲突则不能双栈排序。然而我贪心贪错了。。。这份代码只有30pts。可以借鉴的好思路是如果只有一个栈该如何排序。
//dp f[i]表示i+1-n的最小值
//如果两数中间存在中间数比两数都大则不能排序  
#include<bits/stdc++.h>
using namespace std;

const int inf = 0x3f3f3f3f;
const int maxn = 1010;

int n, tot, mp[maxn][maxn], s1[maxn], s2[maxn], a[maxn], col[maxn], f[maxn];
int st[maxn], top1, top2;

struct node{
    int v, nxt;
} edge[maxn*maxn];

inline void in(int x, int y){
    edge[++tot].v = y;
    edge[tot].nxt = st[x];
    st[x] = tot;
}

void DFS(int now){
    for(int i = st[now]; i; i = edge[i].nxt){
        int to = edge[i].v;
        if(col[to] == col[now]){
            printf("0\n");
            exit(0);
        }
        if(col[to]) continue;
        col[to] = 3 - col[now];
        DFS(to);
    }
}

int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    f[n+1] = inf; 
    for(int i = n; i; i--)
        f[i] = min(f[i+1], a[i]);
    for(int i = 1; i < n; i++)
        for(int j = i+1; j <= n; j++)
            if(a[i] > f[j+1] && a[j] > a[i]){
                mp[i][j] = mp[j][i] = 1;
                in(i, j);
                in(j, i);
            }
    for(int i = 1; i <= n; i++)
        if(!col[i]){
            col[i] = 1;
            DFS(i);
        }
    for(int i = 1; i <= n; i++){
        if(!top1){
            s1[++top1] = i;
            printf("a ");
            continue;
        }
        if(col[i] == 2){
            if(!top2){
                s2[++top2] = i;
                printf("c ");
                continue;
            }
            if(a[s2[top2]] >= a[i]){
                s2[++top2] = i;
                printf("c ");
            }
            if(a[s2[top2]] < a[i] && top2){
                while(a[s2[top2]] < a[i] && top2){
                    top2--;
                    printf("d ");
                }
                s2[++top2] = i;
                printf("c ");
            }
        }
        else if(a[s1[top1]] >= a[i]){
            s1[++top1] = i;
            printf("a ");
        }
        else if(a[s1[top1]] < a[i] && top1){
            while(a[s1[top1]] < a[i] && top1){
                top1--;
                printf("b ");
            }
            s1[++top1] = i;
            printf("a ");
        }
    }
    while(top1){
        top1--;
        printf("b ");
    }
    while(top2){
        top2--;
        printf("d ");
    }
    printf("\n");
    return 0;
}

1.3.3 LCA

如果碰上LCA,那基本就是NOIp最难的题了。。。这玩意儿还能结合模拟的优化,结合树上差分,甚至结合一些我不会的东西,哪怕是贪心也很难受。。。这玩意儿可以通过倍增和Tarjan求,可我只会倍增。
Eg1.运输计划
    二分+树上差分+LCA,要深刻理解树上差分的思想,因为x点经过cnt次,所以其来路必经cnt次,因为LCA处已-2.
//二分,把超过限制的航线的重合最多的边设为虫洞,再扫一遍是否成立 
#include<bits/stdc++.h>
using namespace std;

const int maxn = 300010;

int n, m, st[maxn], fa[maxn][22], lca[maxn], len[maxn], sum[maxn], p[maxn], from[maxn], dep[maxn];
int tot, s[maxn], t[maxn], maxw, maxl;

struct node{
    int v, w, nxt;
} edge[maxn*2];

inline void in(int x, int y, int z){
    edge[++tot].v = y;
    edge[tot].w = z;
    edge[tot].nxt = st[x];
    st[x] = tot;
}

inline void change(int num){
    p[s[num]] += 1;
    p[t[num]] += 1;
    p[lca[num]] -= 2;
}

void BuildT(int now){
    for(int i = 1; i <= 20; i++){
        if(dep[now] < (1<<i))   break;
        fa[now][i] = fa[fa[now][i-1]][i-1];
    }
    for(int i = st[now]; i; i = edge[i].nxt){
        int to = edge[i].v;
        if(to == fa[now][0])    continue;
        fa[to][0] = now;
        sum[to] = sum[now] + edge[i].w;
        dep[to] = dep[now] + 1;
        from[to] = i;
        BuildT(to);
    }
}

int getLCA(int x, int y){
    if(dep[x] < dep[y]) swap(x, y);
    int delta = dep[x] - dep[y];
    for(int i = 0; i <= 20; i++)
        if(delta & (1 << i))    x = fa[x][i];
    for(int i = 20; i >= 0; i--)
        if(fa[x][i] != fa[y][i]){
            x = fa[x][i];
            y = fa[y][i];
        }
    if(x == y)  return x;
    return fa[x][0];
}

inline int getMax(int now, int cnt){
    int exc = p[now];
    for(int i = st[now]; i; i = edge[i].nxt){
        int to = edge[i].v;
        if(to == fa[now][0])    continue;
        exc += getMax(to, cnt);
    }
    if(exc == cnt)  maxw = max(maxw, edge[from[now]].w);
    return exc;
}

inline bool check(int now){
    maxw = 0;
    memset(p, 0, sizeof(p));
    int cnt = 0;
    for(int i = 1; i <= m; i++)
        if(len[i] > now){
            cnt++;
            change(i);
        }
    if(!cnt)    return 1;
    maxw = 0;
    getMax(1, cnt);
    return maxl - maxw <= now;
}

int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1, x, y, z; i < n; i++){
        scanf("%d%d%d", &x, &y, &z);
        in(x, y, z);
        in(y, x, z);
    }
    BuildT(1);
    for(int i = 1; i <= m; i++){
        scanf("%d%d", &s[i], &t[i]);
        lca[i] = getLCA(s[i], t[i]);
        len[i] = sum[s[i]] + sum[t[i]] - (sum[lca[i]] << 1);
        maxl = max(len[i], maxl);
    }
    int l = 0, r = maxl + 1, mid;
    while(l < r){
        mid = l+r >> 1;
        if(check(mid))  r = mid;
        else l = mid + 1;
    }
    printf("%d\n", r);
    return 0;
}
Eg2.货车运输
    在最大生成树上跑LCA,预处理点到LCA的最大权值
#include<bits/stdc++.h>
using namespace std;

const int maxn = 10010;
const int maxm = 50010;
const int inf = 0x3f3f3f3f;

int n, m, q, st[maxn], tot, cnt, f[maxn], s, t, fa[maxn][20], g[maxn][20], dep[maxn];
bool vis[maxn];

struct node{
    int x, y, w;
} a[maxm];

struct data{
    int v, w, nxt;
} edge[2*maxm];

inline bool cmp(node p, node q){
    return p.w > q.w;
}

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

inline void getInit(){
    for(int i = 1; i <= n; i++) f[i] = i;
}

inline void in(int x, int y, int z){
    edge[++tot].v = y;
    edge[tot].w = z;
    edge[tot].nxt = st[x];
    st[x] = tot;
}

inline void Kruskal(){
    getInit();
    for(int i = 1; i <= m; i++){
        int fx = find(a[i].x);
        int fy = find(a[i].y);
        if(fx != fy){
            f[fx] = fy;
            cnt++;
            in(a[i].x, a[i].y, a[i].w);
            in(a[i].y, a[i].x, a[i].w);
        }
    }
}

void Tree(int now){
    vis[now] = 1;
    for(int i = 1; i <= 16; i++){
        if(dep[now] < (1 << i)) break;
        fa[now][i] = fa[fa[now][i-1]][i-1];
        g[now][i] = min(g[now][i-1], g[fa[now][i-1]][i-1]);
    }
    for(int i = st[now]; i; i = edge[i].nxt){
        int to = edge[i].v;
        if(vis[to]) continue;
        dep[to] = dep[now] + 1;
        fa[to][0] = now;
        g[to][0] = edge[i].w;
        Tree(to);
    }
}

int getLCA(int s, int t){
    if(dep[s] < dep[t]) swap(s, t);
    int delta = dep[s] - dep[t];
    for(int i = 0; i <= 16; i++)
        if(delta & (1<<i))  s = fa[s][i];
    for(int i = 16; i >= 0; i--)
        if(fa[s][i] != fa[t][i]){
            s = fa[s][i];
            t = fa[t][i];
        }//可以避免跑出去 
    if(s == t)  return s;
    return fa[s][0];
}

int ask(int later, int former){
    int minw = inf;
    int delta = dep[later] - dep[former];
    for(int i = 0; i <= 16; i++)
        if(delta & (1 << i)){
            minw = min(g[later][i], minw);
            later = fa[later][i];
        }
    return minw;
}

int main(){
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= m; i++)
        scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].w);
    sort(a+1, a+m+1, cmp);
    Kruskal();
    for(int i = 1; i <= n; i++)
        if(!vis[i]) Tree(i);
    scanf("%d", &q);
    while(q--){
        scanf("%d%d", &s, &t);
        if(find(s) != find(t)){
            printf("-1\n");
            continue;
        }
        int lca = getLCA(s, t);
        printf("%d\n", min(ask(s, lca), ask(t, lca)));
    }
    return 0;
} 
Eg4.开车旅行
    不会,再说吧。
Eg5.跑路
    倍增的友好的题目,先用倍增预处理,再跑一遍Floyd。
#include<bits/stdc++.h>
using namespace std;
int dis[60][60],n,m;
bool G[60][60][65];
/*以上是变量说明部分,dis[i][j]表示i到j的路径/边的长度
G[i][j][k]表示,i到j是否存在一条长度为2^k的路径
如果有,为true,没有就是false*/ 
void init()
{
    memset(G,false,sizeof(G));
    memset(dis,10,sizeof(dis));
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        dis[x][y]=1;
        G[x][y][0]=true;
        /*初始化,x到y的路径(边)最短是1,也就是x到y存在
        一条长度为2^0的路径(边)*/ 
    }
}
void work()//此函数对G和dis做预处理 
{
    for(int k=1;k<=64;k++)
    //对于本题的数据,2^64已经足够。 
    for(int i=1;i<=n;i++)
    for(int t=1;t<=n;t++)
    for(int j=1;j<=n;j++)
    //枚举三个点
    if(G[i][t][k-1]&&G[t][j][k-1])
    /*如果i到t存在一条2^k-1长度的路径
    并且t到j存在一条2^k-1长度的路径
    就说明i到t,t到j都可以一秒到达,
    路程*2刚好是2的幂,也可以一秒到达*/ 
    {
        G[i][j][k]=true;
        //标记从i到j存在一条长度为2^k的路径 
        dis[i][j]=1;
        //i到j距离可以一秒到达 
    }
}
void floyd()
{
    for(int k=1;k<=n;k++)
    //这里的注意点:枚举中间点的循环放在最前面 
    for(int i=1;i<=n;i++)
    for(int j=1;j<=n;j++)
    dis[i][j]=min(dis[i][j],dis[i][k]+dis[k][j]);
    //松弛操作。 
}//Floyd图论求最短路。 
int main()
{
    init();
    work();
    floyd();
    printf("%d",dis[1][n]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值