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