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里的所有节点
这是竞赛图