图论题
题目链接:YBT2023寒假Day8 C
题目大意
给你一个无向图,然后你会一直操作直到无法操作,每次找出一个满足条件的三元组 (a,b,c),满足 a<b<c,a,b 与 a,c 之间有边,b,c 之间没有边,然后进行操作把 b,c 连边。
然后问你最后的图有多少种用 n 种颜色染色的方案使得每条边连着的两个点颜色都不同。
思路
首先考虑如果最后的图你知道了要怎么染色。
发现按编号从小到大加入似乎没有头猪,考虑反着过来。
发现反着有一个什么优点呢,就是它可以很好的利用上性质。
那你对于一个点 \(x\),它如果跟后面的边连了 \(y_1,y_2,...,y_m\),那根据我们操作的方法,我们会知道 \(y_1,y_2,...,y_m\) 之间是两两连边的。
那它们的染色一定就是两两不同的,那你 \(x\) 这个点只要跟它们都不同就行。
那我们设 \(d_i\) 为 \(i\) 点有多少条连向编号比 \(i\) 大的点,染色方案就是:\(\prod\limits_{i=1}^n(n-d_i)\)
那我们看怎么求 \(d_i\) 即可。
考虑我们直接看是否能找到一个 \((x,y)(x<y)\) 有边的充要条件。
你看到中间的媒介点有 \(<x,<y\) 的条件,那你考虑猜测是不是要存在一条 \(x\) 到 \(y\) 的路径使得路径上的点除了 \(x,y\) 别的编号都 \(<x\)。
考虑证明:
充分性:如果存在这么一条路径 \(x,p_1,p_2,...,p_m,y\),找到这些点中编号最小的点 \(p_k\)。
那对于 \(p_{k-1},p_{k+1}\) 编号都大于它而且跟它连边。
那他两就可以连边,\(p_k\) 就可以不要了。
那最后就只会剩下 \(x,y\),也就是说明有边了。
必要性:\((x,y)\) 要么直接有边,要么就是由 \((k,x),(k,y)(k<x<y)\) 生成。
那归纳一下,\((k,x)\) 的有边要么直接有,要么再由一个编号 \(<k\) 的生成,\((k,y)\) 同理。
假设由生成的全部递归下去,最后会拆成若干条边(组成路径),而这些边它的编号限制越来越小,也就肯定小于一开始的 \(x\)。
于是你设 \(A_x\) 集合是原图中所有跟 \(x\) 连边且编号比 \(x\) 大的。
\(S_i\) 则是加边之后的图的。
然后 \(S_i\) 通过上面的结论,就是 \(x\) 只走编号比 \(x\) 小的点能到达的点的 \(A_x\) 的并集中 \(>x\) 的部分。
那找 \(>x\) 的部分可以用线段树维护每个的数量。
那至于集合的并,我们可以用并查集维护边形成的连通块,然后合并的时候我们可以用线段树合并来搞。
代码
#include <bits/stdc++.h>
#define mo 998244353
#define ll long long
namespace quickio
{
const int bufferSize = 1 << 20;
char br[bufferSize], *tailr = br, *headr = br;
inline char nextChar()
{
if (headr == tailr)
tailr = (headr = br) + fread(br, 1, bufferSize, stdin);
return headr == tailr ? EOF : *headr++;
}
char bw[bufferSize], *tailw = bw;
inline void printChar(const char &ch)
{
if (tailw == bw + bufferSize)
fwrite(tailw = bw, 1, bufferSize, stdout);
*tailw++ = ch;
}
inline void flush()
{
if (tailw != bw)
fwrite(bw, 1, tailw - bw, stdout);
}
template <class T>
inline void read(T &x)
{
static char ch;
while (!isdigit(ch = nextChar()));
x = ch - '0';
while (isdigit(ch = nextChar()))
x = x * 10 + ch - '0';
}
template <class T>
inline void putint(T x)
{
static char 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;
using namespace std;
const int N = 1e6 + 100;
int n, m, rt[N], fa[N], d[N];
vector <int> G[N];
struct XD_tree {
int ls[N << 6], rs[N << 6], f[N << 6], tot;
void up(int now) {
f[now] = f[ls[now]] + f[rs[now]];
}
void insert(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);
else insert(rs[now], mid + 1, r, pl);
up(now);
}
int query(int now, int l, int r, int L, int R) {
if (!now) return 0;
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;
}
int merge(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;
int find(int now) {
if (fa[now] == now) return now;
return fa[now] = find(fa[now]);
}
int main() {
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();
return 0;
}
标签:now,查集,Day8,int,YBT2023,tail,ch,编号,x1
From: https://www.cnblogs.com/Sakura-TJH/p/YBT2023Day8_C.html