【题录】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; }