题意
一张无向图,每个点可以染为 中的一个整数。要求:
- 所有点颜色异或和为给定的C
- 是一个合法的染色(有边相连的两个点不能染相同的颜色)
求满足要求的方案数。
思路
- 考虑没有2限制怎么做,最裸的是一个 的状压做法。
- 然而这个做法太low了。不如利用一下异或的唯一可逆性。
- 从高到低枚举第一个存在脱离lim限制的数的位。对于高位是确定的,低位只需要拿出第一个脱离限制的数将异或和调整到C,其他数的低位就可以随意选。再保证当前位异或和=C即可。
- 上述做法使用一个递推计点数,是
的。
(pty杂题刚讲过为啥我就是不会呢???) - 接下来考虑边的限制怎么搞。注意到可以直接对边容斥:
- 枚举有多少边不满足并带上-1的系数,那么这些边连成了一些连通块,每个连通块内是一致的。
- 偶数大小的连通块对异或和没有贡献,直接乘上取值集合大小即可。
- 奇数大小的连通块全部提出来,做没有限制的部分。
- 接下来用一个状压递推来模拟上述过程。设状态 为当前用的点集合为s,奇数连通块中lim最小的点集合是t。
- 每次加入一个连通块,并乘上连通系数g。指的是选择块内的边使得其成为连通块的方案权值之和。这个可以通过减去不合法的预处理得到。
- 最后再将每个满状态的f乘上对应的part1答案即可。
时间复杂度分析:
记s中的点为状态1,t中的点为状态2,否则为状态0。注意到若按照特定的转移顺序,状态2前不会有状态0。因此考虑最后一个2,前面是1/2,后面是0/1。有
种合法状态。然后再枚举后面0状态的一个子集进行转移。列式分析后发现是
的。
#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;
}