Oct. Training 1 -图论
H - Boboniu Walks on Graph -图论、集合哈希
https://codeforces.com/problemset/problem/1394/B
题意
给n个点m条有向边,么个点的出度不超过k(k<=9),每条边都有一个边权在(\(1<=w<=m\))且每条边权都不相同,
求有多少个k元组(c1,c2,c3,..ck)满足对于某个点,出度为x,则它的所有出边中,只能走边权第\(c_x\)大的边,然后每个点都能从自己出发走回自己。
思路
不难发现,要满足所有点都能走回自己,那必然是一个有向环。
因为k很小,我们可以暴力枚举所有情况,复杂度为k!.
为了快速check是否满足条件,我们用hash,每个点给一个点权,比对点权和来判断是否满足条件,复杂度变成了\(O(nk!)\)
这样任然超时,可以再做个预处理,将出度为i,选择走第j条边的所有边累加给 sum[i][j],后续就可以o(1)地加答案了。
复杂度变成了O(k * k!)
#include<bits/stdc++.h>
#include<array>
#define ll long long
#define all(a) a.begin(),a.end()
using namespace std;
const int N = 2e5 + 5;
const int M = 1e6 + 1;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const ll mod = 1e9 + 7;
#define int ll
int n, m, k, tot, cnt;
int bs = 131, base[N], out[N], sum[10][10], c[10];
vector<pair<int, int>>g[N];
void check() {
int temp = 0;
for (int i = 1; i <= k; i++) {
temp = (temp + sum[i][c[i]]) % mod;
}
if (temp == tot) cnt++;
}
void dfs(int x, int ans) {
if (x > k) {
if (ans == tot) cnt++;
}
else {
for (int i = 1; i <= x; i++) {
dfs(x + 1, (ans + sum[x][i]) % mod);
}
}
}
void solve() {
cin >> n >> m >> k;
base[0] = 1;
for (int i = 1; i <= n; i++) {
base[i] = (base[i - 1] * bs) % mod;
tot = (tot + base[i]) % mod;
}
for (int i = 1, u, v, w; i <= m; i++) {
cin >> u >> v >> w;
g[u].push_back({ w, v });
out[u]++;
}
for (int i = 1; i <= n; i++)
sort(g[i].begin(), g[i].end());
//预处理
for (int i = 1; i <= n; i++) {
for (int j = 0; j < out[i]; j++) {
sum[out[i]][j + 1] = (sum[out[i]][j + 1] + base[g[i][j].second]) % mod;
}
}
dfs(1, 0);
cout << cnt << "\n";
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int _t = 1;
//cin >> _t;
while (_t--) {
solve();
}
return 0;
}
M - Moment of Bloom
https://codeforces.com/problemset/problem/1583/E
题意
给一张图,和m组简单路径的起点和终点,对于每组起点和终点,你需要选定一条对应的简单路径,然后将该简单路径上的边的权值就加上1(一开始每条边权都是0),
问能否经过这m次操作,使得整个图只有偶数的边权,若有输出任意方案。
思路
有一个结论是:m组数中同一端点出现的次数必须是偶数次,不然无解。如果是奇数次,那就意味着该点出去的某条边一定被操作奇数次,那最后肯定是奇数边权。
如果每个端点出现的次数是偶数次,我们一定能选择对应的路径符合最后要求。
可以构造一颗生成树,对于树的两个节点,只存在一条简单路径,它不会出现环,所以只要满足上面条件的,所访问边的次数一定是偶数次(这点可以画个图理解)。
#include<bits/stdc++.h>
#include<array>
#define ll long long
#define all(a) a.begin(),a.end()
using namespace std;
const int N = 5e5 + 5;
const int M = 1e6 + 1;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const int inf = 0x3f3f3f3f;
const ll mod = 998244353;
#define int ll
ll n, m, q;
vector<int>g[N], g2[N], ans;
int cnt[N], vis[N];
pair<int, int>p[N];
void dfs(int x, int fa) {
if (fa > 0) g2[fa].push_back(x), g2[x].push_back(fa);
vis[x] = 1;
for (auto to : g[x]) {
if (to == fa) continue;
if(!vis[to]) dfs(to, x);
}
}
int dfs2(int x, int fa, int ed) {
int f = 0;
if (x == ed) f = 1;
else for (auto to : g2[x]) {
if (to == fa) continue;
if (dfs2(to, x, ed)) f = 1;
}
if (f) ans.push_back(x);
return f;
}
void solve() {
cin >> n >> m;
for (int i = 1, u, v; i <= m; i++) {
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
cin >> q;
for (int i = 1, u, v; i <= q; i++) {
cin >> u >> v;
p[i] = { u, v };
cnt[u]++;
cnt[v]++;
}
int cntodd = 0;
for (int i = 1; i <= n; i++) {
if (cnt[i] % 2) cntodd++;
}
if (cntodd) {
cout << "NO\n";
cout << cntodd / 2 << "\n";
return;
}
//构造生成树
dfs(1, -1);
cout << "YES\n";
for (int i = 1; i <= q; i++) {
int st = p[i].first, ed = p[i].second;
ans.clear();
//找路径
dfs2(st, -1, ed);
cout << ans.size() << "\n";
for (int i = ans.size() - 1; i >= 0; i--)
cout << ans[i] << " ";
cout << '\n';
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
int _t = 1;
//cin >> _t;
while (_t--) {
solve();
}
return 0;
}
F. Session in BSU
https://codeforces.com/problemset/problem/1027/F
题意
n场考试,第i考试可以选择第a[i]天考出或者第b[i]天考出,问最早把所有考试都考完的时间。一天只能考一场,无法考完所有考试输出-1。
思路
按图论的思想,我们把一条边当做一场考试,然后它连接两个点,分别是它的两个选择a[i],b[i],然后就可以构造成一张图。
对于一个连通块,如果边的数量比点的数量多,那就是考不完的。
如果点的数量和边的数量一样多,那每个点都要选。
如果边的数量少于点的数量(最多只可能少1,连通图的性质),那我们可以舍去权值最大的一个点。
对于如何实现,我们可以跑图,也可以用并查集维护。
将a[i]和b[i]放入一个集合中,对于每个集合维护点数和边数,最后遍历每个集合即可。
#include<bits/stdc++.h>
#include<unordered_map>
#include<array>
#define ll long long
#define int ll
#define ull unsigned long long
#define all(a) a.begin(),a.end()
#define IOS ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
using namespace std;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const double eps = 1e-8;
const ll mod = 1e9 + 7;
const int N = 2e6 + 5;
const int maxn = 1e5 + 10;
int n, fa[N], sz1[N], sz2[N], val[N], vis2[N], cant[N];
int a[N], b[N];
vector<int> v[N], ve;
int find(int x) {
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
void solve()
{
cin >> n;
int tot = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
cin >> b[i];
ve.push_back(a[i]);
ve.push_back(b[i]);
}
ve.push_back(0);
sort(ve.begin(), ve.end());
ve.erase(unique(ve.begin(), ve.end()) , ve.end());
for (int i = 1; i <= n; i++) {
int p1 = lower_bound(ve.begin(), ve.end(), a[i]) - ve.begin();
int p2 = lower_bound(ve.begin(), ve.end(), b[i]) - ve.begin();
val[p1] = a[i];
a[i] = p1;
val[p2] = b[i];
b[i] = p2;
}
for (int i = 1; i <= n * 2; i++){
fa[i] = i;
sz1[i] = 1;
sz2[i] = 0;
}
for (int i = 1; i <= n; i++) {
int u = a[i], v = b[i];
u = find(u);
v = find(v);
if (u != v) {
fa[u] = fa[v];
sz1[v] += sz1[u];
sz2[v] += sz2[u] + 1;
}
else sz2[u]++;
}
tot = ve.size() - 1;
for (int i = 1; i <= tot; i++)
v[find(i)].push_back(i);
for (int i = 1; i <= tot; i++) {
int f = find(i);
if (vis2[f] || !v[f].size()) continue;
vis2[f] = 1;
if (sz2[f] > sz1[f]) {
cout << -1 << "\n";
return;
}
if (sz2[f] < sz1[f]) {
int mx = 0, id = 0;
for (auto x : v[f]) {
if (val[x] > mx) {
mx = val[x];
id = x;
}
}
cant[id] = 1;
}
}
int ans = 0;
for (int i = 1; i <= tot; i++) {
if (!cant[i])
ans = max(ans, val[i]);
}
cout << ans << "\n";
}
signed main()
{
IOS;
int _t = 1;
//cin >> _t;
while (_t--)
solve();
return 0;
}

浙公网安备 33010602011771号