图论课后练习

最小生成树

387. 北极网络

题目链接
直接二分距离 m i d mid mid,kruscal过程中两点距离小于 m i d mid mid即可连边。最后并查集数量小于 s s s说明 m i d mid mid可行,否则增大 m i d mid mid

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 510;
typedef pair<int, int> PII;
int s, n;
int x[N], y[N], fa[N];
struct E{
    int u, v; 
    double dis;
    bool operator < (const E &x) const {return dis < x.dis;}
};
vector<E> e;
inline double dis(int i, int j){return sqrt(pow(x[i] - x[j], 2) + pow(y[i] - y[j], 2));}
int ff(int x){return fa[x] == x ? x : fa[x] = ff(fa[x]); }
bool check(double mid)
{
    for(int i = 1; i <= n; i ++) fa[i] = i;
    int all = n;
    for(auto it : e)
    {
        double dis = it.dis;
        if(dis > mid) break;
        int u = it.u, v = it.v;
        u = ff(u), v = ff(v);
        if(u == v) continue;
        all --;
        fa[u] = v;
    }
    return all <= s;
}
int main()
{
    int t; scanf("%d", &t);
    while(t --)
    {
        e.clear();
        scanf("%d%d", &s, &n);
        for(int i = 1; i <= n; i ++) 
            scanf("%d%d", &x[i], &y[i]);
        if(s == n)
        {
            puts("0.00");
            continue;
        }
        for(int i = 1; i <= n; i ++)
            for(int j = i + 1; j <= n; j ++)
                e.push_back({i, j, dis(i, j)});
        sort(e.begin(), e.end());
        double l = 0, r = 3e4, mid;
        while(r - l >= 1e-5)
        {
            mid = (l + r) / 2;
            if(check(mid)) r = mid;
            else l = mid;
        }
        printf("%.2f\n", mid);
    }
    return 0;
}

388. 四叶草魔杖

题目链接
枚举连通且和为0的每个点集 s s s,每个点集 s s s内的最小代价即其生成树。然后再将点集合并:两个点集不相交,则它们两个可用去更新并集点集。

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 20, inf = 0x3f3f3f3f;
int g[N][N], a[N], n, m, f[1 << 20];
bool vis[N];
struct E{
    int u, v, w; 
    bool operator < (const E &x) const {return w < x.w;}
};
vector<E>e;
int fa[N];
int ff(int x){return x == fa[x] ? x : fa[x] = ff(fa[x]); }
int kruscal(int sz)
{
    int ans = 0;
    for(auto it : e)
    {
        int u = it.u, v = it.v, w = it.w;
        if(!vis[u] || !vis[v]) continue;
        u = ff(u), v = ff(v);
        if(u == v) continue;
        fa[u] = v, ans += w, sz --;
    }
    return sz == 1 ? ans : inf;
}
int main()
{
    cin >> n >> m;
    memset(g, 0x3f, sizeof g);
    for(int i = 0; i < n; i ++) cin >> a[i];
    for(int i = 1; i <= m; i ++)
    {
        int u, v, w; cin >> u >> v >> w;
        g[u][v] = g[v][u] = w;
        e.push_back({u, v, w});
    }
    int mx = (1 << n) - 1;
    sort(e.begin(), e.end());
    memset(f, 0x3f, sizeof f);
    if(!e.size())
    {
        int sum = 0;
        for(int i = 0; i < n; i ++)
        {
            if(a[i] != 0) {
                puts("Impossible"); return 0;
            }
        }
        puts("0"); return 0;
    }
    for(int s = 0; s <= mx; s ++)
    {
        memset(vis, 0, sizeof vis); 
        int sum = 0, sz = 0;
        for(int i = 0; i < n; i ++)
            if((s >> i) & 1) 
                vis[i] = true, fa[i] = i, sum += a[i], sz ++;
        if(sum == 0) 
            f[s] = kruscal(sz);
    }
    if(f[mx] == 0) {
        puts("0"); return 0;
    }
    for(int i = 0; i <= mx; i ++)
    {
        if(f[i] == inf) continue;
        for(int j = 0; j <= mx; j ++)
        {
            if(f[j] == inf || (i & j)) continue;
            f[i | j] = min(f[i | j], f[i] + f[j]);
        }
    }
    if(f[mx] == inf) puts("Impossible");
    else printf("%d\n", f[mx]);
    return 0;
}

树的直径

389. 直径

题目链接
找直径上的必经边。必经边必然是一条链,不可能交叉(交叉说明被不同的直径经过)。先 d f s dfs dfs获得一条直径,记录直径的起点 s t st st终点 e d ed ed,和直径边上到起点的距离 d i s [ i ] dis[i] dis[i]。从起点向终点枚举,找到一个最靠近终点的直径上的点 l l l,它可以不经过直径上的边而找到另一条路径长度为 d i s [ l ] dis[l] dis[l],则说明另一条路径和 l l l~ e d ed ed构成了一条新直径,则 s t st st l l l路径上的都不是必经边。然后从终点向起点枚举,同意找到一个 r r r, r r r e d ed ed的边均不是必经边。则 l l l ~ r r r剩下的边均是必经边。
note: 下面是挺完备的板子哟,什么东西都记录下来,而且不很长。

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 2e5 + 10;
int n;
struct E{
    int to, nxt, w;
    bool is;
}e[N << 1];
int head[N], cnt = 1;
void add(int u, int v, int w)
{
    e[++ cnt] = {v, head[u], w, 0};
    head[u] = cnt;
}
LL mx = 0;
int st, ed;
void dfs1(int u, int fa, LL dep, int &ed,  LL &mx)
{
    if(dep > mx) mx = dep, ed = u;
    for(int i = head[u]; i; i = e[i].nxt)
    {
        int v = e[i].to;
        if(v == fa) continue;
        dfs1(v, u, dep + e[i].w, ed, mx);
    }
}
bool is[N];
LL dis[N];
int ord[N], d_cnt;
bool dfs2(int u, int fa, int st, LL &mx)
{
    if(u == st)
    {
        dis[u] = 0;
        ord[++ d_cnt] = u;
        return is[u] = true;
    }
    for(int i = head[u]; i; i = e[i].nxt)
    {
        int v = e[i].to;
        if(v == fa) continue;
        if(dfs2(v, u, st, mx))
        {
            ord[++ d_cnt] = u;
            mx = dis[u] = mx + e[i].w;
            return e[i].is = e[i ^ 1].is = is[u] = true;
        }
    }
}
LL dfs3(int u, int fa)
{
    LL mx = 0;
    for(int i = head[u]; i; i = e[i].nxt)
    {
        int v = e[i].to;
        if(e[i].is || v == fa) continue;
        mx = max(mx, 1ll * e[i].w + dfs3(v, u));
    }
    return mx;
}
int main()
{
    scanf("%d", &n);
    for(int i = 1; i < n; i ++)
    {
        int u, v, w;
        scanf("%d%d%d", &u, &v, &w);
        add(u, v, w), add(v, u, w);
    }
    dfs1(1, 0, 0, st, mx);
    mx = 0, dfs1(st, 0, 0, ed, mx);
    mx = 0, dfs2(ed, 0, st, mx);
    int l = 1, r = d_cnt;
    for(int i = 2; i <= d_cnt; i ++)
        if(dfs3(ord[i], 0) == dis[ord[i]]) l = i;
    for(int i = d_cnt - 1; i >= 2; i --)
        if(dfs3(ord[i], 0) == dis[ed] - dis[ord[i]]) r = i;
    int ans = 0;
    printf("%lld\n%d\n", mx, r - l);

    return 0;
}

lca

391. 聚会

题目链接
在树上找到3个点相聚的最短距离。先找出两个点的 l c a lca lca,作为相聚的节点,再计算另一个点到此 l c a lca lca的距离。以上过程重复3次取最大值。不可直接找到3个点的 l c a lca lca,然后计算每个点到公共 l c a lca lca的距离,这样会导致有一些边走两次。

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N = 5e5 + 10, inf = 0x3f3f3f3f;
int n, m;
struct E{
    int to, nxt;
}e[N << 1];
int head[N], cnt = 1, all;
int dep[N], f[N][20];
void add(int u, int v)
{
    e[++ cnt] = {v, head[u]};
    head[u] = cnt;
}
void dfs(int u, int ff)
{
    f[u][0] = ff;
    dep[u] = dep[ff] + 1;

    for (int i = 1; i <= all; i ++ )
        f[u][i] = f[f[u][i - 1]][i - 1];

    for(int i = head[u]; i; i = e[i].nxt)
    {
        int v = e[i].to;
        if(v == f[u][0]) continue;
        dfs(v, u);
    }
}
int lca(int u, int v)
{
    if(dep[u] > dep[v]) swap(u, v);
    for(int i = all; i >= 0; i --)
        if(dep[f[v][i]] >= dep[u]) v = f[v][i];
    if(u == v) return u;
    for(int i = all; i >= 0; i --)
        if(f[v][i] != f[u][i]) u = f[u][i], v = f[v][i];
    return f[u][0];
}
int cal(int a, int b, int c, int &ab_lca)
{
    ab_lca = lca(a, b);
    int d = lca(ab_lca, c);
    int ans = dep[a] + dep[b] - 2 * dep[ab_lca];
    if(d == c || d == ab_lca) ans += abs(dep[ab_lca] - dep[c]);
    else ans += dep[c] + dep[ab_lca] - 2 * dep[d];
    return ans;
}
int main()
{
    scanf("%d %d", &n, &m);
    all = (int) (log(n) / log(2)) + 1;
    for(int i = 1; i < n; i ++)
    {
        int u, v; scanf("%d%d", &u, &v);
        add(u, v), add(v, u);
    }
    dfs(1, 0);
    int pos, cost;
    while(m --)
    {
        int a, b, c; scanf("%d%d%d", &a, &b, &c);
        int t1, t2;
        cost = cal(a, b, c, pos); 
        t1 = cal(a, c, b, t2);
        if(t1 < cost) cost = t1, pos = t2;
        t1 = cal(b, c, a, t2);
        if(t1 < cost) cost = t1, pos = t2;
        printf("%d %d\n", pos, cost);
    }   
    return 0;
}

差分约束

393. 雇佣收银员

题目链接
此题的约束为:前7小时+当前小时的开始上班人数不少于当前的小时需要的上班人数。因此可定义前缀和约束:(时间从1-24)设 s [ i ] s[i] s[i]为从第 1 1 1个小时到第 i i i个小时开始上班的总人数。
i > = 8 i>=8 i>=8 s [ i ] − s [ i − 8 ] > = r [ i ] s[i]-s[i-8]>=r[i] s[i]s[i8]>=r[i];而当 i < 8 i<8 i<8则还需要加上第一个小时之前的,即从第24小时向前数,有 s [ i ] + s [ 24 ] − s [ 24 − ( 8 − i ) ] > = r [ i ] s[i]+s[24]-s[24-(8-i)]>=r[i] s[i]+s[24]s[24(8i)]>=r[i]。再加上基本约束:每个小时开始上班人数少于申请人数,且不为负数。
note: spfa出队则vis设为0;约束某个变量x为某个常量a,要加上 a < = x < = a a<=x<=a a<=x<=a的两个约束;看清题意;差分约束需要考虑基本约束条件和题目特定约束条件。

#include <bits/stdc++.h>
using namespace std;
const int N = 30;
int n = 24;
int s[N], num[N], r[N], q_num[N];
bool vis[N];
struct E{
    int to, nxt, w;
}e[N * N];
int head[N], cnt;
void add(int u, int v, int w)
{
    e[++ cnt] = {v, head[u], w};
    head[u] = cnt;
}
bool spfa()
{
    memset(s, -0x3f, sizeof s);
    memset(q_num, 0, sizeof q_num);
    memset(vis, 0, sizeof vis);
    queue<int> q;
    q.push(0); vis[0] = true; q_num[0] = 1; s[0] = 0;
    while(q.size())
    {
        int u = q.front(); q.pop();vis[u] = false;
        for(int i = head[u]; i; i = e[i].nxt)
        {
            int v = e[i].to;
            if(s[v] < s[u] + e[i].w)
            {
                s[v] = s[u] + e[i].w;
                if(!vis[v])
                {
                    vis[v] = true;
                    q_num[v] ++;
                    if(q_num[v] > n) return false;
                    q.push(v);
                }
            }
        }
    }
    return true;
}
bool check(int mid)
{
    cnt = 0;
    memset(head, 0, sizeof head);
    add(0, n, mid), add(n, 0, - mid);
    for(int i = 1; i <= n; i ++)
    {
        add(i - 1, i, 0), add(i, i - 1, - num[i]);
        if(i >= 8) add(i - 8, i, r[i]);
        else add(16 + i, i, r[i] - mid);
    }
    return spfa();
}
int main()
{
    int t; scanf("%d", &t);
    while(t --)
    {
        memset(num, 0, sizeof num);
        for(int i = 1; i <= n; i ++) scanf("%d", &r[i]);
        int m; scanf("%d", &m);
        for(int i = 1; i <= m; i ++)
        {
            int tmp; scanf("%d", &tmp);
            num[tmp + 1] ++;
        }
        int l = 0, r = m, mid, ans;
        while(l < r)
        {
            int mid = (l + r) / 2;
            if(check(mid)) r = mid;

            else l = mid + 1;
        }
        if(check(l)) printf("%d\n", l);
        else puts("No Solution");
    }
    return 0;
}

2-sat

404. 婚礼

在这里插入图片描述

将每个人编号为0~2n-1,第i对夫妻的妻子为i,丈夫为i+n。再将每个人i拆成两个点i和i+2n,i表示和新娘同侧,i+2n表示和新娘异侧。接下来看约束条件:添加连边(i, j)表示j和新娘同侧时,j必须和新娘同侧;添加连边(i, j+2n)表示i和新娘同侧时,j必须和新娘异侧,(i+2n,j),(i+2n,j+2n)连边含义类似。

  • 新郎在异侧:add(0,n+2n)
  • 夫妻在异侧:
    • 妻子和新娘同侧:add(i, i + 3n)
    • 丈夫和新娘同侧:add(i + n, i + 2n)
    • 妻子和新娘异侧:add(i + 2n, i + n)
    • 丈夫和新娘异侧:add(i + 3n, i)
  • 淫乱不被新娘看到:即不能同时在新娘异侧

在tarjan缩点之后如果有i和i+2n在同一个强连通分量里,则产生矛盾无解。
否则至少有一组解,在拓扑序列中,一个人拓扑序更大的点表示的情况必然可行。但是此题要求在同侧和在异侧的人的数量相同,因此可进行dfs,看一个人的一种情况a可否导向另一种情况b,若可导向则说明此人只能是情况b,否则ab两种情况均可,在最后再对此人进行分配以使得两侧人数相同。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值