【NOI2013模拟】坑带的树(仙人球的同构+圆方树乱搞+计数+HASH)

【NOI2013模拟】坑带的树

  • 题意:

    • \(n\)个点,\(m\)条边的同构仙人球个数.
    • \(n\le 1000\)
  • 这是一道怎么看怎么不可做的题。

  • 这种题,肯定是圆方树啦~

  • 好,那么首先转为广义圆方树.

  • 圆方树上有两种点(废话),那么对于一个方点,它实际上代表的是一个点双,所以我们需要判断一个方点的子树是否中间对称,如果对称则这个子树答案乘\(2\).

  • 显然.

  • 然后判断一个圆点与几个方点相连时,注意到方点之间是可以互相交换顺序的,于是我们看看有多少个子树相同,乘个阶乘.

  • 最后就是求同构仙人球的个数了.

  • 这个比较麻烦,但也不难。。

  • 实质上我们可以求一个\(Hash\)值,每次从一个点的父亲节点对应的那条边开始找,找到最后一条边,然后再从第一条边开始找,找到父亲那条边.

  • 这样,对于单独一个环,在环内的两个点,父亲的那条边始终是相同的,所以我们就正反扫一遍找到的边,然后取个Hash值的\(min\)

  • 实现比较困难,但需要奋斗!

  • 这里有个小trick,那就是当我们缩点双时,一定要按照一个固定的顺序去缩,具体代码里会有说。。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

#define I register int
#define F(i, a, b) for (I i = a; i <= b; i ++)
#define G(i, a, b) for (I i = a; i >= b; i --)
#define mem(a, b) memset(a, b, sizeof a)
#define mec(a, b) memcpy(a, b, sizeof a)
#define mn(a, b) ((a) = (a) < (b) ? (a) : (b))
#define mx(a, b) ((a) = (a) > (b) ? (a) : (b))
#define Get getchar()

const int N = 2010, M = 30 * N, Z[5] = {1532531, 12136371, 1232535, 1846315, 1463322}, Mo[5] = {16232312, 12322323, 12552344, 74556263, 13223623}, mo = 1000000003LL;

using namespace std;

int n, m, x, y, answer, bz, New, cnt, top, rt, len, dfn[M], low[M], d[M], size[N], Ans[N][5], HASH[N][5], hash[N][5], ans[M], H[M], jc[M], fath[M], vis[M];
int tov[M], nex[M], las[M], tot;
int To[M], Nx[M], Ls[M], TOT;
struct node { int v , num; } D[M];

void R(I &x) {
	char c = Get; x = 0; I t = 1;
	for (; !isdigit(c); c = Get) t = (c == '-' ? -1 : t);
	for (; isdigit(c); x = (x << 3) + (x << 1) + c - '0', c = Get); x *= t;
}

bool cmp(node x, node y) { return x.v < y.v; }

void ins(I x, I y) { tov[ ++ tot ] = y, nex[ tot ] = las[x], las[x] = tot; }

void link(I x, I y) {
	To[++ TOT] = y, Nx[TOT] = Ls[x], Ls[x] = TOT;
	To[++ TOT] = x, Nx[TOT] = Ls[y], Ls[y] = TOT;
}
void Tarjan(I k, I edge) {
	dfn[k] = low[k] = ++ cnt, d[++ top] = k;
	for (I x = las[k]; x; x = nex[x])
		if (!dfn[tov[x]]) {
			Tarjan(tov[x], x), mn(low[k], low[tov[x]]);
			if (low[tov[x]] >= dfn[k]) {
				link(k, ++ New), link(d[top --], New); 
				while (d[top + 1] ^ tov[x]) link(d[top --], New);
				//我最后两句话想说的就是这个地方,千万不能打成这样:
				//link(d[top --], ++ New), link(k, New);
				//while (d[top + 1] ^ tov[x]) link(d[top --], New);
				//为什么不必多说,意会意会
			}
		} else if (x ^ (edge ^ 1)) mn(low[k], dfn[tov[x]]);
}

void GetHash(I k, I fa) {
	size[k] ++;
	for (I x = Ls[k] ; x ; x = Nx[x])
		if (To[x] ^ fa) GetHash(To[x], k), size[k] += size[To[x]];
	for (len = 0, x = Ls[k]; x ; x = Nx[x])
		if (To[x] ^ fa) D[++ len] = {size[To[x]], To[x]};
	sort(D + 1, D + len + 1, cmp);
	F(i, 0, 4) {
		HASH[k][i] = 1;
		F(j, 1, len) HASH[k][i] = (1LL * HASH[k][i] * D[j].v + 1LL * HASH[D[j].num][i] * Z[i] + 1LL * D[j].v * Z[i]) % Mo[i];
	}
}

bool Good(I p, I x, I q, I y) {
	F(i, 0, 4) if (HASH[x][i] ^ HASH[y][i]) return 1;
	return 0;
}

void GetAns(I k, I fa) {
	ans[k] = 1;
	for (I x = Ls[k] ; x ; x = Nx[x])
		if (To[x] ^ fa) GetAns(To[x], k), ans[k] = (1LL * ans[k] * ans[To[x]]) % mo;
	for (bz = 1, len = 0, x = Ls[k]; x; x = Nx[x])
		if (To[x] ^ fa) H[++ len] = To[x];
	if (k > n) {
		F(i, 1, len / 2)
			if (Good(rt, H[i], rt, H[len - i + 1])) { bz = false; break; }
		if (bz && (len / 2)) ans[k] = (ans[k] * 2LL) % mo;
	}
		else
	{
		F(i, 1, len) D[i] = {HASH[H[i]][0], H[i]};
		sort(D + 1, D + len + 1, cmp), cnt = 1;
		F(i, 2, len)
			if (!Good(rt, D[i].num, rt, D[i - 1].num)) cnt ++; else ans[k] = (1LL * ans[k] * jc[cnt]) % mo, cnt = 1;
		ans[k] = (1LL * ans[k] * jc[cnt]) % mo;
	}
}

void GotHash(I k, I fa) { I y = 0;
	for (I x = Ls[k]; x ; x = Nx[x])
		if (To[x] ^ fa) GotHash(To[x], k); else y = x; //像我刚刚说的一样,从父亲那条边开始找起来,huhuhu
	for (len = 0, x = Nx[y]; x ; x = Nx[x])
		D[++ len] = { hash[To[x]][0], To[x] };
	for (x = Ls[k] ; x ^ y; x = Nx[x])
		if (To[x] ^ fa) D[++ len] = { hash[To[x]][0], To[x] };

	if (k <= n) { //圆点和方点分开讨论哟
		sort(D + 1, D + len + 1, cmp);
		F(i, 0, 4) {
			hash[k][i] = 1;
			F(j, 1, len) hash[k][i] = (1LL * hash[k][i] * D[j].v + 1LL * hash[D[j].num][i] * Z[i] + 1LL * D[j].v * Z[i]) % Mo[i];
		}
	}
		else
	F(i, 0, 4) {
		x = y = 1;
		F(j, 1, len) x = (1LL * x * D[j].v + 1LL * hash[D[j].num][i] * Z[i] + 1LL * D[j].v * Z[i]) % Mo[i];
		G(j, len, 1) y = (1LL * y * D[j].v + 1LL * hash[D[j].num][i] * Z[i] + 1LL * D[j].v * Z[i]) % Mo[i];
		hash[k][i] = min(x, y);
	}
}

int main() {
	R(n), R(m), tot = 1, New = n;
	F(i, 1, m) R(x), R(y), ins(x, y), ins(y, x);
	
	Tarjan(1, 0), GetHash(1, 0);
	
	jc[0] = 1; F(i, 1, n) jc[i] = (1LL * jc[i - 1] * i) % mo;

	rt = 1, GetAns(rt, 0), answer = ans[rt], cnt = 1;
	
	//一下就是求同构仙人球的个数了
	GotHash(rt, 0), mec(Ans, hash);
	F(i, 1, n) {
		if (i == rt) continue;

		GotHash(i, 0), bz = 0;
		F(j, 0, 4)
			if (hash[i][j] ^ Ans[rt][j]) { bz = 1; break; }
		if (!bz) cnt ++;
	}

	printf("%d\n", (1LL * answer * cnt) % mo);
}
posted @ 2018-07-28 21:11  proking  阅读(304)  评论(1编辑  收藏  举报