Happy Card 70pts
大样例乱搞都能过。。。
可以将“炸”看成“三带一”,那么我们最优是先出“三带一”;
首先分别算出原序列中每个数包含 $ 3 $ 的个数 $ cnt $ ,以及模 $ 3 $ 余 $ 1, 2 $ 的个数 $ s1, s2 $ ,然后进行判断, 如果 $ cnt \geq s1 + 2s2 $,那么我们可以看做将原序列所有数四个四个的出,看最后剩下几个,如果剩下 $ 1, 2 $ 个就再出一次,剩 $ 3 $ 个就再出两次;
如果 $ cnt < s1 + 2s2 $ ,那么我们优先带 $ 1 $ , 然后再带 $ 2 $ 即可;
时间复杂度: $ \Theta(n) $ , 瓶颈在输入;
点击查看代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
int t;
int n;
long long a[500005];
long long s1, s2, sum, cnt, ans;
int main() {
// freopen("in.in", "r", stdin);
// freopen("out.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> t;
while(t--) {
cin >> n;
ans = 0;
s1 = s2 = sum = cnt = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
if (a[i] % 3 == 1) s1++;
if (a[i] % 3 == 2) s2++;
cnt += (a[i] / 3);
sum += a[i];
}
if (cnt >= s1 + 2 * s2) {
ans = sum / 4;
if (sum % 4 == 1) ans++;
else if (sum % 4 == 2) ans++;
else if (sum % 4 == 3) ans += 2;
cout << ans << '\n';
} else {
ans = cnt;
long long s = s1;
s1 -= min(s, ans);
cnt -= min(s, ans);
s2 -= min(s2 * 2, cnt) / 2;
cout << ans + s1 + s2 << '\n';
}
}
return 0;
}
Speaker 100pts
考虑我们要求的这个点的位置有几种,有如下两种:
-
在这条链上以及这条链的子树内;
-
在 $ LCA $ 到根的这条链以及它们的子树内;
考虑对这两种情况分别求解,首先一个点 $ y $ 对点 $ x $ 的贡献为 $ a_y - 2dis(x, y) $,所以依据这个我们可以进行DP, 设 $ b_x $ 表示考虑以 $ x $ 为根的子树中到 $ x $ 的最大贡献是多少,有转移 $ b_{x} = \max(b_x, b_u - 2w) $,其中 $ w $ 为边权, $ u $ 为 $ x $ 的一个儿子,初始化 $ b_x = a_x $;
首先考虑如何处理 $ 1 $ ,求一条链上的最值很容易想到倍增,设 $ f_{x, i} $ 表示从 $ x $ 向上跳 $ 2^i $ 跳到哪里, $ g_{x, i} $ 表示从 $ x $ 向上跳 $ 2^i \ b $ 数组的最大值,直接转移即可;
对于 $ 2 $ , 我们类比上面的处理方法,设 $ w_{x, i} $ 表示从 $ x $ 开始向上 $ 2^i $ 的所有点以及它们的子树中的点到 $ i $ 的最大贡献,有转移 $ w_{x, i} = \max(w_{x, i - 1}, w_{f_{x, i - 1}, i - 1} - 2dis(x, f_{x, i - 1})) $;
注意上面我们并不用刻意的处理重复的子树,因为如果选中的点在重复的子树中,那么它会在深度最深的地方被选,在当前点被选已经没有什么意义了;
然后直接倍增处理询问即可;
时空复杂度: $ \Theta(n \log n) $;
点击查看代码
#include <iostream>
#include <cstdio>
using namespace std;
int n, q;
long long a[500005];
struct sss{
int t, ne;
long long w;
}e[500005];
int h[500005], cnt;
inline void add(int u, int v, long long ww) {
e[++cnt].t = v;
e[cnt].ne = h[u];
h[u] = cnt;
e[cnt].w = ww;
}
int f[200005][21], dep[200005];
long long w[200005][21], g[200005][21], dis[200005], b[200005];
void afs(int x, int fa) {
b[x] = a[x];
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == fa) continue;
afs(u, x);
b[x] = max(b[x], b[u] - 2 * e[i].w);
}
}
void dfs(int x, int fa) {
dep[x] = dep[fa] + 1;
f[x][0] = fa;
g[x][0] = max(b[x], b[fa]);
for (int i = h[x]; i; i = e[i].ne) {
int u = e[i].t;
if (u == fa) continue;
w[u][0] = b[x] - 2 * e[i].w;
dis[u] = dis[x] + e[i].w;
dfs(u, x);
}
}
inline int lca(int x, int y) {
if (x == y) return x;
if (dep[x] < dep[y]) swap(x, y);
for (int i = 19; i >= 0; i--) {
if (dep[f[x][i]] >= dep[y]) x = f[x][i];
}
if (x == y) return x;
for (int i = 19; i >= 0; i--) {
if (f[x][i] != f[y][i]) {
x = f[x][i];
y = f[y][i];
}
}
return f[x][0];
}
inline long long di(int x, int y, int lc) {
return dis[x] + dis[y] - 2 * dis[lc];
}
inline long long W(int x, int y, int lc) {
long long ma = -1e18;
int xx = x, yy = y;
for (int i = 19; i >= 0; i--) {
if (dep[f[x][i]] >= dep[lc]) {
ma = max(ma, g[x][i]);
x = f[x][i];
}
}
for (int i = 19; i >= 0; i--) {
if (dep[f[y][i]] >= dep[lc]) {
ma = max(ma, g[y][i]);
y = f[y][i];
}
}
if (xx == yy) ma = b[xx];
int now = lc;
for (int i = 19; i >= 0; i--) {
if (dep[f[lc][i]] >= dep[1]) {
ma = max(ma, w[lc][i] - 2 * di(lc, now, lc));
lc = f[lc][i];
}
}
return ma;
}
int main() {
// freopen("in.in", "r", stdin);
// freopen("out.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> q;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int x, y;
long long ww;
for (int i = 1; i <= n - 1; i++) {
cin >> x >> y;
cin >> ww;
add(x, y, ww);
add(y, x, ww);
}
a[0] = -1e18;
b[0] = -1e18;
afs(1, 0);
dfs(1, 0);
for (int j = 1; j <= 19; j++) {
for (int i = 1; i <= n; i++) {
f[i][j] = f[f[i][j - 1]][j - 1];
g[i][j] = max(g[i][j - 1], g[f[i][j - 1]][j - 1]);
w[i][j] = max(w[i][j - 1], w[f[i][j - 1]][j - 1] - 2 * di(i, f[i][j - 1], f[i][j - 1]));
}
}
long long ans = 0;
for (int i = 1; i <= q; i++) {
cin >> x >> y;
int lc = lca(x, y);
ans = a[x] + a[y] - di(x, y, lc) + W(x, y, lc);
cout << ans << '\n';
}
return 0;
}
Monotonic Queue 4pts
首先考虑转化题意,我们发现,我们可以将序列划分成若干个单调递减的子序列,然后只有一个子序列的开头能够弹出另一个子序列的末尾的若干个元素,这个子序列的划分可以用单调栈预处理;
考虑我们的贡献会出现在子序列的开头,剩下的数弹不出其它数没有意义,发现这个性质后我们就可以发现,这个贡献是前一段子序列的一个后缀和;
考虑如果没有 pop_front
操作,那么这个贡献是确定的,我们直接从前一个子序列找到对应的后缀和即可,但是此时有这个操作,其实就是找前面的子序列中有贡献的那些数的最大后缀和,然后和前面的贡献合并(取 $ \max $),这个可以上线段树前缀加区间查询做;
具体地,考虑设 $ f_i $ 表示只考虑前 $ i $ 位的答案,有转移 $ f_i = \max(f_{i - 1}, f_{i - 1} + \max_{l = pos_i}^{i - 1} \sum_{j = l}^{i - 1} a_j \times vis_j) $, 其中 $ pos_i $ 表示 $ a_i $ 前面第一个比它大的数, $ vis_j $ 表示 $ j $ 对于当前的 $ i $ 有没有贡献(就是 $ j $ 是否在 $ i $ 前面的子序列中且是否有 $ a_j < a_i $),后面这个式子(就是最大后缀和)可以线段树维护,和上面相同,只不过需要动态插入 $ f_i $ 以达到状态转移的效果;
时间复杂度: $ \Theta(n \log n) $;
点击查看代码
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
int n;
long long c[500005], a[500005];
namespace SEG{
inline int ls(int x) {
return x << 1;
}
inline int rs(int x) {
return x << 1 | 1;
}
struct sss{
int l, r;
long long ma, lz;
}tr[3000005];
inline void push_down(int id) {
if (tr[id].lz != 0) {
tr[ls(id)].lz += tr[id].lz;
tr[rs(id)].lz += tr[id].lz;
tr[ls(id)].ma += tr[id].lz;
tr[rs(id)].ma += tr[id].lz;
tr[id].lz = 0;
}
}
inline void push_up(int id) {
tr[id].ma = max(tr[ls(id)].ma, tr[rs(id)].ma);
}
void bt(int id, int l, int r) {
tr[id].l = l;
tr[id].r = r;
if (l == r) return;
int mid = (l + r) >> 1;
bt(ls(id), l, mid);
bt(rs(id), mid + 1, r);
}
void add(int id, int l, int r, long long d) {
if (tr[id].l >= l && tr[id].r <= r) {
tr[id].ma += d;
tr[id].lz += d;
return;
}
push_down(id);
int mid = (tr[id].l + tr[id].r) >> 1;
if (l <= mid) add(ls(id), l, r, d);
if (r > mid) add(rs(id), l, r, d);
push_up(id);
}
long long ask(int id, int l, int r) {
if (tr[id].l >= l && tr[id].r <= r) return tr[id].ma;
push_down(id);
int mid = (tr[id].l + tr[id].r) >> 1;
if (r <= mid) return ask(ls(id), l, r);
else if (l > mid) return ask(rs(id), l, r);
else return max(ask(ls(id), l, mid), ask(rs(id), mid + 1, r));
}
}
int s[500005], cnt;
vector<int> v[500005];
int main() {
// freopen("queue6.in", "r", stdin);
// freopen("out.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> c[i];
}
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
SEG::bt(1, 1, n);
for (int i = 1; i <= n; i++) {
while(cnt > 0 && a[s[cnt]] < a[i]) v[i].push_back(s[cnt--]);
s[++cnt] = i;
}
long long ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = 0; j < v[i].size(); j++) SEG::add(1, 1, v[i][j], c[v[i][j]]);
if (i == 1) continue;
long long f = SEG::ask(1, 1, i - 1);
ans = max(ans, f);
SEG::add(1, i, i, f);
}
cout << ans;
return 0;
}
邻间的骰子之舞 45pts
考虑将一个复制后会跟着若干个粘贴,所以将赋值粘贴一起考虑,那么我们设这一次粘贴了 $ m $ 次,那么就是对原序列乘了一个 $ m + 1 $;
考虑设进行了 $ k $ 次操作( $ \Theta \log n $ 级别 ),那么我们的贡献为 $ \prod_i (m_i + 1) $,代价为 $ kx + y \sum_i m_i $,于是我们想在 $ \prod (m_i + 1) > n $ 的前提下使代价最小;
引理:$ m $ 数组中的数相差不超过 $ 1 $;
考虑证明,根据均值不等式,可以发现积定相等时和最小,但是都是整数,所以最多差 $ 1 $;
首先我们可以枚举 $ k $,然后考虑枚举较小的那个 $ m $ 的数量,然后二分出一个较小的 $ m $,最后直接循环判断一下是否满足 $ \prod (m_i + 1) > n $ 即可;
时间复杂度:$ \Theta(\log^4 n) $;
点击查看代码
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
using namespace std;
long long n, x, y;
long long ma;
unsigned __int128 ans;
void out(unsigned __int128 x) {
if (x == 0) return;
out(x / 10);
cout << (int)(x % 10);
}
int main() {
freopen("dice.in", "r", stdin);
freopen("dice.out", "w", stdout);
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> x >> y;
ma = log2(n) + 1;
ans = 1e36;
for (int k = 1; k <= ma; k++) {
for (int p = 0; p <= k; p++) {
long long l = 1;
long long r = n;
unsigned __int128 sum = 1;
long long t = n + 1;
while(l <= r) {
long long mid = (l + r) >> 1;
sum = 1;
for (int i = 1; i <= p; i++) {
if (sum > n) break;
sum *= mid;
}
for (int i = 1; i <= k - p; i++) {
if (sum > n) break;
sum *= (mid + 1);
}
if (sum > n) {
t = mid;
r = mid - 1;
} else l = mid + 1;
}
sum = 0;
for (int i = 1; i <= p; i++) sum += t;
for (int i = 1; i <= k - p; i++) sum += (t + 1);
unsigned __int128 an = (x - y) * k + y * sum;
ans = min(ans, an);
}
}
if (ans == 0) cout << 0;
else out(ans);
return 0;
}
星海浮沉录 100pts
比较简单的数据结构题,不写了;
标签:cnt,赛记,lc,NOIP,int,sum,long,id,模拟 From: https://www.cnblogs.com/PeppaEvenPig/p/18567115