官方数据和标程:2022杭电多校资料包
目录
难度评价
数据结构场,各种线段树维护应有尽有。
7题一百名,5题快两百名,4题快四百名。有手就能写4到5题,起码补到7题,金牌补到9题以上。
B、G、I、L签到,C数据有锅掉成签到,A、H、K中等,D、E、F困难,J大难
A. Static Query on Tree
数据结构
要求既能从集合A、B內一点出发到达,又能走到集合C內一点的结点数量,显然这些点一定在c子树內,且是a、b的祖先。于是进行树剖,对集合A、B內每个点,其到根结点的路径都打上a/b标记;对集合C內每个点,其子树內所有点打上c标记。问题转换为怎么用线段树维护同时有三种标记的点的数量。
因为是区间操作,而贡献来自单点,不可能让修改暴力递归至单点,可以考虑将标记表示为二进制形式,如三种标记都有为111,则我们需要维护8种标记情况的结点数量,具体写起来很复杂。
#include <bits/stdc++.h>
using namespace std;
#define ls rt << 1
#define rs rt << 1 | 1
constexpr int MAXN = 2e5 + 5;
int sz[MAXN], dep[MAXN], father[MAXN], son[MAXN], dfn, n, q;
int top[MAXN], num[MAXN], rnk[MAXN], bottom[MAXN];
int sum[MAXN << 2], cnt[MAXN << 2][8], tag[MAXN << 2], clr[MAXN << 2];
vector<int> G[MAXN];
void dfs1(int v, int fa)
{
dep[v] = dep[fa] + 1;
sz[v] = 1;
son[v] = 0;
father[v] = fa;
for (auto u : G[v])
{
if (u == fa)
continue;
dfs1(u, v);
sz[v] += sz[u];
if (sz[u] > sz[son[v]])
son[v] = u;
}
}
void dfs2(int v, int tp)
{
top[v] = tp;
num[v] = ++dfn;
bottom[v] = num[v];
rnk[dfn] = v;
if (son[v] == 0)
return;
dfs2(son[v], tp);
for (auto u : G[v])
if (u != son[v] && u != father[v])
dfs2(u, u);
bottom[v] = dfn;
}
void pushUp(int rt)
{
sum[rt] = sum[ls] + sum[rs];
for (int i = 0; i < 8; i++)
cnt[rt][i] = cnt[ls][i] + cnt[rs][i];
}
void pushDown(int rt, int ln, int rn)
{
if (clr[rt])
{
clr[ls] = clr[rs] = 1;
tag[ls] = tag[rs] = 0;
sum[ls] = sum[rs] = 0;
cnt[ls][0] = ln, cnt[rs][0] = rn;
for (int i = 1; i < 8; i++)
cnt[ls][i] = cnt[rs][i] = 0;
clr[rt] = 0;
}
if (tag[rt])
{
tag[ls] |= tag[rt], tag[rs] |= tag[rt];
for (int i = 0; i < 3; i++)
{
if (tag[ls] & (1 << i))
{
int tmp[8] = {0};
for (int j = 1; j < 8; j++)
if (j & (1 << i))
tmp[j] = cnt[ls][j] + cnt[ls][j ^ (1 << i)];
for (int j = 0; j < 8; j++)
cnt[ls][j] = tmp[j];
}
if (tag[rs] & (1 << i))
{
int tmp[8] = {0};
for (int j = 1; j < 8; j++)
if (j & (1 << i))
tmp[j] = cnt[rs][j] + cnt[rs][j ^ (1 << i)];
for (int j = 0; j < 8; j++)
cnt[rs][j] = tmp[j];
}
}
sum[ls] = cnt[ls][7], sum[rs] = cnt[rs][7];
tag[rt] = 0;
}
}
void build(int l, int r, int rt)
{
if (l == r)
{
cnt[rt][0] = 1;
return;
}
int mid = (l + r) >> 1;
build(l, mid, ls);
build(mid + 1, r, rs);
pushUp(rt);
}
void modify(int L, int R, int C, int l, int r, int rt)
{
if (L <= l && r <= R)
{
tag[rt] |= C;
for (int i = 0; i < 3; i++)
{
if (tag[rt] & (1 << i))
{
int tmp[8] = {0};
for (int j = 1; j < 8; j++)
if (j & (1 << i))
tmp[j] = cnt[rt][j] + cnt[rt][j ^ (1 << i)];
for (int j = 0; j < 8; j++)
cnt[rt][j] = tmp[j];
}
}
sum[rt] = cnt[rt][7];
return;
}
int mid = (l + r) >> 1;
pushDown(rt, mid - l + 1, r - mid);
if (L <= mid)
modify(L, R, C, l, mid, ls);
if (R > mid)
modify(L, R, C, mid + 1, r, rs);
pushUp(rt);
}
void pathModify(int v, int u, int z)
{
while (top[v] != top[u])
{
if (dep[top[v]] < dep[top[u]])
swap(v, u);
modify(num[top[v]], num[v], z, 1, n, 1);
v = father[top[v]];
}
if (num[v] > num[u])
swap(v, u);
modify(num[v], num[u], z, 1, n, 1);
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
cin >> n >> q;
for (int i = 2, f; i <= n; i++)
cin >> f, G[f].push_back(i);
dfs1(1, 0);
dfs2(1, 1);
build(1, n, 1);
while (q--)
{
int a, b, c, x;
cin >> a >> b >> c;
while (a--)
cin >> x, pathModify(1, x, 1);
while (b--)
cin >> x, pathModify(1, x, 2);
while (c--)
cin >> x, modify(num[x], bottom[x], 4, 1, n, 1);
cout << sum[1] << "\n";
clr[1] = 1, tag[1] = sum[1] = 0, cnt[1][0] = n;
for (int i = 1; i < 8; i++)
cnt[1][i] = 0;
}
for (int i = 1; i <= n; i++)
G[i].clear();
dfn = 0;
}
return 0;
}
B. C++ to Python
模拟
只输出数字、括号、逗号、负号即可。
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
string s;
cin >> s;
for (auto c : s)
if (isdigit(c) || c == '(' || c == ')' || c == '-' || c == ',')
cout << c;
cout << endl;
}
return 0;
}
C. Copy
暴力
由于杭电特性,限制了数据总和又只能上传一个测试点文件,导致暴力可过。暴力做法如下,对每次询问,倒序遍历所有复制操作 [ l , r ] [l,r] [l,r],如果 x ≤ r x \leq r x≤r,那么实际上没有被这次操作影响,否则查询的位置是 x − ( r − l + 1 ) x-(r-l+1) x−(r−l+1),复杂度 O ( N 2 ) O(N^2) O(N2)。
注意到答案询问异或和,如果知道所有
N
N
N 个数中哪些被询问奇数次,最后遍历一次即可求出答案。
正解考虑优化暴力,离线所有询问和操作,倒序处理。对于一个询问,它还是需要考虑之前的所有操作,但是我们只关心最终哪些数被询问奇数次,所以我们可以同时处理一堆询问。用
b
s
[
x
]
bs[x]
bs[x] 维护
x
x
x 是否被询问奇数次,遇到询问直接令
b
s
[
x
]
⊕
1
bs[x] \oplus 1
bs[x]⊕1;遇到操作
[
l
,
r
]
[l,r]
[l,r],让所有满足
x
>
r
x>r
x>r 的位置都往前移动
r
−
l
+
1
r-l+1
r−l+1,这时会有一些询问重叠(
b
s
[
x
]
⊕
b
s
[
x
−
(
r
−
l
+
1
)
]
=
0
bs[x] \oplus bs[x-(r-l+1)]=0
bs[x]⊕bs[x−(r−l+1)]=0),则它们变为出现偶数次,其余的就是出现奇数次。整个过程可以用bitset优化移位和异或,复杂度
O
(
N
2
w
)
O(\cfrac{N^2}{w})
O(wN2)。
#include <bits/stdc++.h>
using namespace std;
constexpr int MAXN = 1e5 + 5;
int a[MAXN];
bitset<MAXN> bs, all;
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
int n, q;
cin >> n >> q;
for (int i = 1; i <= n; i++)
cin >> a[i], all[i] = 1;
vector<tuple<int, int, int>> qry(q);
for (auto &[op, l, r] : qry)
{
cin >> op >> l;
if (op == 1)
cin >> r;
}
for (int i = q - 1; i >= 0; i--)
{
auto [op, l, r] = qry[i];
if (op == 2)
bs.flip(l);
else
{
auto low = bs & (all >> (n - r)), high = bs & (all << r);
bs = low ^ (high >> (r - l + 1));
}
}
int ans = 0;
for (int i = 1; i <= n; i++)
if (bs[i])
ans ^= a[i];
cout << ans << "\n";
bs.reset(), all.reset();
}
return 0;
}
D. Keychains
计算几何
待补
E. Slayers Come
DP
、数据结构
每个技能可以杀死一个区间的怪物,求有多少种方案让所有怪至少死一次。本题只有两个关键,一是求出所有技能对应的区间,二是求方案数。
以求区间右端点为例,每当
a
[
j
]
−
b
[
j
+
1
]
≥
R
[
i
]
a[j]-b[j+1] \geq R[i]
a[j]−b[j+1]≥R[i] 时,往右扩展一个怪物,注意到对于
R
[
i
]
≥
R
[
i
′
]
R[i] \geq R[i']
R[i]≥R[i′],一定有右端点
r
[
i
]
≤
r
[
i
′
]
r[i] \leq r[i']
r[i]≤r[i′],即
i
i
i 能杀的怪,
i
′
i'
i′ 也一定能杀。那么,只要将技能按
R
R
R 从大到小排序,使用并查集优化扩展过程即可,如果更小的
R
R
R 找到了一个区间,可以直接跳到区间右端点再继续扩展。求左端点同理。当然,由于这个问题相当于在
[
x
,
n
]
[x,n]
[x,n] 上找第一个
a
[
p
]
−
b
[
p
+
1
]
<
R
[
i
]
a[p]-b[p+1]<R[i]
a[p]−b[p+1]<R[i],也可以用线段树二分之类的办法做到,无脑但是麻烦。
求方案数是一个区间重复覆盖问题。考虑DP,令
d
p
[
i
]
dp[i]
dp[i] 表示恰好覆盖区间
[
1
,
i
]
[1,i]
[1,i] 的方案数,区间按右端点从小到大排序。对于当前区间
[
l
,
r
]
[l,r]
[l,r],有
d
p
[
r
]
+
=
∑
i
=
l
−
1
r
d
p
[
i
]
dp[r]+=\sum_{i=l-1}^rdp[i]
dp[r]+=∑i=l−1rdp[i],因为这个新区间可选可不选,对所有
0
≤
i
≤
l
−
2
0 \leq i \leq l-2
0≤i≤l−2 有
d
p
[
i
]
∗
=
2
dp[i]*=2
dp[i]∗=2。因为需要区间操作,可以用线段树维护。
#include <bits/stdc++.h>
using namespace std;
#define ls rt << 1
#define rs rt << 1 | 1
constexpr int64_t MAXN = 1e5 + 10, mod = 998244353;
int a[MAXN], b[MAXN], dsu[MAXN];
int64_t sum[MAXN << 2], tag[MAXN << 2];
struct Info
{
int x, L, R, l, r;
} s[MAXN];
int find(int x) { return x == dsu[x] ? x : dsu[x] = find(dsu[x]); }
void pushUp(int rt) { sum[rt] = (sum[ls] + sum[rs]) % mod; }
void pushDown(int rt)
{
if (tag[rt] != 1)
{
tag[ls] = tag[ls] * tag[rt] % mod, tag[rs] = tag[rs] * tag[rt] % mod;
sum[ls] = sum[ls] * tag[rt] % mod, sum[rs] = sum[rs] * tag[rt] % mod;
tag[rt] = 1;
}
}
void add(int L, int64_t C, int l, int r, int rt)
{
if (l == r)
{
sum[rt] = (sum[rt] + C) % mod;
return;
}
int mid = (l + r) >> 1;
pushDown(rt);
if (L <= mid)
add(L, C, l, mid, ls);
else
add(L, C, mid + 1, r, rs);
pushUp(rt);
}
void mul(int L, int R, int l, int r, int rt)
{
if (L <= l && r <= R)
{
sum[rt] = sum[rt] * 2 % mod;
tag[rt] = tag[rt] * 2 % mod;
return;
}
int mid = (l + r) >> 1;
pushDown(rt);
if (L <= mid)
mul(L, R, l, mid, ls);
if (R > mid)
mul(L, R, mid + 1, r, rs);
pushUp(rt);
}
int64_t query(int L, int R, int l, int r, int rt)
{
if (L <= l && r <= R)
return sum[rt];
int mid = (l + r) >> 1;
pushDown(rt);
int64_t ans = 0;
if (L <= mid)
ans = (ans + query(L, R, l, mid, ls)) % mod;
if (R > mid)
ans = (ans + query(L, R, mid + 1, r, rs)) % mod;
return ans;
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i] >> b[i];
for (int i = 1; i <= m; i++)
cin >> s[i].x >> s[i].L >> s[i].R;
sort(s + 1, s + m + 1, [](const Info &lhs, const Info &rhs)
{ return lhs.R > rhs.R; });
iota(dsu + 1, dsu + n + 1, 1);
for (int i = 1; i <= m; i++)
{
int r = find(s[i].x);
while (r < n && a[r] - b[r + 1] >= s[i].R)
dsu[r] = find(r + 1), r = dsu[r];
s[i].r = r;
}
sort(s + 1, s + m + 1, [](const Info &lhs, const Info &rhs)
{ return lhs.L > rhs.L; });
iota(dsu + 1, dsu + n + 1, 1);
for (int i = 1; i <= m; i++)
{
int l = find(s[i].x);
while (l > 1 && a[l] - b[l - 1] >= s[i].L)
dsu[l] = find(l - 1), l = dsu[l];
s[i].l = l;
}
sort(s + 1, s + m + 1, [](const Info &lhs, const Info &rhs)
{ return lhs.r < rhs.r; });
fill(sum + 1, sum + n * 2 + 5, 0);
fill(tag + 1, tag + n * 2 + 5, 0);
add(0, 1, 0, n, 1); // dp[0] = 1;
for (int i = 1; i <= m; i++)
{
int l = s[i].l, r = s[i].r;
add(r, query(l - 1, r, 0, n, 1), 0, n, 1);
if (l >= 2)
mul(0, l - 2, 0, n, 1);
}
cout << query(n, n, 0, n, 1) << "\n";
}
return 0;
}
F. Bowcraft
DP
、数学
过难
G. Snatch Groceries
排序
题面巨长,聪明人只从倒数第三段开始读,题意实际上就是问从左到右,第一次与后面区间重叠(端点也算)的区间的下标(从0开始),如果没有,答案是 n n n。显然排序一下比较端点就行了。
#include <bits/stdc++.h>
using namespace std;
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
int n;
cin >> n;
vector<pair<int, int>> vec(n);
for (auto &[l, r] : vec)
cin >> l >> r;
stable_sort(vec.begin(), vec.end()); // 用sort会莫名其妙不让提交
int ans = n;
for (int i = 0; i < n - 1; i++)
{
if (vec[i].second >= vec[i + 1].first)
{
ans = i;
break;
}
}
cout << ans << endl;
}
return 0;
}
H. Keyboard Warrior
字符串
、数据结构
询问是否存在一个时刻,目标串是当前串的一个子串。可以在每次两串结尾字符相同时检查一次,通过哈希完成。因为当前串很长,考虑用一个栈压缩存储相同字符。问题主要在于怎么维护当前串的哈希值,如果使用平常的哈希函数,即
h
=
(
∑
i
=
0
n
s
[
i
]
∗
b
a
s
e
n
−
i
)
%
m
o
d
h=(\sum_{i=0}^{n}s[i]*base^{n-i})\%mod
h=(∑i=0ns[i]∗basen−i)%mod,由于是压缩字符串,计算起来还是比较麻烦的。
考虑随机权值,
v
a
l
[
c
h
]
=
r
a
n
d
(
)
val[ch]=rand()
val[ch]=rand(),
h
=
(
∑
i
=
0
n
v
a
l
[
s
[
i
]
]
)
%
m
o
d
h=(\sum_{i=0}^{n}val[s[i]])\%mod
h=(∑i=0nval[s[i]])%mod,显然算当前串哈希值很方便,并且实际上足以避免冲突(如果不放心,本题时限甚至足以四哈希)。于是直接模拟操作,一边维护当前串的字符栈、长度、长度前缀和、哈希前缀和,一边在尾字符相同时检查。检查时可以单独考虑首尾,这里会遇到一些细节。
#include <bits/stdc++.h>
using namespace std;
constexpr int MAXN = 2e6 + 5, mod = 1e9 + 7;
mt19937 mt(chrono::system_clock::now().time_since_epoch().count());
int64_t h[MAXN], len[MAXN], lensum[MAXN], val[200];
char st[MAXN];
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
for (int i = 'a'; i <= 'z'; i++)
val[i] = mt() % mod;
for (int i = '0'; i <= '9'; i++)
val[i] = mt() % mod;
int t;
cin >> t;
while (t--)
{
int n, m, ok = 0;
string s;
cin >> n >> m >> s;
int cntl = 0, cntr = 0;
for (int i = 0; i < n && s[i] == s[0]; i++)
++cntl;
for (int i = n - 1; i >= 0 && s[i] == s.back(); i--)
++cntr;
int64_t h0 = 0;
for (int i = cntl; i < n - cntr; i++)
h0 = (h0 + val[s[i]]) % mod;
int cur = 0;
while (m--)
{
char ch;
int k;
cin >> ch >> k;
if (k == 0)
continue;
if (ch == '-')
{
if (cur == 0 || k >= lensum[cur])
{
cur = 0;
continue;
}
int l = lower_bound(lensum + 1, lensum + cur + 1, lensum[cur] - k) - lensum;
auto del = lensum[l] - (lensum[cur] - k);
cur = l, lensum[cur] -= del, len[cur] -= del;
}
else if (cur == 0 || st[cur] != ch)
{
st[++cur] = ch;
len[cur] = k, lensum[cur] = lensum[cur - 1] + k;
}
else
{
len[cur] += k, lensum[cur] += k;
}
h[cur] = (h[cur - 1] + val[st[cur]] * len[cur] % mod) % mod;
if (st[cur] == s.back() && lensum[cur] >= n && len[cur] >= cntr)
{
int l = upper_bound(lensum + 1, lensum + cur + 1, lensum[cur - 1] - (n - cntr)) - lensum;
if (len[l] < cntl)
continue;
if (l == cur && cntl == n)
ok = 1;
else if (h0 == (h[cur - 1] - h[l] + mod) % mod)
ok = 1;
}
}
cout << (ok ? "yes\n" : "no\n");
}
return 0;
}
I. ShuanQ
数学
注意到 p × q ≡ 1 ( m o d M ) p \times q \equiv 1 \space (mod \space M) p×q≡1 (mod M),即 p × q − 1 = k m p \times q-1=km p×q−1=km,又 M M M 是质数, M > m a x ( p , q ) M > max(p,q) M>max(p,q),所以 M M M 是 p × q − 1 p \times q-1 p×q−1 的一个大于 m a x ( p , q ) max(p,q) max(p,q) 的质因数。如果能找到,那么一定有且仅有一个(因为 M > m a x ( p , q ) M>max(p,q) M>max(p,q)),否则无解。
#include <bits/stdc++.h>
using namespace std;
map<int64_t, int> getPrimeFactor(int64_t a)
{
map<int64_t, int> mp;
for (int64_t i = 2; i * i <= a; i++)
{
if (a <= 1)
break;
while (a % i == 0)
a /= i, ++mp[i];
}
if (a > 1)
mp[a]++;
return mp;
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
int p, q, d;
int64_t ans = -1;
cin >> p >> q >> d;
auto mp = getPrimeFactor(1ll * p * q - 1);
for (auto [v, _] : mp)
if (v > max(p, q))
ans = 1ll * d * q % v;
if (ans != -1)
cout << ans << "\n";
else
cout << "shuanQ\n";
}
return 0;
}
J. Assassination
图论
要求删除最少的边,使得新图中不存在原图的任何一棵最大生成树。显然有两种情况,一是整张图不连通,二是最大生成树权值减小。
我们考虑kruskal求生成树的过程,假设已处理了所有
>
w
>w
>w 的边,根据生成树理论,此时图中并查集连通性唯一确定。现在要加入权值
=
w
=w
=w 的边,如果破坏即将构造的连通性,要么即使加入后续边也无法连通整张图,要么加入后续边后生成树权值减小。
因此将当前所有连通块缩点,所有权值
=
w
=w
=w 的边连接这些点(连接连通块内部点的边不用考虑),对这个图跑最小割,答案对所有
w
w
w 对应的最小割取最小值即可。
#include <bits/stdc++.h>
using namespace std;
constexpr int MAXN = 1e5 + 5, MAXM = 2e6 + 5;
set<int, greater<int>> sw;
vector<pair<int, int>> edge[MAXN];
int n, m, s = 1e5 + 1, t = 1e5 + 2, maxflow, d[MAXN], fa[MAXN];
int head[MAXN], tot = 1;
struct Edge
{
int to, next, w;
} e[MAXM];
void add(int u, int v, int w)
{
e[++tot].to = v, e[tot].w = w, e[tot].next = head[u], head[u] = tot;
e[++tot].to = u, e[tot].w = 0, e[tot].next = head[v], head[v] = tot;
}
bool bfs(const vector<int> &ver)
{
for (auto v : ver)
d[v] = 0;
d[s] = 1, d[t] = 0;
queue<int> q;
q.push(s);
while (!q.empty())
{
int v = q.front();
q.pop();
for (int i = head[v]; i; i = e[i].next)
{
int u = e[i].to, w = e[i].w;
if (w && !d[u])
{
q.push(u);
d[u] = d[v] + 1;
if (u == t)
return 1;
}
}
}
return 0;
}
int dinic(int v, int flow)
{
if (v == t)
return flow;
int rest = flow;
for (int i = head[v]; i && rest; i = e[i].next)
{
int u = e[i].to, w = e[i].w;
if (w && d[u] == d[v] + 1)
{
int k = dinic(u, min(rest, w));
if (!k)
d[u] = 0;
else
e[i].w -= k, e[i ^ 1].w += k, rest -= k;
}
}
return flow - rest;
}
int findfa(int x) { return x == fa[x] ? x : fa[x] = findfa(fa[x]); }
int kruskal()
{
int ans = 1e9;
iota(fa + 1, fa + n + 1, 1);
fill(head + 1, head + n + 1, 0), tot = 1;
for (auto w : sw)
{
vector<int> ver;
for (auto [v, u] : edge[w])
{
int fav = findfa(v), fau = findfa(u);
if (fav != fau)
add(fav, fau, 1), add(fau, fav, 1), ver.push_back(fav), ver.push_back(fau);
}
if (ver.empty())
continue;
int flow = 0;
add(s, ver[0], 1e9), add(ver[1], t, 1e9);
while (bfs(ver))
while (flow = dinic(s, 1e9))
maxflow += flow;
ans = min(ans, maxflow), maxflow = 0;
for (auto [v, u] : edge[w])
fa[findfa(v)] = fa[findfa(u)];
tot = 1;
for (auto v : ver)
head[v] = 0;
head[s] = head[t] = 0;
}
return ans;
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--)
{
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int v, u, w;
cin >> v >> u >> w;
edge[w].emplace_back(v, u), sw.insert(w);
}
cout << kruskal() << "\n";
for (auto w : sw)
edge[w].clear();
sw.clear();
}
return 0;
}
K. DOS Card
数据结构
一眼线段树上维护区间信息求解,但是并不知道维护什么。注意到题目实际上是要求找出两对互不相同的 i < j i<j i<j ,让 a [ i ] 2 − a [ j ] 2 a[i]^2-a[j]^2 a[i]2−a[j]2 之和最大。变成如上形式后无需考虑配对,只有两种情况, + + − − ++-- ++−− 和 + − + − +-+- +−+−,于是可以用线段树区间合并维护所有信息最大值。
设 p p p (plus) 代表 + + + , s s s (sub) 代表 − - −,维护方法如下:
- p p p, s s s 单点已知,区间取最值
- p p = m a x ( l . p p , r . p p , l . p + r . p ) pp=max(l.pp,r.pp,l.p+r.p) pp=max(l.pp,r.pp,l.p+r.p), p s = m a x ( l . p s , r . p s , l . p + r . s ) ps=max(l.ps,r.ps,l.p+r.s) ps=max(l.ps,r.ps,l.p+r.s), s p = m a x ( l . s p , r . s p , l . s + r . p ) sp=max(l.sp,r.sp,l.s+r.p) sp=max(l.sp,r.sp,l.s+r.p), s s = m a x ( l . s s , r . s s , l . s + r . s ) ss=max(l.ss,r.ss,l.s+r.s) ss=max(l.ss,r.ss,l.s+r.s)
- p p s = m a x ( l . p p s , r . p p s , l . p p + r . s , l . p + r . p s ) pps=max(l.pps,r.pps,l.pp+r.s,l.p+r.ps) pps=max(l.pps,r.pps,l.pp+r.s,l.p+r.ps), p s s = m a x ( l . p s s , r . p s s , l . p s + r . s , l . p + r . s s ) pss=max(l.pss,r.pss,l.ps+r.s,l.p+r.ss) pss=max(l.pss,r.pss,l.ps+r.s,l.p+r.ss), p s p = m a x ( l . p s p , r . p s p , l . p s + r . p , l . p + r . s p ) psp=max(l.psp,r.psp,l.ps+r.p,l.p+r.sp) psp=max(l.psp,r.psp,l.ps+r.p,l.p+r.sp), s p s = m a x ( l . s p s , r . s p s , l . s p + r . s , l . s + r . p s ) sps=max(l.sps,r.sps,l.sp+r.s,l.s+r.ps) sps=max(l.sps,r.sps,l.sp+r.s,l.s+r.ps)
- p p s s = m a x ( l . p p s s , r . p p s s , l . p p s + r . s , l . p p + r . s s , l . p + r . p s s ) ppss=max(l.ppss,r.ppss,l.pps+r.s,l.pp+r.ss,l.p+r.pss) ppss=max(l.ppss,r.ppss,l.pps+r.s,l.pp+r.ss,l.p+r.pss), p s p s = m a x ( l . p s p s , r . p s p s , l . p s p + r . s , l . p s + r . p s , l . p + r . s p s ) psps=max(l.psps,r.psps,l.psp+r.s,l.ps+r.ps,l.p+r.sps) psps=max(l.psps,r.psps,l.psp+r.s,l.ps+r.ps,l.p+r.sps)
#include <bits/stdc++.h>
using namespace std;
constexpr int MAXN = 2e5 + 5;
struct Node
{
int64_t p, s, pp, ps, sp, ss, pps, pss, psp, sps, ppss, psps;
Node() { p = s = pp = ps = sp = ss = pps = pss = psp = sps = ppss = psps = -1e18; }
} tr[MAXN << 2];
int64_t a[MAXN];
void pushUp(Node &rt, const Node &l, const Node &r)
{
rt.p = max(l.p, r.p), rt.s = max(l.s, r.s);
rt.pp = max({l.pp, r.pp, l.p + r.p}), rt.ps = max({l.ps, r.ps, l.p + r.s}),
rt.sp = max({l.sp, r.sp, l.s + r.p}), rt.ss = max({l.ss, r.ss, l.s + r.s});
rt.pps = max({l.pps, r.pps, l.pp + r.s, l.p + r.ps}), rt.pss = max({l.pss, r.pss, l.ps + r.s, l.p + r.ss}),
rt.psp = max({l.psp, r.psp, l.ps + r.p, l.p + r.sp}), rt.sps = max({l.sps, r.sps, l.sp + r.s, l.s + r.ps});
rt.ppss = max({l.ppss, r.ppss, l.pps + r.s, l.pp + r.ss, l.p + r.pss}),
rt.psps = max({l.psps, r.psps, l.psp + r.s, l.ps + r.ps, l.p + r.sps});
}
void build(int l, int r, int rt)
{
if (l == r)
{
tr[rt] = Node();
tr[rt].p = a[l], tr[rt].s = -a[l];
return;
}
int mid = (l + r) >> 1;
build(l, mid, rt << 1);
build(mid + 1, r, rt << 1 | 1);
pushUp(tr[rt], tr[rt << 1], tr[rt << 1 | 1]);
}
Node query(int L, int R, int l, int r, int rt)
{
if (L <= l && r <= R)
return tr[rt];
int mid = (l + r) >> 1;
if (R <= mid)
return query(L, R, l, mid, rt << 1);
else if (L > mid)
return query(L, R, mid + 1, r, rt << 1 | 1);
else
{
Node res;
pushUp(res, query(L, R, l, mid, rt << 1), query(L, R, mid + 1, r, rt << 1 | 1));
return res;
}
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int t;
cin >> t;
while (t--)
{
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i], a[i] *= a[i];
build(1, n, 1);
while (m--)
{
int l, r;
cin >> l >> r;
auto res = query(l, r, 1, n, 1);
cout << max(res.ppss, res.psps) << "\n";
}
}
return 0;
}
L. Luxury cruise ship
贪心
、暴力
、DP
显然在 n n n 很大的时候,一直用365填充最优。当达到某个值的时候,开始跑完全背包,不妨设定为 1 e 5 1e5 1e5,反正不会T。
#include <bits/stdc++.h>
using namespace std;
int dp[100500], w[3] = {7, 31, 365};
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
memset(dp, 0x3f, sizeof(dp));
dp[0] = 0;
for (int i = 0; i < 3; i++)
for (int j = w[i]; j < 100500; j++)
dp[j] = min(dp[j], dp[j - w[i]] + 1);
int t;
cin >> t;
while (t--)
{
int64_t n, res = 0;
cin >> n;
if (n >= 100000)
res += (n - 100000) / 365, n -= (n - 100000) / 365 * 365;
cout << (dp[n] == 0x3f3f3f3f ? -1 : res + dp[n]) << "\n";
}
return 0;
}