【題錄】Atcoder ARC#104
C.Fair Elevator
每一站都必須要有人上車/下車,則如果把上車標記為1,下車標記為2,最後的合法序列一定是形如x個1,x個2這樣的若干個段拼在一起的。所以我們暴力列舉分段點及段的長度判斷是否有合法解。
#include <bits/stdc++.h> using namespace std; #define maxn 1000000 int n, a[maxn], b[maxn], r[maxn], rec[maxn]; bool mark[maxn]; int read() { int x = 0, k = 1; char c; c = getchar();while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); } while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); return x * k; } int main() { n = read(); for(int i = 1; i <= n; i ++) { a[i] = read(), b[i] = read(); if(a[i] != -1 && b[i] != -1&& a[i] >= b[i]) { printf("No\n"); return 0; } if(a[i] != -1) { if(r[a[i]]) { printf("No\n"); return 0; } r[a[i]] = 1; rec[a[i]] = i; } if(b[i] != -1) { if(r[b[i]]) { printf("No\n"); return 0; } r[b[i]] = 2; rec[b[i]] = i; } } mark[1] = 1; for(int i = 1; i <= 2 * n; i ++) { if(!mark[i]) continue; for(int L = 1; L <= n; L ++) { if(i + 2 * L - 1 > 2 * n) break; bool flag = 1; for(int j = i; j < i + L; j ++) { if(r[j] == 2) { flag = 0; break; } if(r[j + L] == 1) { flag = 0; break; } if(r[j] && rec[j + L] && (rec[j + L] != rec[j])) { flag = 0; break; } } if(flag) mark[i + 2 * L] = 1; } } if(mark[2 * n + 1]) printf("Yes\n"); else printf("No\n"); return 0; }
D.Multiset Mean
如果最後的平均值是x,則序列中\(\sum (a_{i} - x) = 0\),將元素分為大於x的和小於x的兩部分,即為\(\sum (a_{i} - x) = \sum(x - b_{i})\)。小於x的取值情況:1到x-1,每種物品最多拿K個,大於x的取值情況:1到n-x,每種物品最多拿K個。由此我們預處理dp[i][j]表示價值為1-i的物品每個最多拿K個的情況下總價值為j的方案數。這裡使用無限揹包轉多重揹包即為\(O(nm)\)的複雜度。然後對每一個x統計方案數得到答案。
#include <bits/stdc++.h> using namespace std; #define N 105 #define maxn 600005 int n, K, mod, cnt, num[N], dp[N][maxn]; int read() { int x = 0, k = 1; char c; c = getchar(); while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); } while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); return x * k; } void Up(int &x, int y) { x += y; if(x >= mod) x -= mod; } int add(int x, int y) { x += y; if(x >= mod) x -= mod; return x; } int sub(int x, int y) { x -= y; if(x < 0) x += mod; return x; } int mul(int x, int y) { return 1ll * x * y % mod; } int main() { n = read(), K = read(), mod = read(); int lim = (K * n * (n - 1)) >> 1; dp[0][0] = 1; for(int i = 1; i <= n; i ++) { for(int j = 0; j <= lim; j ++) dp[i][j] = dp[i - 1][j]; for(int j = i; j <= lim; j ++) Up(dp[i][j], dp[i][j - i]); for(int j = lim; j >= 0; j --) if(j >= (K + 1) * i) dp[i][j] = sub(dp[i][j], dp[i][j - (K + 1) * i]); } for(int i = 1; i <= n; i ++) { int ans = 0; for(int j = 0; j <= lim; j ++) Up(ans, mul(dp[i - 1][j], dp[n - i][j])); ans = mul(ans, (K + 1)); ans = sub(ans, 1); printf("%d\n", ans); } return 0; }
E.Random LIS
爆搜每個位置上面數字的排名,此時最長上上子序列的長度一定。問題轉化為滿足\(x_{n} < x_{n + 1}\) 且 \(x_{n} <= A_{n}\) 的數列有多少個。把\(A_{n}\) 從小到大分成一段一段的,不同段之間相對大小一定滿足,方案相乘,同段之中則實用組合數求解。雖然C(n, m) 中的n很大,但因為m很小,所以可以O(m) 計算。
#include <bits/stdc++.h> using namespace std; #define N 1000 #define INF 1000000009 #define mod 1000000007 int n, len, ans = 0, mx, cnt, a[N], b[N], p[N], cal[N]; int A[N], inv[N], dp[N], lim[N], R[N], t, mark[N]; int read() { int x = 0, k = 1; char c; c = getchar(); while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); } while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); return x * k; } int mul(int x, int y) { return 1ll * x * y % mod; } int add(int x, int y) { x += y; if(x >= mod) x -= mod; return x; } void Up(int &x, int y) { x += y; if(x >= mod) x -= mod; } int Qpow(int x, int t) { int base = 1; for(; t; t >>= 1, x = mul(x, x)) if(t & 1) base = mul(base, x); return base; } int C(int n, int m) { if(n < m) return 0; int num = 1; for(int i = 1; i <= m; i ++) num = mul(num, mul(n - i + 1, inv[i])); return num; } void dfs2(int now, int lst) { if(now == (mx + 1)) { int ans1 = 1; for(int i = 1; i <= cnt; i ++) cal[i] = 0; for(int i = 1; i <= mx; i ++) cal[b[i]] ++; for(int i = 1; i <= cnt; i ++) ans1 = mul(ans1, C(p[i], cal[i])); ans1 = mul(ans1, len); Up(ans, ans1); return; } for(int i = lst; i <= cnt; i ++) { if(lim[now] < R[i]) break; b[now] = i; dfs2(now + 1, i); } } void Work() { cnt = 0; len = 0; for(int i = 1; i <= n; i ++) dp[i] = 0; for(int i = 1; i <= n; i ++) for(int j = 0; j < i; j ++) if(a[j] < a[i]) dp[i] = max(dp[i], dp[j] + 1); for(int i = 1; i <= n; i ++) len = max(len, dp[i]); for(int i = 1; i <= mx; i ++) lim[i] = INF; for(int i = 1; i <= n; i ++) lim[a[i]] = min(lim[a[i]], A[i]); for(int i = 1; i <= mx; i ++) for(int j = i + 1; j <= mx; j ++) lim[i] = min(lim[i], lim[j]); for(int i = 1; i <= mx; i ++) if(lim[i] - lim[i - 1]) p[++ cnt] = lim[i] - lim[i - 1], R[cnt] = lim[i]; dfs2(1, 1); } void dfs(int num) { if(num == (n + 1)) { for(int i = 1; i <= mx; i ++) if(!mark[i]) return; Work(); return; } for(int i = 1; i <= n; i ++) { a[num] = i; int rec = mx; mx = max(mx, i); mark[i] ++; dfs(num + 1); mx = rec; mark[i] --; } } int main() { n = read(); for(int i = 1; i <= n; i ++) A[i] = read(); for(int i = 1; i <= n; i ++) inv[i] = Qpow(i, mod - 2); dfs(1); for(int i = 1; i <= n; i ++) ans = mul(ans, Qpow(A[i], mod - 2)); printf("%d\n", ans); return 0; }
F.Visibility Sequence
如果把一個點同它左側高度大於自己的點之間連一條邊(不存在則連向超級源點)那麼將會構成一棵樹。觀察這棵樹的性質,會發現每一個子樹節點的編號都是一個連續的區間[l, r],且根節點的編號為l,其權值為子樹中最大。考慮當前的一個根節點下面的若干個兒子,則編號大的點的權值一定大於等於編號小的點的權值。若每個點的權值都在約束範圍之內,則滿足以上幾個條件的每一棵不同形態(點的編號不同)的樹都是一個不同的p序列。為了儘量讓每個點的權值在範圍內,可以貪心的讓每個點的權值往大了取減少對下方點的限制。設dp[l][r][num]為子樹(不包括當前根)的節點編號為[l,r],當前根的權值為num+1 的不同方案數。列舉的時候就列舉兒子中編號最大的點進行轉移。
#include <bits/stdc++.h> using namespace std; #define N 105 #define mod 1000000007 int n, a[N], dp[N][N][N]; int read() { int x = 0, k = 1; char c; c = getchar(); while(c < '0' || c > '9') { if(c == '-') k = -1; c = getchar(); } while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); return x * k; } int mul(int x, int y) { return 1ll * x * y % mod; } void Up(int &x, int y) { x += y; if(x >= mod) x -= mod; } int dfs(int l, int r, int mx) { if(l > r) return 1; if(!mx) return 0; if(dp[l][r][mx] != -1) return dp[l][r][mx]; dp[l][r][mx] = 0; for(int i = l; i <= r; i ++) { int top = min(mx, a[i]); Up(dp[l][r][mx], mul(dfs(l, i - 1, top), dfs(i + 1, r, top - 1))); } return dp[l][r][mx]; } int main() { n = read(); for(int i = 1; i <= n; i ++) a[i] = read(); for(int i = 1; i <= n; i ++) for(int j = i; j <= n; j ++) for(int k = 1; k <= n; k ++) dp[i][j][k] = -1; printf("%d\n", dfs(1, n, n)); return 0; }