2019 Multi-University Training Contest 6
B.Nonsense Time
首先有这样一个结论:随机生成序列的期望\(LIS\)长度为\(O(\sqrt{n})\)。然后就可以愉快的暴力了。
考虑逆序时间,即每次删去一个数,并回答询问。
因为限制\(LIS\)的长度为\(\sqrt{n}\),那么期望删除\(\sqrt{n}\)次才会修改\(LIS\)长度,这个时候暴力更新\(LIS\)即可。
复杂度\(O(nlogn\sqrt{n})\),求\(LIS\)并且得到\(LIS\)序列可用树状数组来搞,复杂度是\(O(nlogn)\)的。Code
#include <bits/stdc++.h> using namespace std; const int N = 5e5 + 5; int T, n; int a[N], b[N], c[N], d[N], nxt[N], pre[N]; bool used[N]; int f[N], g[N]; int lowbit(int x) { return x & (-x); } int query(int x) { int ans = 0, p = 0; for(; x; x -= lowbit(x)) { if(c[x] > ans) { ans = c[x]; p = d[x]; } } return p; } void update(int p, int x, int v) { for(; x < N; x += lowbit(x)) { if(c[x] < v) { c[x] = v; d[x] = p; } } } int build() { int tot = 0, p = 0; for(int i = 1; i <= n; i++) used[i] = 0; for(int i = nxt[0]; i <= n; i = nxt[i]) { int k = query(a[i] - 1); f[i] = f[k] + 1; if(f[i] > tot) tot = f[i], p = i; update(i, a[i], f[i]); g[i] = k; } used[p] = 1; while(g[p]) used[p = g[p]] = 1; for(int i = 1; i <= n; i++) for(int j = i; j < N; j += lowbit(j)) c[j] = d[j] = 0; return tot; } int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> T; while(T--) { cin >> n; for(int i = 1; i <= n; i++) cin >> a[i]; for(int i = 1; i <= n; i++) cin >> b[i]; for(int i = 0; i <= n; i++) nxt[i] = i + 1; for(int i = n + 1; i; i--) pre[i] = i - 1; int ans = build(); vector <int> res; for(int i = n; i >= 1; i--) { res.push_back(ans); pre[nxt[b[i]]] = pre[b[i]]; nxt[pre[b[i]]] = nxt[b[i]]; if(used[b[i]]) ans = build(); } reverse(res.begin(), res.end()); for(int i = 0; i < res.size(); i++) cout << res[i] << " \n"[i == res.size() - 1]; } return 0; }
E.Snowy Smile
题目中生成的区间可能会很大,考虑离散化,但是这样就会影响原来区间的信息。
注意到有用的信息就是区间端点+覆盖次数。那么我们用线段树维护这些信息就行了。线段树中每个结点都维护的一个左闭右开的区间,另外有一些附加的信息,同时还保存每个叶子结点原来的大小(因为是离散化过后的)。
然后就解决了。详见代码吧:Code
#include <bits/stdc++.h> #define MP make_pair using namespace std; typedef long long ll; typedef pair<int, int> pii; const int N = 2005; int T; int x[N], y[N], w[N]; int a[N], b[N]; struct SEG{ ll sum[N << 2], maxv[N << 2], lmax[N << 2], rmax[N << 2]; void push_up(int o) { sum[o] = sum[o << 1] + sum[o << 1|1]; maxv[o] = max(maxv[o << 1], max(maxv[o << 1|1], rmax[o << 1] + lmax[o << 1|1])); lmax[o] = max(lmax[o << 1], sum[o << 1] + lmax[o << 1|1]); rmax[o] = max(rmax[o << 1|1], sum[o << 1|1] + rmax[o << 1]); } void build(int o, int l, int r) { sum[o] = maxv[o] = lmax[o] = rmax[o] = 0; if(l == r) return; int mid = (l + r) >> 1; build(o << 1, l, mid); build(o << 1|1, mid + 1, r); } void update(int o, int l, int r, int p, int v) { if(l == r) { sum[o] += v; maxv[o] = lmax[o] = rmax[o] = sum[o]; return ; } int mid = (l + r) >> 1; if(p <= mid) update(o << 1, l, mid, p, v); else update(o << 1|1, mid + 1, r, p, v); push_up(o); } }seg; int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> T; while(T--) { a[0] = b[0] = 0; int n; cin >> n; for(int i = 1; i <= n; i++) { cin >> x[i] >> y[i] >> w[i]; a[++a[0]] = x[i]; b[++b[0]] = y[i]; } sort(a + 1, a + a[0] + 1); sort(b + 1, b + b[0] + 1); a[0] = unique(a + 1, a + a[0] + 1) - a - 1; b[0] = unique(b + 1, b + b[0] + 1) - b - 1; vector <pii> v[N]; for(int i = 1; i <= n; i++) { x[i] = lower_bound(a + 1, a + a[0] + 1, x[i]) - a; y[i] = lower_bound(b + 1, b + b[0] + 1, y[i]) - b; v[x[i]].push_back(MP(y[i], w[i])); } ll ans = 0; for(int i = 1; i <= a[0]; i++) { seg.build(1, 1, b[0]); for(int j = i; j <= a[0]; j++) { for(auto P : v[j]) { seg.update(1, 1, b[0], P.first, P.second); } ans = max(ans, seg.maxv[1]); } } cout << ans << '\n'; } return 0; }
F.Faraway
每个点会将平面分为四个部分,在每个部分距离的绝对值是可以直接去掉的。
最终被\(n\)个点划分的平面有\(n^2\)个,那么就枚举每一个平面,考虑统计答案。因为\(lcm(2,3,4,5)=60\),所以我们\(60*60\)枚举横纵坐标,判合法之后统计答案即可。Code
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int N = 15; int T; int n, m; int x[N], y[N], k[N], t[N]; int a[N], b[N]; int na, nb; bool check(int X, int Y) { for(int i = 1; i <= n; i++) { if((abs(X - x[i]) + abs(Y - y[i])) % k[i] != t[i]) return 0; } return 1; } ll calc(int l, int r) { int len = r - l - 1; if(len < 0) return 0; return len / 60 + 1; } int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> T; while(T--) { cin >> n >> m; a[na = 1] = b[nb = 1] = m + 1; for(int i = 1; i <= n; i++) { cin >> x[i] >> y[i] >> k[i] >> t[i]; a[++na] = x[i]; b[++nb] = y[i]; } sort(a + 1, a + na + 1); sort(b + 1, b + nb + 1); ll ans = 0; for(int i = 0; i < na; i++) if(a[i] < a[i + 1]) for(int j = 0; j < nb; j++) if(b[j] < b[j + 1]) for(int X = 0; X < 60; X++) { for(int Y = 0; Y < 60; Y++) { if(check(a[i] + X, b[j] + Y)) ans += calc(a[i] + X, a[i + 1]) * calc(b[j] + Y, b[j + 1]); } } cout << ans << '\n'; } return 0; }
K.11 Dimensions
先求出\(dp[i,j]\),表示处理了后面\(i\)位,且模\(m\)余数为\(j\)的总方案数。
因为题目要求字典序第\(k\)小,所以就考虑逐位确定。注意到有很多个"?"的话,前面很多都是取\(0\)的,因为每个位置有\(10\)种选择,\(20\)个位置就有\(10^{20}\)种选择了,所以逐位确定时只用考虑后面\(20\)个位置。
因为\(dp\)已经求出了方案,后面直接gao就行。
代码如下:Code
#include <bits/stdc++.h> using namespace std; typedef long long ll; typedef unsigned long long ull; const int N = 50005, MOD = 1e9 + 7; const ll inf = 1ll << 61; int T; int n, m, q; ll pw[N], pwm[N]; ll dp[N][22]; char s[N]; int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> T; pw[0] = pwm[0] = 1; for(int i = 1; i < N; i++) pw[i] = pw[i - 1] * 10 % MOD; vector <int> p; while(T--) { p.clear(); cin >> n >> m >> q >> s + 1; for(int i = 1; i <= n + 1; i++) pwm[i] = pwm[i - 1] * 10 % m; for(int i = 0; i <= n; i++) { for(int j = 0; j < m; j++) dp[i][j] = 0; } dp[0][0] = 1; ll ans = 0, sum = 0; for(int i = 1; i <= n; i++) { char ch = s[n - i + 1]; if(ch == '?') { p.push_back(i - 1); for(int j = 0; j < 10; j++) { for(int k = 0; k < m; k++) { int tmp = (j * pwm[i - 1] % m + k) % m; dp[i][tmp] += dp[i - 1][k]; if(dp[i][tmp] > inf) dp[i][tmp] = inf; } } } else { for(int k = 0; k < m; k++) { dp[i][k] += dp[i - 1][k]; if(dp[i][k] > inf) dp[i][k] = inf; } ans = (ans + (ch - '0') * pw[i - 1]) % MOD; sum = (sum + (ch - '0') * pwm[i - 1]) % m; } } int c = min(30, (int)p.size()); ll Ans = ans; while(q--) { ll k; cin >> k; ans = Ans; int f = (m - sum) % m; if(dp[n][f] < k) { cout << -1 << '\n'; continue; } f = sum; for(int i = c - 1; i >= 0; i--) { for(int j = 0; j < 10; j++) { int now = (m - (f + j * pwm[p[i]]) % m) % m; if(dp[p[i]][now] >= k) { f = (f + j * pwm[p[i]]) % m; ans = (ans + 1ll * j * pw[p[i]]) % MOD; break; } else k -= dp[p[i]][now]; } } cout << ans << '\n'; } } return 0; }
H.TDL
\((f(n,m)-n)\) ^ \(n=k ->f(n,m)=n\) ^ \(k\)。
打表发现\(f(n,m)\)在给定数据范围内好像不超过600,那么暴力枚举\(n\)从\(k-600\)~\(k+600\)就行了。Code
#include <bits/stdc++.h> using namespace std; typedef long long ll; int T; ll k; int m; int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> T; while(T--) { cin >> k >> m; ll ans = -1; int f = 1; for(ll i = max(1ll, k - 650); f && i <= k + 650; i++) { int cnt = 0; for(ll j = i + 1;; j++) { if(__gcd(i, (ll)j) == 1) cnt++; if(cnt == m) { if((i + (i ^ k)) == j) f = 0, ans = i; break; } } } cout << ans << '\n'; } return 0; }
L.Stay Real
签到题。贪心取即可。Code
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int MAXN = 1e5 + 5, INF = 0x3f3f3f3f; int t, n, a[MAXN]; int main() { ios::sync_with_stdio(false); cin.tie(0); cin >> t; while (t--) { ll ans[2] = { 0 }; cin >> n; for (int i = 1; i <= n; i++) { cin >> a[i]; } sort(a + 1, a + 1 + n, greater<int>()); int now = 0; for (int i = 1; i <= n; i++) { ans[now] += a[i]; now ^= 1; } cout << ans[0] << ' ' << ans[1] << '\n'; } return 0; }