20220722
作者:互联网
染色
要求环上没有重复点,可以先求个点双。对每个点双分类讨论:
- 边数 \(<\) 点数:该点双中只有两个点一条边,该边不在任何环中,可以随意染色
- 边数 \(=\) 点数:该点双是一个环。根据 Burnside 引理,方案数为 \(\frac{1}{n}\sum_{i=0}^{n-1}k^{\gcd(i,n)}\)
- 边数 \(>\) 点数:该点双由若干环套起来,只要每种颜色的个数相同就本质相同,直接插板法算方案数
证明:肯定存在一个三度点(一定有两个环在该点相交),手玩一下可以发现可以在不影响其他边的情况下交换该点的两邻边,进而可以任意交换两边(先把这两边移到三度点旁边,交换后再移回去)
实现上注意有重边,应该令割点属于 dfs 树上最浅的点双,边属于 dfs 树上较深的点双
code
const int N = 5e5+5;
int n,m,mm=1,ind,dcc,head[N],nxt[N*4],to[N*4],dfn[N],low[N],in[N],siz1[N],siz2[N];
mint k,ans(1);
auto C=[](mint n,int m) {
mint res(1);
For(i,1,m) res *= (n-i+1) / i;
return res;
};
void tj(int u) {
static int stk[N],tp;
dfn[u] = low[u] = ++ind, stk[++tp] = u;
for(int i = head[u], v; v = to[i], i; i = nxt[i])
if( dfn[v] ) ckmin(low[u],dfn[v]);
else {
tj(v), ckmin(low[u],low[v]);
if( dfn[u] <= low[v] ) {
++dcc, ++siz1[dcc];
do in[stk[tp--]] = dcc, ++siz1[dcc]; while( stk[tp+1] != v );
}
}
}
signed main() { freopen("painting.in","r",stdin); freopen("painting.out","w",stdout);
io>>n>>m>>k.x;
For(i,1,m, x,y)
io>>x>>y,
nxt[++mm] = head[x], to[mm] = y, head[x] = mm,
nxt[++mm] = head[y], to[mm] = x, head[y] = mm;
For(i,1,n) if( !dfn[i] ) tj(i), in[i] = inf;
for(int i = 2; i < mm; i += 2) ++siz2[min(in[to[i]],in[to[i^1]])];
For(i,1,dcc)
if( siz2[i] == 1 ) ans *= k;
else if( siz1[i] == siz2[i] ) {
mint res;
Rep(j,0,siz2[i]) res += Pow(k,__gcd(j,siz2[i]));
ans *= res / siz2[i];
} else ans *= C(k+siz2[i]-1,siz2[i]);
io<<ans.x;
return 0;
}
序列计数问题
经典题,以前见过但没做。。。
先把 \(n,c\) 都 \(-1\)
考虑容斥,枚举超过限制的 \(x\),答案即为
\[\sum_{s\subseteq U}(-1)^{|s|}{n+m-c|s|-\sum_{i\in s}b^{i}\choose m} \]枚举 \(|s|\),那么 \(a=n+m-c|s|\) 是定值。在 \(x=\sum_{i\in s}b^{i}\le a\) 的情况下 \({a-x\choose m}\) 是关于 \(x\) 的 \(m\) 次多项式,\(O(m^{2})\) 暴力计算出系数后代入 \(\sum x\) 即可求出 \(\sum{a-x\choose m}\)
剩下的问题是计算 \(x\) 的次幂。可以枚举 \(x\) 与 \(a\) 在 \(b\) 进制下 LCP 的下一位(注意到 \(x\) 在 \(b\) 进制下每位为 \(0/1\)),当前位 \(x<a\),那么低位可以随便填。预处理 \(g[i,j,k]\) 表示后 \(i\) 位,有 \(j\) 位为 \(1\),和的 \(k\) 次幂。使用二项式定理转移和合并高低位即可
实现上可以记录 \(c\) 的正负,并在高精减前判断大小,这样就不用写高精负数了。细节比较多
时间复杂度 \(O(m^{3}\log n)\)
计算机
sub 1
读题分,手玩即可
out
NOT 2 2
RSH 2 61
LSH 2 3
NOT 3 3
RSH 3 62
LSH 3 9
NOT 4 4
RSH 4 63
LSH 4 1
SET 1 2
OR 3 3 4
OR 1 1 3
sub 2
用 \(a_{i},b_{i}\) 表示两个数二进制第 \(i\) 位的值,\(c_i\) 表示第 \(i\) 位是否进位,那么 \(a+b=a\oplus b\oplus2c\)
注意到并行对分治结构是利好的,比如线段树建树复杂度变为 \(\log 64\)。如果能把 \(c\) 表示成具有结合律的信息,就可以类似 DDP 一样挂到线段树上。
考虑 \(c\) 的递推过程:
- \(a_{i}=b_{i}=1\):\(c_{i}=1\)
- \(a_{i}=b_{i}=0\):\(c_{i}=0\)
- \(a_{i}=1\vee b_{i}=1\):\(c_{i}=c_{i-1}\)
考虑对每个区间维护 \(t0[i],t1[i]\) 表示 \(c_{l-1}=0/1\) 时 \(c_{i}\) 的值,合并时使用 \(t0[mid],t1[mid]\) 更新 \([mid+1,r]\) 的值即可
code
int n=4,a[64],b[64],t0[64],t1[64];
uLL ans,x[401];
void SET(int i,int j) { x[i] = x[j]; printf("SET %d %d\n",i,j); }
void XOR(int i,int j,int k) { x[i] = x[j]^x[k]; printf("XOR %d %d %d\n",i,j,k); }
void AND(int i,int j,int k) { x[i] = x[j]&x[k]; printf("AND %d %d %d\n",i,j,k); }
void OR(int i,int j,int k) { x[i] = x[j]|x[k]; printf("OR %d %d %d\n",i,j,k); }
void LSH(int i,int j) { x[i] <<= j; printf("LSH %d %d\n",i,j); }
void RSH(int i,int j) { x[i] >>= j; printf("RSH %d %d\n",i,j); }
void NOT(int i,int j) { x[i] = ~x[j]; printf("NOT %d %d\n",i,j); }
#define mid (l+r>>1)
void bld(int l,int r) {
if( l == r ) return;
bld(l,mid), bld(mid+1,r);
For(i,mid+1,r)
AND(3,t1[mid],t1[i]), AND(4,t0[mid],t1[i]),
OR(t1[i],t0[i],3), OR(t0[i],t0[i],4);
}
void qry(int l,int r) {
if( l == r ) {
if( l ) SET(b[l],t0[l-1]), LSH(b[l],l);
else SET(b[l],2);
return;
}
qry(l,mid), qry(mid+1,r), XOR(b[l],b[l],b[mid+1]);
}
signed main() { freopen("cal2.out","w",stdout);
scanf("%llu %llu",&x[1],&x[2]), ans = x[1]+x[2];
NOT(400,400), RSH(400,63);
Rep(i,0,64)
a[i] = ++n, SET(n,1), RSH(n,i), AND(n,n,400),
b[i] = ++n, SET(n,2), RSH(n,i), AND(n,n,400);
Rep(i,0,64) t0[i] = ++n, AND(t0[i],a[i],b[i]), t1[i] = a[i], OR(t1[i],a[i],b[i]);
bld(0,63), qry(0,63);
XOR(1,1,b[0]);
assert(n<=400), assert(ans==x[1]);
return 0;
}
sub 3
鸽
标签:int,mid,void,20220722,t0,t1,siz2 来源: https://www.cnblogs.com/401rk8/p/16536493.html