20250716 连通性问题/竞赛图

P1407 [国家集训队] 稳定婚姻

我们已知 n 对夫妻的婚姻状况,称第 i 对夫妻的男方为 Bi​,女方为 Gi​。若某男 Bi​ 与某女 Gj​ 曾经交往过(无论是大学,高中,亦或是幼儿园阶段,i=j),则当某方与其配偶(即 Bi​ 与 Gi​ 或 Bj​ 与 Gj​)感情出现问题时,他们有私奔的可能性。不妨设 Bi​ 和其配偶 Gi​ 感情不和,于是 Bi​ 和 Gj​ 旧情复燃,进而 Bj​ 因被戴绿帽而感到不爽,联系上了他的初恋情人 Gk​ ……一串串的离婚事件像多米诺骨牌一般接踵而至。若在 Bi​ 和 Gi​ 离婚的前提下,这 2n 个人最终依然能够结合成 n 对情侣,那么我们称婚姻 i 为不安全的,否则婚姻 i 就是安全的。

给定所需信息,你的任务是判断每对婚姻是否安全。

我们尝试着将所有的丈夫和妻子用线段连接起来,表示他们之间存在着联系,如果这时有一个女孩和其中的丈夫交往过,那么他们之间也存在着这种联系,所以这两种情况我们可以认为本质是相同的。
那么我们将所有 现在或曾经交往过的 男孩和女孩连接起来,可以发现出现了一些环,而处在环中的几对夫妻都可以更换伴侣,也就是题目中所说的婚姻不安全。那么我们找出这些环,判断哪些夫妻处在环中即可。
对于找环,我们想到了Tarjan求强连通分量,但是这个算法是在有向图上进行的,于是我们尝试给我们连接出的无向图定向,发现只要按照 ...男→女→男→女... 的顺序,男女交替就可以Tarjan判出环来。
所以我们可以这样建图:

夫妻之间:girl→boy
情人之间:boy→girl

Tarjan求强连通分量,对于一对夫妻,如果两人在同一个强连通分量里,那么这对婚姻就是不安全的,反之,则是安全的。


P8867 [NOIP2022] 建造军营

P2662 牛场围栏

小L有 N 种可以建造围栏的木料,长度分别是 l1​,l2​,⋯,lN​,每种长度的木料无限。

修建时,他将把所有选中的木料拼接在一起,因此围栏的长度就是他使用的木料长度之和。但是聪明的小L很快发现很多长度都是不能由这些木料长度相加得到的,于是决定在必要的时候把这些木料砍掉一部分以后再使用。

不过由于小L比较节约,他给自己规定:任何一根木料最多只能削短 M 米。当然,每根木料削去的木料长度不需要都一样。不过由于测量工具太原始,小L只能准确的削去整数米的木料,因此,如果他有两种长度分别是 7 和 11 的木料,每根最多只能砍掉 1 米,那么实际上就有 4 种可以使用的木料长度,分别是 6,7,10,11。

因为小L相信自己的奶牛举世无双,于是让他们自己设计围栏。奶牛们不愿意自己和同伴在游戏时受到围栏的限制,于是想刁难一下小L,希望小L的木料无论经过怎样的加工,长度之和都不可能得到他们设计的围栏总长度。不过小L知道,如果围栏的长度太小,小L很快就能发现它是不能修建好的。因此她希望得到你的帮助,找出无法修建的最大围栏长度。


同余最短路

首先如果有1或者所有数字的GCD大于1,那么无解

然后差分约束,将最小的设为模数,把每个剩余系Ki​抽象为图中的点,那么连接它们的边就是Ai​中的那些数。

点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll INF = (1LL<<60);

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int N, M;
    cin >> N >> M;
    vector<int> l(N);
    for(int i = 0; i < N; i++){
        cin >> l[i];
    }

    const int MAXV = 3000;
    vector<bool> has(MAXV+1, false);
    for(int x : l){
        int lo = max(1, x - M);
        int hi = x;
        for(int d = lo; d <= hi; d++){
            has[d] = true;
        }
    }
    vector<int> D;
    for(int d = 1; d <= MAXV; d++){
        if(has[d]) D.push_back(d);
    }

    if (D.empty()) {
        cout << -1 << "\n";
        return 0;
    }

    bool has1 = has[1];
    if(has1){
        cout << -1 << "\n";
        return 0;
    }
    int g = 0;
    for(int d : D){
        g = gcd(g, d);
    }
    if(g > 1){
        cout << -1 << "\n";
        return 0;
    }

    int a = *min_element(D.begin(), D.end());
    vector<ll> dist(a, INF);
    dist[0] = 0;
    using pli = pair<ll,int>;
    priority_queue<pli, vector<pli>, greater<pli>> pq;
    pq.emplace(0, 0);

    while(!pq.empty()){
        auto [d, u] = pq.top(); pq.pop();
        if(d > dist[u]) continue;
        for(int c : D){
            int v = (u + c) % a;
            ll nd = d + c;
            if(nd < dist[v]){
                dist[v] = nd;
                pq.emplace(nd, v);
            }
        }
    }

    ll ans = 0;
    for(int r = 0; r < a; r++){
        if(dist[r] == INF){
            cout << -1 << "\n";
            return 0;
        }
        ans = max(ans, dist[r] - a);
    }
    cout << ans << "\n";
    return 0;
}


P9140 [THUPC 2023 初赛] 背包

本题中,你需要解决完全背包问题。

有 n 种物品,第 i 种物品单个体积为 vi​、价值为 ci​。

q 次询问,每次给出背包的容积 V,你需要选择若干个物品,每种物品可以选择任意多个(也可以不选),在选出物品的体积的和恰好为 V 的前提下最大化选出物品的价值的和。你需要给出这个最大的价值和,或报告不存在体积和恰好为 V 的方案。

为了体现你解决 NP-Hard 问题的能力,V 会远大于 vi​,详见数据范围部分。


考虑做 mod vk​ 的同余最短路,并用走过一条边表示加入新的元素,以及超过 vk​ 后所必要的把原来的 k 扔掉的操作。vk是性价比最高的点

具体地,对于在取模后为 i 的基础上加上物品 j,我们连边
图片

直接跑 SPFA 即可。时间复杂度为 O(SPFA(maxvi​,nmaxvi​)+q)。

但是会T

考虑一个一个加入物品,此时可以转移的项在 mod m 意义下形成了 gcd(vj​,m) 个环。

类似于 [模拟赛 2022.11.18] 风信子,注意到我们只需要在每个环上转两圈就可以求出答案——要不然原图存在负环,而本题选出性价比最高的元素的体积作为模数事实上保证了这一点。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
using ll = long long;

int v[57], c[57];
ll dis[100007];

int gcd(int a, int b){
	return b == 0 ? a : gcd(b, a % b);
}

inline ll max(ll a, ll b){
	return a > b ? a : b;
}
constexpr ll INF = 0x8000000000000000ll;
int main(){
	int n, q, base = -1;
	cin>>n>>q;
	for (int i = 1; i <= n; i++){
		cin>>v[i]>>c[i];
		if (base == -1 || (ll)c[base] * v[i] < (ll)c[i] * v[base]) base = i;
	}
	for (int i = 1; i < v[base]; i++){
		dis[i] = INF;
	}
	for (int i = 1; i <= n; i++){
		if (i == base) continue;
		int d = gcd(v[i], v[base]), len = v[base] / d * 2;
		for (int j = 0; j < d; j++){
			for (int k = j, l = 1; l <= len; l++){
				int newv = k + v[i], nxt = newv % v[base];
				if (dis[k] != INF) dis[nxt] = max(dis[nxt], dis[k] + c[i] - (ll)newv / v[base] * c[base]);
				k = nxt;
			}
		}
	}
	for (int i = 1; i <= q; i++){
		ll V, rem;
		scanf("%lld", &V);
		rem = V % v[base];
		if (dis[rem] == INF){
			printf("-1\n");
		} else {
			printf("%lld\n", V / v[base] * c[base] + dis[rem]);
		}
	}
	return 0;
}


竞赛图

P3561 [POI 2017] Turysta

给出一个 n 个点的有向图,任意两个点之间有且仅一条有向边。

对于每个点 v,求出从 v 出发的一条经过点数最多,且没有重复经过同一个点两次及两次以上的简单路径。
输入格式

第一行包含一个正整数 n,表示点数。

接下来的 n−1 行,其中的第 i 行有 i−1 个数。

如果第 j 个数是 1,那么表示有向边 j→i+1 ,如果是 0,那么表示有向边 j←i+1。


性质1 缩点之后是一条链

性质2 竞赛图必有哈密顿通路(所以这是构造而不是最优化)

性质3 强连通竞赛图必有哈密顿回路。

首先我们显然可以通过插入法,构造强连通竞赛图的哈密顿回路并构造整个的哈密顿路径。

缩点后的 DAG 不仅仅是无环的,它还是一个“传递的竞赛图”

路径只能“向前”走,即从 S_i 走到 S_i+1,绝不可能从 S_i+1 走回 S_i。否则 S_i 和 S_i+1 就属于同一个 SCC 了。

为了让路径最长,我们的策略应该是:在一个 SCC 内部尽可能多地经过点,然后移动到下一个 SCC,再把那个 SCC 的所有点都走遍,以此类推,直到最后一个 SCC。

步骤 1:用 Tarjan 算法求所有强连通分量

步骤 2:在每个 SCC 内部寻找哈密顿回路

步骤 3:为每个点拼接最终的最长路径

我们遍历 cntScc 个 SCC,往下走计算是O(n)的。

我们可以保证回路差一步重复时一定有边连向下一个SCC里的所有节点

这是竞赛图


posted @ 2025-07-16 22:07  Dreamers_Seve  阅读(10)  评论(0)    收藏  举报