jzoj6423 画 (容斥计数)

题意

一张无向图,每个点可以染为 [ 0 , l i m i ] [0,lim_i] 中的一个整数。要求:

  1. 所有点颜色异或和为给定的C
  2. 是一个合法的染色(有边相连的两个点不能染相同的颜色)

求满足要求的方案数。

n 15 , m n ( n 1 ) / 2 n\leq15,m\leq n(n-1)/2
C , l i m i 1 0 18 C,lim_i\leq10^{18}

思路

  • 考虑没有2限制怎么做,最裸的是一个 O ( 4 n log C ) O(4^n\log C) 的状压做法。
  • 然而这个做法太low了。不如利用一下异或的唯一可逆性。
  • 从高到低枚举第一个存在脱离lim限制的数的位。对于高位是确定的,低位只需要拿出第一个脱离限制的数将异或和调整到C,其他数的低位就可以随意选。再保证当前位异或和=C即可。
  • 上述做法使用一个递推计点数,是 O ( n log C ) O(n\log C) 的。(pty杂题刚讲过为啥我就是不会呢???)
  • 接下来考虑边的限制怎么搞。注意到可以直接对边容斥:
  • 枚举有多少边不满足并带上-1的系数,那么这些边连成了一些连通块,每个连通块内是一致的。
  • 偶数大小的连通块对异或和没有贡献,直接乘上取值集合大小即可。
  • 奇数大小的连通块全部提出来,做没有限制的部分。
  • 接下来用一个状压递推来模拟上述过程。设状态 f ( s , t ) f(s,t) 为当前用的点集合为s,奇数连通块中lim最小的点集合是t。
  • 每次加入一个连通块,并乘上连通系数g。指的是选择块内的边使得其成为连通块的方案权值之和。这个可以通过减去不合法的预处理得到。
  • 最后再将每个满状态的f乘上对应的part1答案即可。

时间复杂度分析:
记s中的点为状态1,t中的点为状态2,否则为状态0。注意到若按照特定的转移顺序,状态2前不会有状态0。因此考虑最后一个2,前面是1/2,后面是0/1。有 O ( n 2 n ) O(n2^n) 种合法状态。然后再枚举后面0状态的一个子集进行转移。列式分析后发现是 O ( 3 n ) O(3^n) 的。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int N = 16, mo = 998244353;

int edge[N], w[N];
int n, m;
ll C;
ll lim[N];
ll g[1<<N], ec[1<<N];
ll f[43046721];
int mi[N];
int ful;

#define lowbit(x) ((x) & -(x))
void init_g() {
	for(int w = 1; w < ful; w <<= 1) {
		for(int i = 0; i < ful; i++) if(i & w) {
			ec[i] += ec[i ^ w];
		}
	}

	g[0] = 1;
	for(int i = 1; i < ful; i++) {
		g[i] = ec[i] == 0;
		int mi = lowbit(i);
		for(int j = (i - 1) & i; j; j = (j - 1) & i) if(j & mi) {
			g[i] -= (ec[i ^ j] == 0) * g[j];
		}
	}

	mi[1] = 1;
	for(int i = 2; i <= n; i++) mi[i] = mi[i - 1] * 3;
}

ll ori_val, ori;
int st[N];

void upd(int x,int news, int minl, int sz, int as, int fir) {
	if (x > n) {
		if (!sz) return;
		if (sz & 1) news += mi[minl];
		// cerr << "updates " << news << " " << ori << " c = " << g[as] % mo * ((sz & 1) ? 1 : lim[minl] + 1) % mo << endl;
		f[news] = (f[news] 
			+ ori_val * g[as] % mo * (((sz & 1) ? 1 : lim[minl] + 1) % mo)) % mo;
		return;
	}
	if (fir == 1 && st[x] == 0) {
		upd(x + 1, news + mi[x], 
			lim[x] < lim[minl] ? x : minl, sz + 1, as + w[x], 0);
	} else {
		upd(x + 1, news, minl, sz, as, fir && st[x] != 0);
		if (st[x] == 0) {
			upd(x + 1, news + mi[x], 
				lim[x] < lim[minl] ? x : minl, sz + 1, as + w[x], fir && st[x] != 0);
		}
	}
}

ll p[1 << N];
bool ed[1 << N];

ll getans() {
	static int con[N]; con[0] = 0;
	static ll f[N][2][2];
	int r = 0;
	for(int i = 1; i <= n; i++) if (st[i] == 2) {
		con[++con[0]] = i; r |= w[i];
	}
	if (ed[r]) return p[r];
	if (con[0] == 0) return C == 0;
	ll ret = 0;
	int flag = 1;
	for(int i = 62; ~i; i--) {
		ll w = 1ll << i;
		memset(f, 0, sizeof f);
		f[0][0][0] = 1;
		int ad = 0;
		for(int j = 1; j <= con[0]; j++) {
			int x = con[j];
			ad ^= lim[x] >> i & 1;
			for(int pre = 0; pre < 2; pre++) {
				for(int has = 0; has < 2; has++) 
					if(f[j - 1][pre][has]) {
						int ts = (lim[x] >> i & 1);
						for(int e = 0; e <= ts; e++) {
							ll v = f[j - 1][pre][has];
							int _has = has || e < ts;
							if (!(has ^ _has)) {
								if (e == ts) {
									v =  ((lim[x] & (w - 1)) + 1) % mo * v % mo;
								} else {
									v = w % mo * v % mo;
								}
							}
							f[j][pre ^ e][_has] = (f[j][pre ^ e][_has] + v) % mo;
						}
					}
			}
		}
		int req = (C >> i & 1);
		ret = (ret + f[con[0]][req][1]) % mo;
		if (ad != req) {
			flag = 0; break;
		}
	}
	ret += flag;
	return ed[r] = 1, p[r] = ret;
}

int main() {
	freopen("draw.in","r",stdin);
	freopen("draw.out","w",stdout);
	cin >> n >> m >> C;
	ful = 1 << n;
	for(int i = 1; i <= n; i++) {
		scanf("%lld", &lim[i]), w[i] = 1 << i - 1;
	}
	for(int i = 1; i <= m; i++) {
		int u, v; scanf("%d %d", &u, &v);
		edge[u] |= w[v], edge[v] |= w[u];
		ec[w[u] | w[v]] = 1;
	}
	init_g();
	f[0] = 1;
	int ful3 = mi[n] * 3;
	lim[0] = 1e18 + 1;
	ll ans = 0;
	for(int i = 0; i < ful3; i++) if (f[i]) {
		int z = i, full = 1;
		for(int j = 1; j <= n; j++, z /= 3) {
			st[j] = z % 3;
			full &= st[j] > 0;
		}
		if (full) {
			ans = (ans + getans() * f[i]) % mo;
		} else {
			ori = i;
			ori_val = f[i];
			upd(1, i, 0, 0, 0, 1);
		}
	}
	cout << (ans + mo) % mo << endl;
}
发布了266 篇原创文章 · 获赞 93 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/jokerwyt/article/details/103021909