2019 Multi-University Training Contest 6
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
先对横纵坐标离散化,之后\(O(n^2)\)枚举上下边界,下边界扫过去时维护\(y\)的和,之后解决的就是一个最大连续子段和的问题。
因为是动态维护,考虑线段树。详见代码:
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;
}
重要的是自信,一旦有了自信,人就会赢得一切。