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;
}
posted @ 2022-10-22 11:01  Yaqu  阅读(32)  评论(0)    收藏  举报