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 递增。
\(n\le 100\),\(x_i\le 10^{18}\)。
从后往前考虑,对于末尾的数 \(x_i\),要满足 \(x_i\nmid\text{LCM}_{j\ne i}(x_j)\)。
为了方便判断考虑转化一下:\(\text{LCM}_{j\ne i}(\gcd(x_j,x_i))<x_i\)。
同时对于可以放在末尾的数 \(x_i\),它放在前面不会更优,于是就放末尾了。
暴力判断,时间复杂度 \(O(n^3\log 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\) 组数据。\(T\le 2\cdot 10^5\),\(n\le 10^9\),\(a_1,a_2,b_1,b_2\le 10^6\)。
先转化为 \(\lfloor i/b_1\rfloor-\lfloor i/b_2\rfloor=a_2-a_1\),设为 \(a\)。
先特判掉 \(b_1=b_2\) 的情况,不妨设 \(b_1<b_2\)。
显然可以先圈一圈范围:\(i/b_1-i/b_2\in(-1,1]\)。
乱搞一下,分成 \(i/b_1-i/b_2\in(-1,0]\) 和 \(i/b_1-i/b_2\in(0,1]\)。此时计算一下 \(\sum\lfloor i/b_1\rfloor-\sum\lfloor i/b_2\rfloor\) 就可以求出对应的答案。
时间复杂度 \(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\n", 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\n", ans);
}
}
ARC124E Pass to Next
给定 \(n\) 个正整数,求
\(n\le 10^5\),\(a_i\le 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\n", ans);
}
ARC125E Snack
给定 \(n\) 种零食,第 \(i\) 种零食有 \(a_i\) 份。有 \(m\) 个小朋友分零食,第 \(i\) 个小朋友至多得到 \(c_i\) 份零食,至多得到 \(b_i\) 份同一种零食。求最多可以分多少份零食。
\(n,m\le2\cdot 10^5\),\(a_i,c_i\le 10^{12}\),\(b_i\le 10^7\)。
这显然是最大流模型,转化为最小割之后直接做。时间复杂度 \(O(n\log 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\)。
\(n\le 2\cdot 10^5\)。
实际上这棵树并没有什么用,我们只需要度数序列 \(d_1,d_2,\cdots,d_n\),满足 \(\sum d_i=2n-2\),\(d_i\ge 1\)。除了这个性质之外就没有别的了。
所以考虑随便打表猜结论,然后什么都看不出来。
乱搞一下,发现把 \(d_i\) 都减去 \(1\) 没什么区别,于是就继续打表。
发现若 \(m(s),M(s)\) 分别表示选出 \(d_i\) 之和为 \(s\) 的点数的最小值和最大值,则 \([m(s),M(s)]\) 的点数都可以选出来。
如果这个结论是对的,那么就不用对点数记状态了,直接大力背包即可。时间复杂度 \(O(n\sqrt n)\)。
题解告诉我们这个结论确实是对的。考虑设 \(z=\sum[d_i=0]\),则对于任意子集 \(S\subseteq [n]\),\(-z\le \text{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\n", ans);
}
CF1556F Sports Betting
给定 \(n\) 个点的竞赛图,每个点 \(i\) 有点权 \(a_i\),\(i\to j\) 的概率是 \(\frac{a_i}{a_i+a_j}\),求能直接或间接到达所有点的点数期望值\(\bmod(10^9+7)\)。
\(n\le 14\),\(a_i\le 10^6\)。
根据经典套路,竞赛图缩点之后是链,所以直接枚举入度为 \(0\) 的强连通分量 \(S\),计算 \(|S|f_Sg_{S,V\backslash S}\) 之和,其中 \(f_S\) 表示 \(S\) 是强连通图的概率,\(g_{S,T}=\prod_{i\in S}\prod_{j\in T}\frac{a_i}{a_i+a_j}\) 表示所有 \(S,T\) 之间的边的方向都是 \(S\to T\)。
先考虑计算 \(f_S\),直接容斥,如果不是强连通图,枚举入度为 \(0\) 的强连通分量 \(T\),则有
算 \(g\) 的时候先对所有 \((x,T)\) 预处理 \(\prod_{y\in T}a_x/(a_x+a_y)\) 就可以做到 \(O(n3^n)\)。
题解是 \(O(3^n)\) 做法。直接折半,分别将 \(S,T\) 拆成两部分得到四项,算 \(g\) 的复杂度就直接降到 \(\text{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\n", ans);
}
2021.9.2
ARC124F Chance Meeting
给定 \(n\times m\) 的网格图,初始时骆驼在 \((1,1)\),猫猫在 \((n,1)\),每次操作骆驼可以向右/下走,猫猫可以往右/上走。求满足以下条件的操作序列个数\(\bmod 998244353\)。
- 骆驼最后走到 \((n,m)\),猫猫最后走到 \((1,m)\)。
- 存在恰好一个操作,这个操作之后骆驼和猫猫在同一个格子上。
\(2\le n,m\le 2\cdot 10^5\)。
完全不会,滚去看题解了
先将 \(n,m\) 都减去 \(1\),即两者都需做 \(n\) 次竖直移动和 \(m\) 次水平移动。
发现只考虑竖直移动时,不关心移动两者中的哪个,因为它们在同一行当且仅当进行了 \(n\) 次竖直移动。
设 \(f_i\) 表示两者在 \(i\) 次水平移动之后第一次相遇的方案数,则答案是 \(\binom{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+m\log 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\n", (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,b\le n\le 3\cdot 10^5\),\(l\le r\le 10^{18}\),\(r-l\le 3\cdot 10^5\)。
发现 \(a,b\) 的系数就是 Stern-Brocot 树的前 \(n\) 层的中序遍历。
实际上第 \(n\) 层之后的数就不可能 \(\le n\) 了,所以 \(B\) 就是所有 \(x\perp y\land ax+by\le 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,整除分块套类欧是 \(\text{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
2021.9.6
G Colorful Doors
直线上有 \(n\) 对传送门,进入一道传送门时会从与之对应的传送门的相同方向出来。
Snuke 从左边向右一直走,给定 \(s_1,s_2,\cdots,s_{2n-1}\) 表示 Snuke 有没有从第 \(i\) 道传送门出来过。
构造放置这 \(n\) 对传送门的方案,需判断无解。
\(n\le 10^5\)。
若将直线接成一个环,即认为第 \(2n\) 道传送门的右边是第 \(1\) 道传送门,则这个环上的路径等价于若干个环,条件即为钦定其中的一个环恰好是某些段。
先看只有一个环的情况:
- 若 \(n\) 是偶数,一个构造是 \((1,2,1,2,3,4,3,4,\cdots,2n-1,2n,2n-1,2n)\)。
- 若 \(n\) 是奇数,
发现玩不出来所以无解,因为考虑加一对传送门,如果加在同一个环上那么这个环会裂开,如果加在两个不同环上那么这两个环会合并,所以环数与 \(n-1\) 同奇偶。
考虑两边都是 \(1\) 的门的个数 \(s\),因为它们之间要两两对应所以 \(s\) 必须是偶数。
- 若 \(s\) 是 \(4\) 的倍数,可以通过调整 \(1\) 连续段开头和末尾的传送门使得恰好跳过 \(0\) 这些段,剩下的就变成 \(n\) 是偶数且全 \(1\) 的情况。
- 若 \(s\) 是 \(4k+2\pod{k\in\mathbb N}\),若长度 \(>1\) 的 \(1\) 连续段只有一个则相当于 \(n\) 是奇数且全 \(1\) 的情况,否则可以先将两对门配对,规约到 \(s=4k\) 的情况。
#include<bits/stdc++.h>
#define PB emplace_back
using namespace std;
const int N = 200003;
char str[N];
int n, m, num, ans[N];
vector<int> v[2][2];
void work(vector<int> &v, int val){v.erase(lower_bound(v.begin(), v.end(), val));}
int main(){
ios::sync_with_stdio(false);
cin >> n >> str+1; str[0] = str[n<<1] = '1';
for(int i = 0;i < (n<<1);++ i)
v[str[i] == '1'][str[i+1] == '1'].PB(i);
m = v[1][1].size();
if(m & 1){puts("No"); return 0;}
if(m & 2){
int a = 0;
while(a <= n<<1 && str[a] == '1') ++ a;
if(a > n<<1){puts("No"); return 0;}
while(true){
while(a < n<<1 && str[a+1] == '0') ++ a;
if(a >= n<<1){puts("No"); return 0;}
if(a+2 <= n<<1 && str[a+2] == '1') break;
a += 2;
}
int b = a+1;
while(b <= n<<1 && str[b] == '1') ++ b;
if(b > n<<1){puts("No"); return 0;}
int p = 0;
while(p < m && v[1][1][p] > a && v[1][1][p] < b-1) ++ p;
if(p >= m){puts("No"); return 0;}
while(p+1 < m && v[1][1][p+1] < a) ++ p;
int x = a, y = b-1, z = a+1, w = v[1][1][p];
ans[x] = ans[y] = ++num;
ans[z] = ans[w] = ++num;
work(v[0][1], x); work(v[1][0], y);
work(v[1][1], z); work(v[1][1], w);
}
for(int i = 0;i < v[0][1].size();++ i) ans[v[0][1][i]] = ans[v[1][0][i]] = ++num;
for(int i = 0;i < v[0][0].size();i += 2) ans[v[0][0][i]] = ans[v[0][0][i+1]] = ++num;
for(int i = 0;i < v[1][1].size();i += 4){
ans[v[1][1][i]] = ans[v[1][1][i+2]] = ++num;
ans[v[1][1][i+1]] = ans[v[1][1][i+3]] = ++num;
}
puts("Yes");
for(int i = 0;i < n<<1;++ i) printf("%d ", ans[i]);
}
2021.9.7
SWERC 2020
L Restaurants
给定 \(n\) 个顾客和 \(m\) 个餐厅,每个顾客预订了一些餐厅,并对这些餐厅进行排序,每个餐厅对预订它的顾客进行排序,第 \(i\) 个餐厅的容量是 \(c_i\),构造给一些顾客分配餐厅的方案,使得:
- 每个餐厅都不容纳多于容量数量的顾客;
- 每个顾客都只分配到一家餐厅;
- 没有一对餐厅 \(r\) 与顾客 \(c\),使得:
- 顾客 \(c\) 预订了餐厅 \(r\);
- 顾客 \(c\) 没有分配到餐厅 或 相比于他被分配到的餐厅,他更喜欢餐厅 \(r\);
- 餐厅 \(r\) 有一些剩余位置 或 已满但相比于至少一个分配到餐厅 \(r\) 的顾客,它更喜欢顾客 \(c\)。
\(c_i\le n\le 5\cdot 10^4\),\(m\le 10^4\),预订数量 \(\le 10^6\)。
翻译题面好 tm 累,感觉直接上稳定婚姻匹配模板就行,结果冲一发就过了
#include<bits/stdc++.h>
#define PB emplace_back
using namespace std;
typedef pair<int, int> pii;
const int N = 50003, M = 10003, K = 2000003;
int n, m, c[M], q[K], fr, re;
string str;
stringstream ss;
vector<int> V[N];
map<int, int> mp[M];
set<pii> S[M];
bool del[N];
int main(){
ios::sync_with_stdio(false);
cin >> n >> m;
for(int i = 0;i < m;++ i) cin >> c[i];
getline(cin, str);
for(int i = 0, u;i < n;++ i){
q[re++] = i;
getline(cin, str); ss.clear(); ss << str;
while(ss >> u) V[i].PB(u-1);
reverse(V[i].begin(), V[i].end());
}
for(int i = 0, u;i < m;++ i){
getline(cin, str); ss.clear(); ss << str;
int now = 0;
while(ss >> u){mp[i][u-1] = -now; ++now;}
}
while(fr < re){
int u = q[fr++];
if(V[u].empty()){del[u] = true; continue;}
int v = V[u].back(); V[u].pop_back();
S[v].emplace(mp[v][u], u);
if(S[v].size() > c[v]){
q[re++] = S[v].begin()->second;
S[v].erase(S[v].begin());
}
}
for(int i = 0;i < n;++ i) if(!del[i]) printf("%d\n", i+1);
}
M Fantasmagorie
定义 \(01\) 矩阵是好的当且仅当:
- 边界上的元素都相同;
- 不含 \(\begin{pmatrix}0 & 1 \\ 1 & 0\end{pmatrix}\) 或 \(\begin{pmatrix}1 & 0 \\ 0 & 1\end{pmatrix}\) 的连续子矩阵。
- 将同色四连通块缩点之后,每个点的度数 \(\le 2\)。
给定两个 \(H\times W\) 的好矩阵,构造一组每次 flip 一个元素,从第一个矩阵变为第二个矩阵的长度 \(\le 2.5\cdot 10^5\) 的操作序列,使得两种颜色的四连通块个数保持不变,且自始至终是好矩阵。需判断无解。
\(H\le 64\),\(W\le 103\)。
还是这个条件太恶心了,vp 的时候看都不想看 。
条件说明同色四连通块应当是套在一起的。
因为操作可逆,所以考虑将两个矩阵都变为一个标准化的矩阵。
......... ......... .........
..#####.. .#######. .#######.
..##.##.. .###.###. .#.....#.
..#...#.. .##...##. .#.###.#.
..#.#.#.. .##.#.##. .#.###.#.
..#...#.. => .##...##. => .#.###.#.
..#####.. .#######. .#.###.#.
...#.#... .#######. .#.###.#.
...#.#... .#######. .#.###.#.
...#.#... .#######. .#.....#.
.###.###. .#######. .#######.
......... ......... .........
我们可以把次外的连通块扩展,将最外的连通块挤成宽为 \(1\) 的环,然后去掉边界之后就是一个子问题。只需要对最外的连通块 bfs,按照与左上角的距离从大到小 flip 就可以保证自始至终是好矩阵。
由此可得有解当且仅当最终得到的矩阵相同,也即最外层字符及连通块数均相同。
#include<bits/stdc++.h>
#define PB emplace_back
#define fi first
#define se second
#define ALL(x) x.begin(), x.end()
using namespace std;
typedef pair<int, int> pii;
typedef tuple<int, int, int> tiii;
const int N = 111, d[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
int m, n;
struct {
int lys, dis[N][N];
string S[N];
vector<pii> ans;
void work(){
for(int i = 0;i < n;++ i) cin >> S[i];
lys = 0;
while(true){
queue<pii> q; priority_queue<tiii> pq;
memset(dis, -1, sizeof dis);
q.emplace(lys, lys); dis[lys][lys] = 0;
while(!q.empty()){
pii _ = q.front(); q.pop();
for(int i = 0;i < 4;++ i){
int nx = _.fi + d[i][0], ny = _.se + d[i][1];
if(nx >= 0 && nx < n && ny >= 0 && ny < m && S[lys][lys] == S[nx][ny] && !~dis[nx][ny]){
q.emplace(nx, ny); dis[nx][ny] = dis[_.fi][_.se] + 1;
}
}
}
for(int i = lys;i < n-lys;++ i)
for(int j = lys;j < m-lys;++ j)
if(S[lys][lys] != S[i][j]) pq.emplace(1e9, i, j);
++ lys; if(pq.empty()) break;
while(!pq.empty()){
int x = get<1>(pq.top()), y = get<2>(pq.top()); pq.pop();
if(S[lys-1][lys-1] == S[x][y]){ans.PB(x, y); S[x][y] ^= '.'^'#';}
for(int i = 0;i < 4;++ i){
int nx = x + d[i][0], ny = y + d[i][1];
if(nx >= lys && nx < n-lys && ny >= lys && ny < m-lys && S[lys-1][lys-1] == S[nx][ny] && ~dis[nx][ny]){
pq.emplace(dis[nx][ny], nx, ny); dis[nx][ny] = -1;
}
}
}
}
}
} _1, _2;
int main(){
ios::sync_with_stdio(false);
cin >> m >> n;
_1.work(); _2.work();
if(_1.S[0][0] != _2.S[0][0] || _1.lys != _2.lys){
puts("IMPOSSIBLE"); return 0;
}
reverse(ALL(_2.ans));
for(const pii &o : _1.ans) printf("%d %d\n", o.se, o.fi);
for(const pii &o : _2.ans) printf("%d %d\n", o.se, o.fi);
}
J Daisy's Mazes
给定 \(N\) 个点 \(M\) 条边的有向图,每条边染 \(C\) 种颜色,Daisy 带上一个栈从 \(0\) 号点出发,栈的每个元素是 \(C\) 种颜色之一。
设当前在点 \(u\),栈顶元素是 \(c\),若 \(u\) 有颜色 \(c\) 的出边则选择一条颜色 \(c\) 的出边并弹栈,否则任意选择一条出边,将该种颜色压入栈。
已知 Daisy 可以走到 \(N-1\) 号点,求初始时的栈大小的最小值。
\(N\le 50\),\(M\le 100\),\(C\le 20\),保证有解。
对于这种图上路径的括号匹配问题,考虑从剥括号的过程设计 dp 状态。
考虑再引入 \(n\) 个点 \(0=x_0,x_1,\cdots,x_{n-1}\),\(x_{i+1}\) 向 \(x_i\) 连所有颜色的边,则栈大小可以为 \(m\) 当且仅当带空栈从 \(x_m\) 出发可以走到终点。
因为初始的栈如果有两个连续的相同颜色则一定不优(要用的时候可以转化为先压入再弹出),所以可以先从 \(x_m\) 走到 \(0\) 得到任意的大小为 \(m\) 的栈。
然后 \(N-1\) 向自己连所有颜色的边,就可以限制初始和结束时刻都是空栈,经过的边的颜色即为括号序列。
于是就可以 dp 了,设 \(f(c,r_1,r_2)\) 表示不匹配栈顶的颜色 \(c\) 的情况下,是否有 \(r_1\to r_2\) 的括号序列。
转移即为拼接括号序列和套一层括号。时间复杂度是玄学 。
#include<bits/stdc++.h>
#define PB emplace_back
using namespace std;
const int N = 103, C = 21;
int n, m, k;
bool f[N][N][C];
vector<int> E[N][C], R[N][C];
void add(int u, int v, int c){E[u][c].PB(v); R[v][c].PB(u);}
void dfs(int u, int v, int c){
if(f[u][v][c]) return;
f[u][v][c] = true;
for(int i = 1;i < 2*n;++ i){
if(f[v][i][c]) dfs(u, i, c);
if(f[i][u][c]) dfs(i, v, c);
}
for(int x : R[u][c])
for(int d = 0;d <= k;++ d)
if(x < n || E[x][d].empty())
for(int y : E[v][c])
dfs(x, y, d);
}
int main(){
ios::sync_with_stdio(false);
cin >> n >> m >> k;
for(int i = 0, u, v, c;i < m;++ i){
cin >> u >> v >> c; add(u + n, v + n, c);
}
for(int i = 0;i < k;++ i) add(2*n-1, 2*n-1, i);
for(int i = 1;i < n;++ i)
for(int j = 0;j < k;++ j)
add(i, i + 1, j);
for(int i = 0;i < 2*n;++ i)
for(int j = 0;j <= k;++ j)
dfs(i, i, j);
for(int i = n;i;-- i)
if(f[i][2*n-1][k]){printf("%d\n", n - i); return 0;}
}
假装在做集训队作业,进度 13/150