这场出得真好
C[LGV 数学 FFT]
zfnb
首先可以LGV 引理得到一个行列式,每项都是
a
i
,
j
=
C
j
+
a
i
−
1
j
=
(
j
+
a
i
−
1
)
!
j
!
(
a
i
−
1
)
!
=
(
j
+
a
i
)
…
(
a
i
)
a_{i,j}=C^{j}_{j+a_i-1}=\cfrac{(j+a_i-1)!}{j! (a_i-1)!} = (j+a_i)\dots(a_i)
ai,j=Cj+ai−1j=j!(ai−1)!(j+ai−1)!=(j+ai)…(ai)
先把
j
!
j!
j! 提出去,然后每一列都减去前面的列若干次(这里可以模拟一下整理
a
i
∗
(
a
i
+
1
)
∗
(
a
i
+
2
)
a_i*(a_i+1)*(a_i+2)
ai∗(ai+1)∗(ai+2) 的过程),然后可以把通项变成这个样子
1
j
!
(
a
i
+
1
)
j
\frac{1}{j!}(a_i+1)^j
j!1(ai+1)j
每一列再提出一个 a j + 1 a_j+1 aj+1 这是一个范德蒙德行列式,由公式可以知道 $A’ = \frac{1}{j!}\prod_{j=1}^{n}a_j+1\prod_{1\leq i \leq j \leq n}a_j-a_i $
再把前面的阶乘加上,答案就是 ∏ j = 1 n 1 j ! ∏ j = 1 n a j + 1 ∏ 1 ≤ i ≤ j ≤ n a j − a i \prod_{j=1}^{n}\frac{1}{j!}\prod_{j=1}^{n}a_j+1\prod_{1\leq i \leq j \leq n}a_j-a_i ∏j=1nj!1∏j=1naj+1∏1≤i≤j≤naj−ai
后面这一项可以愉快地卷积,这里复制一下FFT的笔记
利用卷积求
∏
1
≤
i
≤
j
≤
n
a
j
−
a
i
\prod_{1\leq i \leq j \leq n}a_j-a_i
1≤i≤j≤n∏aj−ai
考虑算差为
d
d
d 的数对个数,最后相乘。用
f
i
f_i
fi 记录
i
i
i 是否出现,然后用得到的多项式和反转后的多项式相乘,本题就是
{
f
1
,
f
2
,
…
,
f
1000000
}
\{ f_1,f_2,\dots,f_{1000000}\}
{f1,f2,…,f1000000}和
{
f
1000000
−
1
,
f
1000000
−
2
,
…
,
f
1000000
−
1000000
}
\{ f_{1000000-1},f_{1000000-2},\dots,f_{1000000-1000000}\}
{f1000000−1,f1000000−2,…,f1000000−1000000},卷积过后
f
u
b
−
i
f_{ub-i}
fub−i 就是差值为
i
i
i 的有序对出现的次数,快速幂一乘即可
q
p
o
w
(
i
,
s
u
m
[
u
b
−
i
]
)
qpow(i,sum[ub-i])
qpow(i,sum[ub−i])
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const double PI = acos(-1.0);
struct Complex {
double x, y;
Complex(double _x = 0.0, double _y = 0.0) {
x = _x;
y = _y;
}
Complex operator-(const Complex &b) const {
return Complex(x - b.x, y - b.y);
}
Complex operator+(const Complex &b) const {
return Complex(x + b.x, y + b.y);
}
Complex operator*(const Complex &b) const {
return Complex(x * b.x - y * b.y, x * b.y + y * b.x);
}
};
/*
* 进行 FFT 和 IFFT 前的反置变换
* 位置 i 和 i 的二进制反转后的位置互换
*len 必须为 2 的幂
*/
void change(Complex y[], int len) {
int i, j, k;
for (int i = 1, j = len / 2; i < len - 1; i++) {
if (i < j) swap(y[i], y[j]);
// 交换互为小标反转的元素,i<j 保证交换一次
// i 做正常的 + 1,j 做反转类型的 + 1,始终保持 i 和 j 是反转的
k = len / 2;
while (j >= k) {
j = j - k;
k = k / 2;
}
if (j < k) j += k;
}
}
/*
* 做 FFT
*len 必须是 2^k 形式
*on == 1 时是 DFT,on == -1 时是 IDFT
*/
void FFT(Complex y[], int len, int on) {
change(y, len);
for (int h = 2; h <= len; h <<= 1) {
Complex wn(cos(2 * PI / h), sin(on * 2 * PI / h));
for (int j = 0; j < len; j += h) {
Complex w(1, 0);
for (int k = j; k < j + h / 2; k++) {
Complex u = y[k];
Complex t = w * y[k + h / 2];
y[k] = u + t;
y[k + h / 2] = u - t;
w = w * wn;
}
}
}
if (on == -1) {
for (int i = 0; i < len; i++) {
y[i].x /= len;
}
}
}
typedef long long ll;
typedef unsigned long long ull;
const int mod = 998244353;
const int N = 4e6;
Complex x1[N], x2[N];
int a1[N], a2[N];
int sum[N];
ull qp(ull x, ull n) {
ull res = 1;
while (n > 0) {
if (n & 1) {
res = (res * x) % mod;
}
x = (x * x) % mod;
n >>= 1;
}
return res;
}
int main() {
int n, ub = 1000000;
ll frac = 1, prod = 1, proda = 1;
cin >> n;
for (int i = 0, tmp; i < n; i++) {
cin >> tmp;
frac = frac * (i + 1) % mod;
prod = prod * qp(frac, mod - 2) % mod;
proda = 1ll * (tmp + 1) * proda % mod;
a1[tmp]++;
a2[ub - tmp]++;
}
int len = 1;
ub++;
while (len < ub * 2 || len < ub * 2) len <<= 1;
for (int i = 0; i < ub; i++) x1[i] = Complex(a1[i], 0);
for (int i = ub; i < len; i++) x1[i] = Complex(0, 0);
for (int i = 0; i < ub; i++) x2[i] = Complex(a2[i], 0);
for (int i = ub; i < len; i++) x2[i] = Complex(0, 0);
FFT(x1, len, 1);
FFT(x2, len, 1);
for (int i = 0; i < len; i++) x1[i] = x1[i] * x2[i];
FFT(x1, len, -1);
for (int i = 0; i < len; i++) sum[i] = int(x1[i].x + 0.5);
//, cout << i << ": " << sum[i] << endl;
ll ans = 1;
ub--;
for (int i = 1; i <= ub; i++) {
if (sum[ub - i]) {
// cout << i << ": " << sum[ub - i] << endl;
ans = (ans * qp(i, sum[ub - i])) % mod;
}
}
// cout << ans << " " << prod << " " << proda << endl;
ans = ans * prod % mod * proda % mod;
cout << ans;
return 0;
}
J[二分图/带花树]
注意到除了右转,场上只能同时开两个灯。先假设为所有的车都单独亮灯,再考虑如何分配可以节约时间。将可以同时通过的车连边,如果匹配上,则说明两辆车可以同时走,因此一对匹配表示节约1秒。看起来这是一个非常暴力的做法,但是由于总点数不超过 4 ∗ 4 ∗ 100 4*4*100 4∗4∗100,且是稀疏图,因此不妨大胆尝试匈牙利。
这题的做法和前几天那场二分图类似,但是建模稍微不明显一点点。
#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int N = 1600+5;
int a[5][5];
vector<int> cnt[5][5];
vector<int> mp[N];
int ntot, vis[N], link[N];
void add(int u,int v){
// cout<<u<<" "<<v<<endl;
mp[u].push_back(v);
mp[v].push_back(u);
}
int dfs(int x){
for(auto v:mp[x]){
if(vis[v]) continue;
vis[v] = 1;
if(link[v] == 0 || dfs(link[v])){
link[v] = x;
return 1;
}
}
return 0;
}
int main(){
ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
int cse;
cin >> cse;
while(cse--){
int ans = 0, res = 0; ntot = 0;
for(int i = 0; i < 4; i++){
for(int j = 0; j < 4; j++){
cin >> a[i][j];
if((j+1)%4 == i){
res = max(res, a[i][j]);
}else{
int tmp = a[i][j];
ans += tmp;
cnt[i][j].clear();
while(tmp--){
cnt[i][j].push_back(++ntot);
// cout<<i<<" "<<j<<": "<<ntot<<endl;
}
}
}
}
for(auto u:cnt[0][2]) for(auto v:cnt[2][0]) add(u,v);
for(auto u:cnt[1][3]) for(auto v:cnt[3][1]) add(u,v);
for(auto u:cnt[0][1]) for(auto v:cnt[2][3]) add(u,v);
for(auto u:cnt[1][2]) for(auto v:cnt[3][0]) add(u,v);
for(int i = 0; i < 4; i++){
for(auto u:cnt[i][(i+1)%4]) for(auto v:cnt[i][(i+2)%4]) add(u,v);
for(auto u:cnt[i][(i+2)%4]) for(auto v:cnt[(i+1)%4][(i+2)%4]) add(u,v);
}
int sum = 0;
for(int i = 1; i <= ntot; i++){
memset(vis, 0, sizeof vis);
int tmp = dfs(i);
sum += tmp;
}
ans = max(ans - sum/2,res);
cout<<ans<<"\n";
for(int i = 1; i <= ntot; i++) mp[i].clear(), link[i] = 0;
}
}