2021.7.31
上午把回忆录写完了,不太想看深度学习,就搞搞谔谔模拟赛。
2021.8.6
滚来参加 tx 的一个活动,zjk、cyx、cxr 都来了,邓老师被请过来装学员 /kx
下午坐高铁到了深圳,等 zz 等了 1h,就滚去酒店了。
晚上搞了一些谔谔的团建活动,但通信题还是比超脑好玩多了,虽然我啥都没干的团队任务又让我意识到了我是个 fw 的事实。
题目就是通过 10 根绳子传递长度不超过 10 的字符串,字符可能是小写字母、数字、数学符号、常见汉字。
邓老师造的编码方法是一个绳子表示一个字符,穿一次的结表示 0,穿两次的结表示 1,类型用 2bit,然后直接对汉字之外的字符编码,汉字直接写拼音。
zz 他们队的编码方法要劣一些,但他们打结技术高超所以 rk1 了,ntf 他们队也 rk2 了,我们即使有邓老师带还是垫底了。
2021.8.7
听了一天课,无聊死了。
晚上坐双层巴士乱逛,导游的声音有点 /tuu。
2021.8.12
总体来说体验很差,因为实在不想做任务。
终于熬到结束可以放假了。
2021.9.1
上午无聊就在数竞室看小蓝皮(
ARC122E Increasing LCMs
给定长为 (n) 的正整数列 (x_i),求重排方案使得前缀 LCM 递增。
(nle 100),(x_ile 10^{18})。
从后往前考虑,对于末尾的数 (x_i),要满足 (x_i mid ext{LCM}_{j e i}(x_j))。
为了方便判断考虑转化一下:( ext{LCM}_{j e i}(gcd(x_j,x_i))<x_i)。
同时对于可以放在末尾的数 (x_i),它放在前面不会更优,于是就放末尾了。
暴力判断,时间复杂度 (O(n^3log V))。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 103;
int n;
LL a[N], ans[N];
bool vis[N];
LL gcd(LL a, LL b){return b ? gcd(b, a % b) : a;}
LL lcm(LL a, LL b){return a / gcd(a, b) * b;}
int main(){
ios::sync_with_stdio(false);
cin >> n;
for(int i = 1;i <= n;++ i) cin >> a[i];
for(int p = n;p;-- p){
bool flg = false;
for(int i = 1;i <= n;++ i) if(!vis[i]){
LL tmp = 1;
for(int j = 1;j <= n;++ j)
if(!vis[j] && i != j) tmp = lcm(tmp, gcd(a[j], a[i]));
if(tmp < a[i]){ans[p] = a[i]; vis[i] = flg = true; break;}
}
if(!flg){puts("No"); return 0;}
}
puts("Yes");
for(int i = 1;i <= n;++ i) printf("%lld ", ans[i]);
}
ARC123E Training
给定正整数 (a_1,a_2,b_1,b_2,n),求
(T) 组数据。(Tle 2cdot 10^5),(nle 10^9),(a_1,a_2,b_1,b_2le 10^6)。
先转化为 (lfloor i/b_1 floor-lfloor i/b_2 floor=a_2-a_1),设为 (a)。
先特判掉 (b_1=b_2) 的情况,不妨设 (b_1<b_2)。
显然可以先圈一圈范围:(i/b_1-i/b_2in(-1,1])。
乱搞一下,分成 (i/b_1-i/b_2in(-1,0]) 和 (i/b_1-i/b_2in(0,1])。此时计算一下 (sumlfloor i/b_1 floor-sumlfloor i/b_2 floor) 就可以求出对应的答案。
时间复杂度 (O(T))。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int T;
LL n, a, b, c, d;
LL calc(LL a, LL b){LL c = a / b; return (c*(c-1ll)>>1)*b + (a-b*c)*c;}
LL calc(LL l, LL r, LL a, LL b){return a*(r-l) + calc(r,b) - calc(l,b);}
int main(){
ios::sync_with_stdio(false);
cin >> T;
while(T --){
cin >> n >> a >> b >> c >> d;
if(b == d){printf("%lld
", a == c ? n : 0); continue;}
if(b > d){swap(a, c); swap(b, d);} c -= a;
LL ans = 0, l = max(b*d*(c-1)/(d-b),0ll) + 1, r = min(b*d*c/(d-b),n) + 1;
if(l < r) ans = r - l + calc(l, r, 0, b) - calc(l, r, c, d);
l = max(b*d*c/(d-b),0ll) + 1; r = min(b*d*(c+1)/(d-b),n) + 1;
if(l < r) ans += r - l + calc(l, r, c, d) - calc(l, r, 0, b);
printf("%lld
", ans);
}
}
ARC124E Pass to Next
给定 (n) 个正整数,求
(nle 10^5),(a_ile 10^9)。
(min x_i=0) 的限制很容易去掉,只需要减去限制传球数 (>0) 的情况即可。
考虑组合意义,传完球后每个人要取出自己的一个球。
因为每个人的传球数互相独立,所以对每个人取出球的来源做 dp。设 (f_{i,0}) 表示第 (i) 个人取出自己的球的情况下,前 (i-1) 个人取球的方案数,(f_{i,1}) 表示第 (i) 个人取出第 (i-1) 个人给他的球的情况下,前 (i) 个人取球的方案数。
设 (S_k(n)=sum_{i=0}^ni^k),则可以列出转移式:
如果限制传球数 (>0) 就将第一个柿子的 (a_i) 减去 (1)。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 100003, mod = 998244353, inv3 = (mod+1)/3;
int n, a[N], f[N][2], ans;
void qmo(int &x){x += x >> 31 & mod;}
int calc(bool f1, bool f2){
memset(f, 0, n+1<<3); f[0][f1] = 1;
for(int i = 0;i < n;++ i){
int b = a[i]-f2, c = (b*(b+1ll)>>1)%mod;
f[i+1][0] = ((LL)c*f[i][0]+(b+1ll)*f[i][1])%mod;
b += f2; c = (b*(b+1ll)>>1)%mod;
int d = c*(b-1ll)%mod*inv3%mod;
f[i+1][1] = ((LL)c*f[i][1]+(LL)d*f[i][0])%mod;
} return f[n][f1];
}
int main(){
ios::sync_with_stdio(false);
cin >> n;
for(int i = 0;i < n;++ i) cin >> a[i];
qmo(ans += calc(0,0)+calc(1,0)-mod);
qmo(ans -= calc(0,1)); qmo(ans -= calc(1,1));
printf("%d
", ans);
}
ARC125E Snack
给定 (n) 种零食,第 (i) 种零食有 (a_i) 份。有 (m) 个小朋友分零食,第 (i) 个小朋友至多得到 (c_i) 份零食,至多得到 (b_i) 份同一种零食。求最多可以分多少份零食。
(n,mle2cdot 10^5),(a_i,c_ile 10^{12}),(b_ile 10^7)。
这显然是最大流模型,转化为最小割之后直接做。时间复杂度 (O(nlog n+m))。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200003;
template<typename T>
bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int n, m;
LL a[N], b[N], c[N], d[N], e[N], tmp1, tmp2, ans = 1e18;
int main(){
ios::sync_with_stdio(false);
cin >> n >> m;
for(int i = 1;i <= n;++ i) cin >> a[i];
for(int i = 1;i <= m;++ i) cin >> b[i];
for(int i = 1;i <= m;++ i){
cin >> c[i]; tmp2 += c[i];
int x = max(n - c[i] / b[i], 0ll);
d[x] += b[i]; e[x] += c[i];
}
sort(a + 1, a + n + 1);
for(int i = 0;i <= n;++ i){
tmp2 += a[i] - e[i]; tmp1 += d[i];
chmin(ans, tmp2 + tmp1 * (n-i));
}
printf("%lld", ans);
}
ARC125F Tree Degree Subset Sum
给定 (n) 个点的树,求有多少对二元组 ((x,y)),满足存在一种选出 (x) 个点的方案,使得度数之和为 (y)。
(nle 2cdot 10^5)。
实际上这棵树并没有什么用,我们只需要度数序列 (d_1,d_2,cdots,d_n),满足 (sum d_i=2n-2),(d_ige 1)。除了这个性质之外就没有别的了。
所以考虑随便打表猜结论,然后什么都看不出来。
乱搞一下,发现把 (d_i) 都减去 (1) 没什么区别,于是就继续打表。
发现若 (m(s),M(s)) 分别表示选出 (d_i) 之和为 (s) 的点数的最小值和最大值,则 ([m(s),M(s)]) 的点数都可以选出来。
如果这个结论是对的,那么就不用对点数记状态了,直接大力背包即可。时间复杂度 (O(nsqrt n))。
题解告诉我们这个结论确实是对的。考虑设 (z=sum[d_i=0]),则对于任意子集 (Ssubseteq [n]),(-zle ext{sum}(S)-|S|le z-2),所以 (M(s)-m(s)le 2z-2),而 (m(s)) 的方案不选 (0),如果选上可以得到 ([m(s),m(s)+z]) 的方案,(M(s)) 同理可得 ([M(s)-z,M(s)]) 的方案,得证。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 200003;
int n, z, d[N], f[N];
LL ans;
template<typename T>
bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
template<typename T>
bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
int main(){
ios::sync_with_stdio(false);
cin >> n;
memset(f, 0x3f, sizeof f); f[0] = 0;
for(int i = 0;i < n;++ i) d[i] = -1;
for(int i = n-1<<1, x;i;-- i){cin >> x; ++ d[x-1];}
sort(d, d + n);
for(int i = 0, j;i < n;i += j){
for(j = 0;d[i+j] == d[i];++ j);
int rem = j;
for(int k = 1;k <= rem;rem -= k, k <<= 1){
int val = d[i]*k;
for(int i = n-2;i >= val;-- i)
chmin(f[i], f[i-val]+k);
}
if(rem){
int val = d[i]*rem;
for(int i = n-2;i >= val;-- i)
chmin(f[i], f[i-val]+rem);
}
}
for(int i = 0;i < n-1;++ i) ans += max(0, n+1-f[n-2-i]-f[i]);
printf("%lld
", ans);
}
CF1556F Sports Betting
给定 (n) 个点的竞赛图,每个点 (i) 有点权 (a_i),(i o j) 的概率是 (frac{a_i}{a_i+a_j}),求能直接或间接到达所有点的点数期望值(mod(10^9+7))。
(nle 14),(a_ile 10^6)。
根据经典套路,竞赛图缩点之后是链,所以直接枚举入度为 (0) 的强连通分量 (S),计算 (|S|f_Sg_{S,Vackslash S}) 之和,其中 (f_S) 表示 (S) 是强连通图的概率,(g_{S,T}=prod_{iin S}prod_{jin T}frac{a_i}{a_i+a_j}) 表示所有 (S,T) 之间的边的方向都是 (S o T)。
先考虑计算 (f_S),直接容斥,如果不是强连通图,枚举入度为 (0) 的强连通分量 (T),则有
算 (g) 的时候先对所有 ((x,T)) 预处理 (prod_{yin T}a_x/(a_x+a_y)) 就可以做到 (O(n3^n))。
题解是 (O(3^n)) 做法。直接折半,分别将 (S,T) 拆成两部分得到四项,算 (g) 的复杂度就直接降到 ( ext{soft-}O(2^n))。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1<<14, mod = 1e9+7;
int n, n1, n2, l1, l2, lim, a[14], e[14][14], f[4][128][128], dp[N], ans;
void qmo(int &x){x += x >> 31 & mod;}
int ksm(int a, int b){
int res = 1;
for(;b;b >>= 1, a = (LL)a * a % mod)
if(b & 1) res = (LL)res * a % mod;
return res;
}
int calc(int S, int T){
return (LL)f[0][S&l1-1][T&l1-1]*f[1][S&l1-1][T>>n1]%mod*f[2][S>>n1][T&l1-1]%mod*f[3][S>>n1][T>>n1]%mod;
}
int main(){
ios::sync_with_stdio(false);
cin >> n; n1 = n>>1; n2 = n+1>>1; l1 = 1<<n1; l2 = 1<<n2; lim = 1<<n;
for(int i = 0;i < n;++ i){
cin >> a[i];
for(int j = 0;j < i;++ j){
e[i][j] = (LL)a[i]*ksm(a[i]+a[j],mod-2)%mod;
qmo(e[j][i] = mod+1-e[i][j]);
}
}
for(int S = 0;S < l1;++ S)
for(int T = 0;T < l1;++ T) if(!(S&T)){
f[0][S][T] = 1;
for(int i = 0;i < n1;++ i) if(S>>i&1)
for(int j = 0;j < n1;++ j) if(T>>j&1)
f[0][S][T] = (LL)f[0][S][T] * e[i][j] % mod;
}
for(int S = 0;S < l2;++ S)
for(int T = 0;T < l2;++ T) if(!(S&T)){
f[3][S][T] = 1;
for(int i = 0;i < n2;++ i) if(S>>i&1)
for(int j = 0;j < n2;++ j) if(T>>j&1)
f[3][S][T] = (LL)f[3][S][T] * e[i+n1][j+n1] % mod;
}
for(int S = 0;S < l1;++ S)
for(int T = 0;T < l2;++ T){
f[1][S][T] = 1;
for(int i = 0;i < n1;++ i) if(S>>i&1)
for(int j = 0;j < n2;++ j) if(T>>j&1)
f[1][S][T] = (LL)f[1][S][T] * e[i][j+n1] % mod;
}
for(int S = 0;S < l2;++ S)
for(int T = 0;T < l1;++ T){
f[2][S][T] = 1;
for(int i = 0;i < n2;++ i) if(S>>i&1)
for(int j = 0;j < n1;++ j) if(T>>j&1)
f[2][S][T] = (LL)f[2][S][T] * e[i+n1][j] % mod;
}
for(int i = 1;i <= n;++ i){
for(int S = 1;S < lim;++ S) if(__builtin_popcount(S) == i){
dp[S] = 1;
for(int T = S&S-1;T;T = T-1&S)
qmo(dp[S] -= (LL)dp[T]*calc(T,S-T)%mod);
ans = (ans + (LL)dp[S]*i%mod*calc(S,lim-1-S)) % mod;
}
}
printf("%d
", ans);
}
2021.9.2
ARC124F Chance Meeting
给定 (n imes m) 的网格图,初始时骆驼在 ((1,1)),猫猫在 ((n,1)),每次操作骆驼可以向右/下走,猫猫可以往右/上走。求满足以下条件的操作序列个数(mod 998244353)。
- 骆驼最后走到 ((n,m)),猫猫最后走到 ((1,m))。
- 存在恰好一个操作,这个操作之后骆驼和猫猫在同一个格子上。
(2le n,mle 2cdot 10^5)。
完全不会,滚去看题解了
先将 (n,m) 都减去 (1),即两者都需做 (n) 次竖直移动和 (m) 次水平移动。
发现只考虑竖直移动时,不关心移动两者中的哪个,因为它们在同一行当且仅当进行了 (n) 次竖直移动。
设 (f_i) 表示两者在 (i) 次水平移动之后第一次相遇的方案数,则答案是 (inom{2n}{n}sum_{i=0}^mf_if_{m-i})。
算 (f) 是经典容斥,设 (g_i) 表示两者在 (i) 次水平移动之后相遇的方案数,则 (g_i=frac{(2i+n)!}{i!^2n!}),枚举两者倒数第二次相遇的位置 (j) 就得到:
其中 (C_i) 是 Catalan 数,所以做一次卷积就算出来了,时间复杂度 (O(n+mlog m))。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 1<<19, M = 600003, mod = 998244353;
int n, m, lim, A[N], B[N], C[N], rev[N], w[N], fac[M], inv[M], ans;
int ksm(int a, int b){
int res = 1;
for(;b;b >>= 1, a = (LL)a * a % mod)
if(b & 1) res = (LL)res * a % mod;
return res;
}
void init(int n){
fac[0] = 1;
for(int i = 1;i <= n;++ i) fac[i] = (LL)fac[i-1] * i % mod;
inv[n] = ksm(fac[n], mod-2);
for(int i = n;i;-- i) inv[i-1] = (LL)inv[i] * i % mod;
}
void qmo(int &x){x += x >> 31 & mod;}
void calrev(int len){
int L = -1; lim = 1;
while(lim <= len){lim <<= 1; ++ L;}
for(int i = 0;i < lim;++ i)
rev[i] = (rev[i>>1]>>1) | ((i&1)<<L);
for(int i = 1;i < lim;i <<= 1){
int wn = ksm(3, (mod-1)/(i<<1)); w[i] = 1;
for(int j = 1;j < i;++ j) w[i+j] = (LL)w[i+j-1]*wn%mod;
}
}
void NTT(int *A, bool op){
for(int i = 0;i < lim;++ i)
if(i < rev[i]) swap(A[i], A[rev[i]]);
for(int md = 1;md < lim;md <<= 1)
for(int i = 0;i < lim;i += md<<1)
for(int j = 0;j < md;++ j){
int y = (LL)A[md+i+j]*(op&&j?mod-w[(md<<1)-j]:w[md+j])%mod;
qmo(A[md+i+j] = A[i+j] - y); qmo(A[i+j] += y-mod);
}
if(op){
int inv = ksm(lim, mod-2);
for(int i = 0;i < lim;++ i)
A[i] = (LL)A[i] * inv % mod;
}
}
int main(){
ios::sync_with_stdio(false);
cin >> n >> m; -- n; -- m;
init(max(n,m<<1)+n); calrev(m<<1);
for(int i = 0;i <= m;++ i) A[i] = B[i] = (LL)fac[2*i+n]*inv[i]%mod*inv[i]%mod*inv[n]%mod;
for(int i = 0;i < m;++ i) C[i+1] = 2ll*fac[2*i]%mod*inv[i]%mod*inv[i+1]%mod;
NTT(B, 0); NTT(C, 0);
for(int i = 0;i < lim;++ i) B[i] = (LL)B[i] * C[i] % mod;
NTT(B, 1);
for(int i = 0;i <= m;++ i) qmo(A[i] -= B[i]);
for(int i = 0;i <= m;++ i) ans = (ans + (LL)A[i]*A[m-i]) % mod;
printf("%lld
", (LL)ans*fac[2*n]%mod*inv[n]%mod*inv[n]%mod);
}
2021.9.3
ARC123F Insert Addition
对于序列 (A={a_1,a_2,cdots,a_n}),定义 (f(A)={a_1,a_1+a_2,a_2,a_2+a_3,cdots,a_{n-1}+a_n,a_n})。
给定三个正整数 (a,b,n,l,r),设 (A={a,b}),(B) 是 (f^{(n)}(A)) 去掉 (>n) 的数之后得到的序列,求 (b_l,b_{l+1},cdots,b_r)。
(a,ble nle 3cdot 10^5),(lle rle 10^{18}),(r-lle 3cdot 10^5)。
发现 (a,b) 的系数就是 Stern-Brocot 树的前 (n) 层的中序遍历。
实际上第 (n) 层之后的数就不可能 (le n) 了,所以 (B) 就是所有 (xperp yland ax+byle n) 的 ((x,y)) 按 (x/y) 排序之后的结果。
注意到 (f^{(n)}(A)=f^{(n-1)}({a,a+b})-{a+b}+f^{(n-1)}({a+b,b})) 的递归性质,于是就可以直接在 SB 树上二分,需要的就是求一棵子树的大小,直接 Mobius 反演即可。
暴力实现的复杂度是大约是 2log,整除分块套类欧是 ( ext{soft-}O(sqrt n)) 的。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 300003, M = 26000;
int a, b, n, tot, pri[M], mu[N];
bool notp[N]; LL l, r;
void print(int x){
if(l > r) return; -- r;
if(l == 1){printf("%d ", x); return;} -- l;
}
void print(int a, int b){if(a + b <= n){print(a, a + b); print(a + b); print(a + b, b);}}
LL calc(int a, int b){
if(a < b) swap(a, b);
LL res = 0;
for(int i = 1, tmp;a+b <= (tmp = n/i);++ i) if(mu[i])
for(int j = 1;a*j <= tmp;++ j)
res += (tmp - a*j) / b * mu[i];
return res;
}
void work(int a, int b){
if(l > r || a + b > n) return;
LL c = calc(a, b);
if(l > c){l -= c; r -= c; return;}
if(l == 1 && c <= r){print(a, b); return;}
work(a, a + b); print(a + b); work(a + b, b);
}
int main(){
ios::sync_with_stdio(false);
cin >> a >> b >> n >> l >> r;
mu[1] = 1; notp[0] = notp[1] = true;
for(int i = 2;i <= n;++ i){
if(!notp[i]){pri[tot++] = i; mu[i] = -1;}
for(int j = 0;j < tot && i * pri[j] <= n;++ j){
notp[i * pri[j]] = true;
if(i % pri[j]) mu[i * pri[j]] = -mu[i];
else break;
}
}
print(a); work(a, b); print(b);
}
2021.9.5
上午开了场 vp,果然又因为沙雕错误卡题了 qwq