【题录】Atcoder ExaWizards 2019 & KEYENCE 2019
ExaWizards 2019:
C.
注意到左移和右移的操作都不会改变魔像的相对位置,所以如果a位置的魔像最后会从左边掉出,则a左侧的所有魔像均会从其左侧掉出。通过二分找到最右边的从左边掉出的魔像,最左边的从右边掉出的魔像,中间的魔像都会被保留。
#include <bits/stdc++.h> using namespace std; #define maxn 1000000 int n, Q, d[maxn], ansl, ansr; char s[maxn], c[maxn]; bool Checkl(int x) { for(int i = 1; i <= Q; i ++) if(c[i] == s[x]) { x += d[i]; if(x <= 0) return 1; if(x >= n + 1) return 0; } return 0; } bool Checkr(int x) { for(int i = 1; i <= Q; i ++) if(c[i] == s[x]) { x += d[i]; if(x >= n + 1) return 1; if(x <= 0) return 0; } return 0; } int main() { cin >> n >> Q; scanf("%s", s + 1); for(int i = 1; i <= Q; i ++) { char t; scanf(" %c %c", &c[i], &t); if(t == 'L') d[i] = -1; else d[i] = 1; } int l = 1, r = n; while(l < r) { int mid = (l + r) >> 1; if(mid + 1 <= r) mid += 1; if(Checkl(mid)) l = mid; else r = mid - 1; } if(Checkl(l)) ansl = l; else ansl = 0; l = 1, r = n; while(l < r) { int mid = (l + r) >> 1; if(Checkr(mid)) r = mid; else l = mid + 1; } if(Checkr(l)) ansr = l; else ansr = n + 1; printf("%d", ansr - 1 - ansl); return 0; }
D.
把所有元素从小到大排序后从大的开始放入操作序列。则当前最大的元素只有放在当前操作序列的第一个空位才会对黑板上当前的x产生影响(否则x一定会在这个数字之前对一个更小的数字进行取模操作,此时这个大数失去意义)。设dp[i][j]为放入了前i个数(倒序)后黑板上的x为j的方案数,分第i - 1个数是放在第一个位置还是后面的位置两种情况进行转移即可。
#include <bits/stdc++.h> using namespace std; #define maxn 105000 #define mod 1000000007 int n, X, ans, a[maxn], f[2][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 Add(int &a, int b) { a += b; if(a >= mod) a -= mod; } int mul(int a, int b) { return 1ll * a * b % mod; } int main() { n = read(), X = read(); for(int i = 1; i <= n; i ++) a[i] = read(); sort(a + 1, a + 1 + n); int now = 1, nxt = 0; f[now][X] = 1; for(int i = n + 1; i > 1; i --) { for(int j = X; j >= 0; j --) f[nxt][j] = 0; for(int j = X; j >= 0; j --) if(f[now][j]) { Add(f[nxt][j % a[i - 1]], f[now][j]); Add(f[nxt][j], mul(f[now][j], (i - 2))); } swap(nxt, now); } for(int i = 0; i <= X; i ++) Add(ans, mul(f[now][i], i)); printf("%d\n", ans); }
E.
枚举最后的连续同色球是几个什么颜色的球,则前面的每个出现的概率是二分之一,后面每个球的概率都是一。前面每个球等价,后面每个球等价,故总共的序列个数*概率就是对一个位置的概率的贡献。使用线段树进行区间加操作。
#include <bits/stdc++.h> using namespace std; #define mod 1000000007 #define maxn 3000000 int B, W, inv2, ppow[maxn], tag[maxn], t[maxn], fac[maxn], finv[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 add(int a, int b) { a += b; if(a >= mod) a -= mod; return a; } void Add(int &a, int b) { a += b; if(a >= mod) a -= mod; } int mul(int a, int b) { return 1ll * a * b % 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 m, int n) { if(n < m) return 0; if(n < 0 || m < 0) return 0; return mul(fac[n], mul(finv[m], finv[n - m])); } void Push_down(int p, int l, int r) { if(!tag[p]) return; int mid = (l + r) >> 1; Add(tag[p << 1], tag[p]); Add(tag[p << 1 | 1], tag[p]); Add(t[p << 1], mul(tag[p], mid - l + 1)); Add(t[p << 1 | 1], mul(tag[p], r - mid)); tag[p] = 0; return; } void Mod(int p, int l, int r, int L, int R, int x) { if(l > R || r < L) return; if(l >= L && r <= R) { Add(t[p], mul(x, r - l + 1)); Add(tag[p], x); return; } Push_down(p, l, r); int mid = (l + r) >> 1; Mod(p << 1, l, mid, L, R, x); Mod(p << 1 | 1, mid + 1, r , L, R, x); t[p] = add(t[p << 1], t[p << 1 | 1]); } void Print(int p, int l, int r) { if(l == r) { printf("%d\n", t[p]); return; } int mid = (l + r) >> 1; Push_down(p, l, r); Print(p << 1, l, mid); Print(p << 1 | 1, mid + 1, r); } int main() { B = read(), W = read(); inv2 = Qpow(2, mod - 2); fac[0] = finv[0] = ppow[0] = 1; int n = B + W; for(int i = 1; i <= n; i ++) ppow[i] = mul(ppow[i - 1], inv2); for(int i = 1; i <= n; i ++) fac[i] = mul(fac[i - 1], i); for(int i = 1; i <= n; i ++) finv[i] = Qpow(fac[i], mod - 2); for(int i = 1; i <= B; i ++) { int x = B - i, y = W; Mod(1, 1, n, n - i + 1, n, mul(C(x, x + y - 1), ppow[x + y])); if(x) Mod(1, 1, n, 1, n - i - 1, mul(C(y - 1, x + y - 2), ppow[x + y])); } for(int i = 1; i <= W; i ++) { int x = B, y = W - i; if(B) Mod(1, 1, n, 1, n - i - 1, mul(C(y, x + y - 2), ppow[x + y])); if(B) Mod(1, 1, n, n - i, n - i, mul(C(y, x + y - 1), ppow[x + y])); } Print(1, 1, n); return 0; }
KEYENCE 2019:
D.
从大到小去进行处理。如果处理到x时,一个行或者列的最大值已经确定,我们把这样的行或者列称作确定行/确定列。如果一个数字既是行最大又是列最大,那么这个数字的位置已经确定了。如果一个数字仅仅只是行最大,说明它应该出现在该行中的确定列,每个确定列等价,可以随便放。(仅仅列最大同理)。如果一个数字两个都不是,那么应该出现在确定行和确定列的交界(全部位置等价)。使用乘法原理计算。
#include <bits/stdc++.h> using namespace std; #define N 1005 #define mod 1000000007 #define maxn 1200000 int n, m, markl[N], markr[N], L[maxn], R[maxn], cntl, cntr, ans = 1, tot; 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 main() { n = read(), m = read(); for(int i = 1; i <= n; i ++) { int x = read(); R[x] = i; } for(int i = 1; i <= m; i ++) { int x = read(); L[x] = i; } for(int i = n * m; i >= 1; i --) { if(L[i] && R[i]) { if(markl[L[i]] || markr[R[i]]) { printf("0\n"); exit(0); } markl[L[i]] = markr[R[i]] = 1; tot += cntl + cntr; cntl ++, cntr ++; } else if(L[i]) { if(markl[L[i]]) { printf("0\n"); exit(0); } markl[L[i]] = 1; cntl ++; tot += cntr - 1; ans = mul(ans, cntr); } else if(R[i]) { if(markr[R[i]]) { printf("0\n"); exit(0); } markr[R[i]] = 1; cntr ++; tot += cntl - 1; ans = mul(ans, cntl); } else { ans = mul(ans, tot); tot -= 1; } } printf("%d\n", ans); return 0; }
E
分析什么样的边一定不会出现在最小生成树中。如果存在若干条边能够连接i,j两点,且这若干条边的权值均小于等于i,j之间的边,则i,j之间的边一定不会出现在最小生成树中。考虑分治,每次当前层只考虑左边的点和右边的点之间的连边。那么令f[i] = a[i] - D * i, g[i] = a[i] + D * i, 则一条i,j(i为左侧点,j为右侧点)之间的边的权值为 f[i] + g[j],设i1为f[i]最小的点,j1为个g[j]最小的点,那么如果i不等于i1,j不等于j1,那么f[i]+g[j1],f[i1]+g[j],f[i1]+g[j1]这三条边均小于等于f[i]+g[j]。所以这样的i,j之间的边可以忽略。那么每一层只需要加入i = i1 或 j = j1 的边, 一共nlogn条边。之后再排序求出最小生成树的复杂度一共为\(nlog^{2}n\)。
#include <bits/stdc++.h> using namespace std; #define LL long long #define maxn 10000000 int n, D, cnt, fa[maxn], a[maxn]; LL ans, f[maxn], g[maxn]; struct edge { int u, v; LL w; edge(int _u, int _v, LL _w) { u = _u, v = _v, w = _w; } friend bool operator <(edge a, edge b) { return a.w < b.w; } friend bool operator >(edge a, edge b) { return a.w > b.w; } }; priority_queue <edge, vector<edge>, greater<edge> > q; 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 find(int u) { if(fa[u] != u) return fa[u] = find(fa[u]); else return u; } void Div_Con(int l, int r) { if(l >= r) return; int mid = (l + r) >> 1; int i0 = 0, j0 = 0; for(int i = l; i <= mid; i ++) if((!i0) || (f[i] < f[i0])) i0 = i; for(int i = mid + 1; i <= r; i ++) if((!j0) || (g[i] < g[j0])) j0 = i; for(int i = l; i <= mid; i ++) if(i != i0) q.push(edge(i, j0, f[i] + g[j0])); for(int i = mid + 1; i <= r; i ++) q.push(edge(i0, i, f[i0] + g[i])); Div_Con(l, mid); Div_Con(mid + 1, r); } signed main() { n = read(), D = read(); for(int i = 1; i <= n; i ++) a[i] = read(), fa[i] = i; for(int i = 1; i <= n; i ++) f[i] = 1ll * a[i] - 1ll * i * D; for(int i = 1; i <= n; i ++) g[i] = 1ll * a[i] + 1ll * i * D; Div_Con(1, n); while(!q.empty() && (cnt < (n - 1))) { edge t = q.top(); q.pop(); int fu = find(t.u), fv = find(t.v); if(fu != fv) ans += t.w, fa[fu] = fv, cnt ++; } printf("%lld\n", ans); return 0; }