发现反着有一个什么优点呢,就是它可以很好的利用上性质。
那你对于一个点 x,它如果跟后面的边连了 y1,y2,...,ym,那根据我们操作的方法,我们会知道 y1,y2,...,ym 之间是两两连边的。
那它们的染色一定就是两两不同的,那你 x 这个点只要跟它们都不同就行。
那我们设 di 为 i 点有多少条连向编号比 i 大的点,染色方案就是:n∏i=1(n−di)
那我们看怎么求 di 即可。
考虑我们直接看是否能找到一个 (x,y)(x<y) 有边的充要条件。
你看到中间的媒介点有 <x,<y 的条件,那你考虑猜测是不是要存在一条 x 到 y 的路径使得路径上的点除了 x,y 别的编号都 <x。
考虑证明:
于是你设 Ax 集合是原图中所有跟 x 连边且编号比 x 大的。 Si 则是加边之后的图的。
然后 Si 通过上面的结论,就是 x 只走编号比 x 小的点能到达的点的 Ax 的并集中 >x 的部分。
那找 >x 的部分可以用线段树维护每个的数量。
那至于集合的并,我们可以用并查集维护边形成的连通块,然后合并的时候我们可以用线段树合并来搞。
代码
#include<bits/stdc++.h>#define mo 998244353#define ll long longnamespace quickio
{
constint bufferSize = 1 << 20;
char br[bufferSize], *tailr = br, *headr = br;
inlinecharnextChar(){
if (headr == tailr)
tailr = (headr = br) + fread(br, 1, bufferSize, stdin);
return headr == tailr ? EOF : *headr++;
}
char bw[bufferSize], *tailw = bw;
inlinevoidprintChar(constchar &ch){
if (tailw == bw + bufferSize)
fwrite(tailw = bw, 1, bufferSize, stdout);
*tailw++ = ch;
}
inlinevoidflush(){
if (tailw != bw)
fwrite(bw, 1, tailw - bw, stdout);
}
template <classT>
inlinevoidread(T &x){
staticchar ch;
while (!isdigit(ch = nextChar()));
x = ch - '0';
while (isdigit(ch = nextChar()))
x = x * 10 + ch - '0';
}
template <classT>
inlinevoidputint(T x){
staticchar buf[15], *tail = buf;
if (!x)
printChar('0');
else {
for (; x; x /= 10) *++tail = x % 10 + '0';
for (; tail != buf; --tail) printChar(*tail);
}
}
}
using quickio::read;
using quickio::putint;
using quickio::printChar;
usingnamespace std;
constint N = 1e6 + 100;
int n, m, rt[N], fa[N], d[N];
vector <int> G[N];
structXD_tree {
int ls[N << 6], rs[N << 6], f[N << 6], tot;
voidup(int now){
f[now] = f[ls[now]] + f[rs[now]];
}
voidinsert(int &now, int l, int r, int pl){
if (!now) now = ++tot;
if (l == r) {
f[now] = 1; return ;
}
int mid = (l + r) >> 1;
if (pl <= mid) insert(ls[now], l, mid, pl);
elseinsert(rs[now], mid + 1, r, pl);
up(now);
}
intquery(int now, int l, int r, int L, int R){
if (!now) return0;
if (L <= l && r <= R) return f[now];
int mid = (l + r) >> 1, re = 0;
if (L <= mid) re += query(ls[now], l, mid, L, R);
if (mid < R) re += query(rs[now], mid + 1, r, L, R);
return re;
}
intmerge(int x1, int x2, int l, int r){
if (!x1 || !x2) return x1 + x2;
if (l == r) {
f[x1] |= f[x2]; return x1;
}
int mid = (l + r) >> 1;
ls[x1] = merge(ls[x1], ls[x2], l, mid);
rs[x1] = merge(rs[x1], rs[x2], mid + 1, r);
up(x1); return x1;
}
}T;
intfind(int now){
if (fa[now] == now) return now;
return fa[now] = find(fa[now]);
}
intmain(){
freopen("graph.in", "r", stdin);
freopen("graph.out", "w", stdout);
// scanf("%d %d", &n, &m);read(n); read(m);
for (int i = 1; i <= m; i++) {
// int x, y; scanf("%d %d", &x, &y);int x, y; read(x); read(y);
if (x < y) swap(x, y); G[x].push_back(y);
T.insert(rt[y], 1, n, x);
}
for (int i = 1; i <= n; i++) fa[i] = i;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < G[i].size(); j++) {
int x = G[i][j];
int X = find(i), Y = find(x);
if (X == Y) continue;
rt[Y] = T.merge(rt[Y], rt[X], 1, n); fa[X] = Y;
}
d[i] = T.query(rt[find(i)], 1, n, i + 1, n);
}
ll ans = 1;
for (int i = 1; i <= n; i++)
ans = ans * (n - d[i]) % mo;
putint((int)ans);
// printf("%lld", ans); quickio::flush();
return0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
2022-02-08 【YBT2022寒假Day3 C】毒瘤染色(LCT)(圆方树)(容斥)
2022-02-08 【YBT2022寒假Day3 B】【LOJ 2460】欧拉回路 / 桥(二分)(欧拉回路)(网络流)
2022-02-08 【YBT2022寒假Day3 A】森林之和(prufer序列)(DP)
2021-02-08 GDKOI2021 爆炸记