P8819 CSP-S 2022 星战
P8819 CSP-S 2022 星战 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
先翻译一下题目:
- 失活某条边;
- 失活以某个点为终点的所有边;
- 激活某条边;
- 激活以某个点为终点的所有边。
然后问:如果只考虑激活的边,是否满足:
- 所有的点出度均为
; - 所有的点都满足,从这个点出发,可以走到一个环中。
首先我们发现,如果所有点的出度均为
因此压根不用判环,只用判断所有点出度是否均为
事实上,所有点出度均为
的有向图称作基环内向森林,它由若干个弱联通块(将所有有向边看成无向边后的联通块)构成,每个弱联通块称作基环内向树。对于基环内向树,它可以被认为是由有向环和长在环上的若干条链构成。这些链单向导通到环上。更多有关定义可以自行阅读 OI Wiki 的图论相关概念。
我们观察到,一、三操作可以用
不过,前 8 个测试点,是支持我们用最坏时间复杂度
然后 9 和 10 两个测试点还是保证没有二、四操作的。单次操作可以做到
这里是我的 50 分代码。
思考许久后我发现,容易维护的不是出度,而是入度。具体来说:
设原图上点
- 失活
: ; - 失活以
为终点的所有边: ; - 激活
: ; - 激活以
为终点的所有边: 。
这些都可以
那么入度和出度有什么关系呢?
一张图里,所有点的入度和等于出度和。我们的目标是所有出度都是
于是我们得到所有出度均为
比如,如果有三个点,点
但是,我们可以利用 哈希 使得这个条件的充分性有极大概率是正确的,进一步使得这个条件的充要性极大概率正确,通过本题。
我们初始给每个点随机一个权值
而
重新设计:
- 失活
: ; - 失活以
为终点的所有边: ; - 激活
: ; - 激活以
为终点的所有边: 。
这个过程中,所有点的
- 设
是 的入点集合,则 。理由是 的定义。 - 如果所有点
的出度都是 ,那么 只会出现在它所连向的 的入点集合中。因此,将所有入点集合的权值加起来,即 ,我们应该恰好得到 。也即, 。
因此,
一般地,我们设点
显然所有
有,但是这样的一组
又因为你的
这就是哈希的原理了:判定时构造一个必要不充分条件,但这个“不充分”实际上有非常大非常大的概率充分,以至于不充分性可以忽略不计,从而达到充要判定的效果。读者应当熟悉的字符串哈希,也是同样的原理。
这种哈希似乎一般称作“和哈希”(sum hash)。
/*
* @Author: crab-in-the-northeast
* @Date: 2022-10-31 05:16:58
* @Last Modified by: crab-in-the-northeast
* @Last Modified time: 2022-10-31 05:23:44
*/
#include <bits/stdc++.h>
#define int long long
inline int read() {
int x = 0;
bool flag = true;
char ch = getchar();
while (!isdigit(ch)) {
if (ch == '-')
flag = false;
ch = getchar();
}
while (isdigit(ch)) {
x = (x << 1) + (x << 3) + ch - '0';
ch = getchar();
}
if(flag)
return x;
return ~(x - 1);
}
const int maxn = (int)5e5 + 5;
int r[maxn], w[maxn], g[maxn];
signed main() {
int n = read(), m = read();
std :: mt19937 rng(time(0));
for (int u = 1; u <= n; ++u)
w[u] = rng(); // 这个函数生成随机数的范围和 unsigned int 范围一致,会爆 int。当然你也可以 % 一下
int tar = std :: accumulate(w + 1, w + n + 1, 0LL);
// 这是求和函数,注意 0 后面要加 LL (否则会爆)
int now = 0;
while (m--) {
int u = read(), v = read();
r[v] += w[u];
g[v] = r[v];
now += w[u];
}
int q = read();
while (q--) {
int t = read();
if (t == 1) {
int u = read(), v = read();
r[v] -= w[u];
now -= w[u];
} else if (t == 2) {
int v = read();
now -= r[v];
r[v] = 0;
} else if (t == 3) {
int u = read(), v = read();
r[v] += w[u];
now += w[u];
} else if (t == 4) {
int v = read();
now += g[v] - r[v];
r[v] = g[v];
}
puts(now == tar ? "YES" : "NO");
}
return 0;
}
如果觉得这篇题解写得好,请不要忘记点赞,谢谢!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!