T1 矩阵
随机一个向量 \(V\) ,判断 \(V\times A\times B\) 是否等于 \(V\times C\) 即可,实质上我们在判断对于每个 \(i\in[1,n]\) \(\sum_{k=1}^nV_k\sum_{p=1}^{n}A_{k,p}B_{p,i}\) 是否等于 \(\sum_{k=1}^{n}V_kC_{k,i}\) 。
code
#include <cstdio>
#include <vector>
#include <utility>
#include <random>
#include <ctime>
using namespace std;
const int max1 = 3000;
const int mod = 998244353;
int T, n, A[max1 + 5][max1 + 5], B[max1 + 5][max1 + 5], C[max1 + 5][max1 + 5];
int L[max1 + 5], R[max1 + 5], tmp[max1 + 5];
vector < pair <int, int> > posA, posB;
void Work ()
{
scanf("%d", &n);
for ( int i = 1; i <= n; i ++ )
for ( int j = 1; j <= n; j ++ )
scanf("%d", &A[i][j]);
for ( int i = 1; i <= n; i ++ )
for ( int j = 1; j <= n; j ++ )
scanf("%d", &B[i][j]);
for ( int i = 1; i <= n; i ++ )
for ( int j = 1; j <= n; j ++ )
scanf("%d", &C[i][j]);
for ( int i = 1; i <= n; i ++ )
L[i] = R[i] = rand() % mod;
for ( int i = 1; i <= n; i ++ )
{
tmp[i] = 0;
for ( int j = 1; j <= n; j ++ )
tmp[i] = ( tmp[i] + 1LL * L[j] * A[j][i] ) % mod;
}
for ( int i = 1; i <= n; i ++ )
L[i] = tmp[i];
for ( int i = 1; i <= n; i ++ )
{
tmp[i] = 0;
for ( int j = 1; j <= n; j ++ )
tmp[i] = ( tmp[i] + 1LL * L[j] * B[j][i] ) % mod;
}
for ( int i = 1; i <= n; i ++ )
L[i] = tmp[i];
for ( int i = 1; i <= n; i ++ )
{
tmp[i] = 0;
for ( int j = 1; j <= n; j ++ )
tmp[i] = ( tmp[i] + 1LL * R[j] * C[j][i] ) % mod;
}
for ( int i = 1; i <= n; i ++ )
R[i] = tmp[i];
bool flag = true;
for ( int i = 1; i <= n; i ++ )
if ( L[i] != R[i] )
flag = false;
if ( flag )
printf("Yes\n");
else
printf("No\n");
return;
}
int main ()
{
freopen("matrix.in", "r", stdin);
freopen("matrix.out", "w", stdout);
srand(time(NULL));
scanf("%d", &T);
while ( T -- )
Work();
return 0;
}
T2 错排
容易发现前 \(m\) 个数一定错排,后 \(n-m\) 个数存在 \(m\) 个数没有限制,因此设总共 \(n\) 个数,前 \(m\) 个数没有限制,剩余数严格错排的方案数为 \(f_{n,m}\) ,那么答案为 \(\tfrac{(n-m)!}{(n-2m)!}f_{n-m,m}\) 。
当 \(m=0\) 时,有经典错排递推式, \(f_{n,0}=(n-1)(f_{n-1,0}+f_{n-2,0})\) ,考虑 \(m\ne 0\) 的情况,简单考虑 \(p_m\) 的情况,如果 \(p_m=m\) ,贡献为 \(f_{n-1,m-1}\) ,如果 \(p_m\ne m\) ,这相当于第 \(m\) 个数也存在限制,贡献为 \(f_{n,m-1}\) ,因此有递推式:
\[f_{n,m}=f_{n-1,m-1}+f_{n,m-1} \]发现递推形似组合数,容易想到一种快速求解答案的思路:设阈值 \(B\) ,预处理所有满足 \(m=kB\) 的每个 \(f_{n,m}\) ,假设当前询问为 \(f_{n,m}\) ,显然答案为 \(\sum\binom{m\operatorname{mod}B}{n-t}f_{t,\lfloor\tfrac{m}{B}\rfloor}\) ,容易发现计算的复杂度为 \(O(B)\) 。
考虑如何进行预处理,题解给出的做法是生成函数,设 \(F_m(x)=\sum_{i=0}^{\infty}f_{i,m}x^i\) ,显然 \(F_m(x)=(x+1)F_{m-1}(x)\) ,那么 \(F_m(x)=(x+1)^mF_0(x)\) ,用多项式乘法预处理可以做到 \(O(n\sqrt{n\log n})\) ,但常数极大;另一种做法是直接递推,通过打表可以发现 \(f_{n,m}=(n-1)f_{n-1,m}+(n-1-m)f_{n-2,m}\) ,简单理解就是第 \(n\) 个数可以直接在 \(n-1\) 个数形成的错排的基础上交换,也可以交换 \((n-1-m)\) 个有限制的位置满足这些位置 \(p_i=i\) ,可以做到 \(O(n\sqrt{n})\) 。
code
#include <cstdio>
#include <algorithm>
using namespace std;
const int lim = 2e5, B = 447;
const int mod = 998244353;
int inv[lim + 5], fac[lim + 5], ifac[lim + 5], f[B + 5][lim + 5];
int C ( int n, int m )
{
if ( n < m || n < 0 || m < 0 )
return 0;
return 1LL * fac[n] * ifac[n - m] % mod * ifac[m] % mod;
}
int Solve ( int n, int m )
{
int k = m / B, t = m % B, ans = 0;
for ( int i = max(0, n - t); i <= n; i ++ )
ans = ( ans + 1LL * C(t, n - i) * f[k][i] ) % mod;
return ans;
}
int main ()
{
freopen("qwq.in", "r", stdin);
freopen("qwq.out", "w", stdout);
inv[1] = 1;
for ( int i = 2; i <= lim; i ++ )
inv[i] = 1LL * ( mod - mod / i ) * inv[mod % i] % mod;
fac[0] = 1, ifac[0] = inv[1];
for ( int i = 1; i <= lim; i ++ )
fac[i] = 1LL * fac[i - 1] * i % mod, ifac[i] = 1LL * ifac[i - 1] * inv[i] % mod;
for ( int i = 0; i <= B; i ++ )
{
int k = i * B;
f[i][k] = fac[k], f[i][k + 1] = 1LL * fac[k] * k % mod;
for ( int j = k + 2; j <= lim; j ++ )
f[i][j] = ( 1LL * ( j - 1 ) * f[i][j - 1] + 1LL * ( j - 1 - k ) * f[i][j - 2] ) % mod;
}
int T, n, m, ans;
scanf("%d", &T);
while ( T -- )
{
scanf("%d%d", &n, &m);
if ( n >= m + m )
ans = 1LL * fac[n - m] * ifac[n - m - m] % mod * Solve(n - m, m) % mod;
printf("%d\n", ans);
}
return 0;
}
T3 异或图
首先考虑快速求解 \(m=0\) 的答案,我们从高位向低位依次考虑,如果当前位存在位置 \(i\) 满足 \(a_i\) 这一位为 \(1\) ,并且 \(b_i\) 这一位为 \(0\) ,那么 \(b_i\) 的低位可以任意填写,此时只需要计算当前位异或值等于目标值并且其他数低位任意的方案数,根据这个性质,我们设 \(f_{i,0/1,0/1}\) 表示当前考虑了前 \(i\) 个数,此时这一位的异或值为 \(0/1\) ,是否存在一个位置 \(p\) ,满足 \(a_p\) 这一位为 \(1\) 并且 \(b_p\) 这一位为 \(0\) ,转移时考虑第 \(i\) 个数填 \(0/1\) 即可。(转移比较麻烦但是很简单,在此不再赘述)
考虑解决原问题,比较显然的思路是枚举边集进行容斥,钦定 \(s\) 集合内的边满足 \(b_u=b_v\) ,容斥系数为 \((-1)^{|s|}\) ,取连通块内最小的 \(a_i\) 作为限制 \(lim\) ,那么对于偶数大小的连通块,可以直接产生 \(lim\) 的贡献,对于奇数大小的连通块,它相当于一个 \(a_i=lim\) 的点,可以用 \(m=0\) 的算法求解,我们得到了一个 \(O(2^{|边集|}n\log w)\) 的暴力。
考虑枚举连通块而不是枚举边集,首先需要求解一个连通块的容斥系数,设 \(g_S\) 表示当 \(S\) 集合内所有点连通时,可行的边集的容斥系数之和,比较经典的套路是转化为边任选减去不连通的系数和,设 \(S\) 内部的点数为 \(siz\) ,边数为 \(cnt\) ,显然有初始化 \(g_S=[cnt=0\operatorname{and}siz=1]\) ,随意选定集合内一个点,枚举这个点所在的连通块进行容斥即可。
设 \(h_{s,t}\) 表示当前考虑了 \(s\) 集合内的点,奇数大小连通块内最小的点形成的集合为 \(t\) 的容斥系数和,转移时枚举下一个连通块即可(为了避免重复,枚举未选点组成集合的 \(lowbit\) 所在的连通块),发现 \(t\subseteq s\) ,因此可以用三进制壮压减少状态,复杂度为 \(O(4^n)\) ,但常数很小。
code
#include <cstdio>
using namespace std;
const int max1 = 15, max2 = 1 << 15, max3 = 14348907, max_bit = 60;
const int mod = 998244353;
int n, m, inv[max1 + 5];
bool edge[max1 + 5][max1 + 5];
long long a[max1 + 5], b[max1 + 5], C;
long long bit[max1 + 5][max_bit + 5];
int siz[max2 + 5], low[max2 + 5], Min[max2 + 5], f[max2 + 5], g[max2 + 5], power[max2 + 5];
int h[max3 + 5];
int Map[max2 + 5];
long long Bit ( int i, int j )
{
if ( j == -1 )
return 1LL;
return bit[i][j] + 1LL;
}
int Solve ( int s )
{
if ( ~Map[s] )
return Map[s];
int ans = 0;
for ( int i = max_bit; i >= 0; i -- )
{
int f[2][2], tmp[2][2], now;
f[0][0] = 1, f[0][1] = f[1][0] = f[1][1] = 0;
now = 0;
for ( int j = 1; j <= n; j ++ )
{
if ( !( s >> j - 1 & 1 ) )
continue;
tmp[0][0] = f[0][0], tmp[0][1] = f[0][1], tmp[1][0] = f[1][0], tmp[1][1] = f[1][1];
if ( a[j] >> i & 1 )
{
f[0][0] = Bit(j, i - 1) % mod * tmp[1][0] % mod;
f[0][1] = ( ( 1LL << i ) % mod * tmp[0][1] + Bit(j, i - 1) % mod * tmp[1][1] + tmp[0][0] ) % mod;
f[1][0] = Bit(j, i - 1) % mod * tmp[0][0] % mod;
f[1][1] = ( ( 1LL << i ) % mod * tmp[1][1] + Bit(j, i - 1) % mod * tmp[0][1] + tmp[1][0] ) % mod;
}
else
{
f[0][0] = Bit(j, i - 1) % mod * tmp[0][0] % mod;
f[0][1] = Bit(j, i - 1) % mod * tmp[0][1] % mod;
f[1][0] = Bit(j, i - 1) % mod * tmp[1][0] % mod;
f[1][1] = Bit(j, i - 1) % mod * tmp[1][1] % mod;
}
now ^= a[j] >> i & 1;
}
if ( C >> i & 1 )
ans = ( ans + f[1][1] ) % mod;
else
ans = ( ans + f[0][1] ) % mod;
if ( now != ( C >> i & 1 ) )
break;
}
long long sum = 0;
for ( int i = 1; i <= n; i ++ )
{
if ( !( s >> i - 1 & 1 ) )
continue;
sum ^= a[i];
}
ans = ( ans + ( sum == C ) ) % mod;
Map[s] = ans;
return ans;
}
int main ()
{
freopen("graph.in", "r", stdin);
freopen("graph.out", "w", stdout);
scanf("%d%d%lld", &n, &m, &C);
for ( int i = 1; i <= n; i ++ )
scanf("%lld", &a[i]);
for ( int i = 1, u, v; i <= m; i ++ )
scanf("%d%d", &u, &v), edge[u][v] = edge[v][u] = true;
inv[0] = inv[1] = 1;
for ( int i = 2; i <= n; i ++ )
inv[i] = 1LL * ( mod - mod / i ) * inv[mod % i] % mod;
for ( int i = 1; i <= n; i ++ )
inv[i] = 1LL * inv[i - 1] * inv[i] % mod;
for ( int i = 0; i <= n; i ++ )
{
bit[i][0] = a[i] & 1;
for ( int j = 1; j <= max_bit; j ++ )
bit[i][j] = bit[i][j - 1] | ( a[i] & 1LL << j );
}
siz[0] = 0, low[0] = Min[0] = -1, f[0] = 0, g[0] = 1;
for ( int s = 1; s < 1 << n; s ++ )
{
siz[s] = siz[s >> 1] + ( s & 1 );
low[s] = Min[s] = ( s & 1 ) ? 1 : low[s >> 1] + 1;
if ( s ^ ( 1 << low[s] - 1 ) && a[Min[s ^ ( 1 << low[s] - 1 )]] < a[low[s]] )
Min[s] = Min[s ^ ( 1 << low[s] - 1 )];
f[s] = f[s ^ ( 1 << low[s] - 1 )];
for ( int i = 1; i <= n; i ++ )
if ( ( s >> i - 1 & 1 ) && edge[i][low[s]] )
++f[s];
if ( !f[s] )
g[s] = siz[s] == 1;
else
{
g[s] = 0;
int tmp = s ^ ( 1 << low[s] - 1 );
for ( int t = tmp & ( tmp - 1 ); ; t = ( t - 1 ) & tmp )
{
t ^= 1 << low[s] - 1;
if ( !f[s ^ t] )
g[s] = ( g[s] - g[t] + mod ) % mod;
t ^= 1 << low[s] - 1;
if ( !t )
break;
}
}
}
int lim = 1, ans = 0;
for ( int i = 1; i <= n; i ++ )
lim = lim * 3;
power[0] = 0;
for ( int s = 1; s < 1 << n; s ++ )
power[s] = power[s >> 1] * 3 + ( s & 1 );
for ( int s = 0; s < 1 << n; s ++ )
Map[s] = -1;
h[0] = 1;
for ( int s = 0; s < lim; s ++ )
{
if ( !h[s] )
continue;
int tmp = s, k = 0;
for ( int i = 1; i <= n; i ++, tmp /= 3 )
if ( !( tmp % 3 ) )
k ^= 1 << i - 1;
if ( k )
{
tmp = k ^ ( 1 << low[k] - 1 );
for ( int t = tmp; ; t = ( t - 1 ) & tmp )
{
t ^= 1 << low[k] - 1;
int new_s = s + power[t] + ( siz[t] & 1 ? power[1 << Min[t] - 1] : 0 ), up = ( ( siz[t] & 1 ) ? 1LL : a[Min[t]] + 1LL ) % mod * g[t] % mod;
h[new_s] = ( h[new_s] + 1LL * h[s] * up ) % mod;
t ^= 1 << low[k] - 1;
if ( !t )
break;
}
}
else
{
k = 0;
tmp = s;
for ( int i = 1; i <= n; i ++, tmp /= 3 )
if ( tmp % 3 == 2 )
k ^= 1 << i - 1;
ans = ( ans + 1LL * h[s] * Solve(k) ) % mod;
}
}
printf("%d\n", ans);
return 0;
}
标签:10,连通,省选,max1,int,联测,ans,include,mod
From: https://www.cnblogs.com/KafuuChinocpp/p/17338343.html