20250906
T1
推倒骨牌
多维护几个东西就能直接倍增了。不要开 long long。
代码
#include <iostream>
#include <algorithm>
#include <string.h>
#define lowbit(x) ((x) & (-(x)))
using namespace std;
int n, q;
struct BIT {
pair<int, int> bit[500005];
void add(int x, int y) { for (int tx = x; x <= n; x += lowbit(x)) bit[x] = max(bit[x], make_pair(y, tx)); }
pair<int, int> query(int x) {
pair<int, int> ret = { 0, 0 };
for (; x; x -= lowbit(x)) ret = max(ret, bit[x]);
return ret;
}
} bit;
int p[500005], l[500005];
int f[21][500005][4];
signed main() {
freopen("dominoes.in", "r", stdin);
freopen("dominoes.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = 1; i <= n; i++) cin >> p[i] >> l[i];
for (int i = n; i; i--) {
pair<int, int> tmp = bit.query(upper_bound(p + 1, p + n + 1, p[i] + l[i]) - p - 1);
f[0][i][1] = max(tmp.first, p[i] + l[i]);
bit.add(i, f[0][i][1]);
f[0][i][0] = upper_bound(p + 1, p + n + 1, f[0][i][1]) - p - 1;
f[0][n + 1][1] = max(f[0][n + 1][1], p[i] + l[i]);
f[0][i][3] = bit.query(upper_bound(p + 1, p + n + 1, f[0][i][1]) - p - 1).second;
}
f[0][n + 1][0] = n;
for (int i = 1; i < 21; i++) {
for (int j = 1; j <= n + 1; j++) {
int *g = f[i - 1][j];
f[i][j][0] = f[i - 1][g[0] + 1][0];
f[i][j][1] = f[i - 1][g[0] + 1][1];
f[i][j][2] = f[i - 1][g[0] + 1][2] + g[2] + max(0, p[g[0] + 1] - g[1]);
f[i][j][3] = f[i - 1][g[0] + 1][3];
}
}
cin >> q;
while (q--) {
int x, y;
cin >> x >> y;
int ans = 0;
for (int i = 20; ~i; i--) {
if (f[i][f[0][x][0] + 1][0] < y)
ans += f[1][x][2] + f[i][f[0][x][0] + 1][2], x = f[i][f[0][x][0] + 1][3];
}
if (f[0][x][0] < y) ans += f[1][x][2];
cout << ans << "\n";
}
return 0;
}
T2
购买装饰品
枚举两个的交选了多少,剩下的只有一个要的选最小的那些,再剩下的再选最小的那些。随便拿个比如说权值线段树维护一下前 \(k\) 小和就好了。
代码
#include <iostream>
#include <algorithm>
#include <string.h>
#include <vector>
#define int long long
using namespace std;
const int inf = 0x3f3f3f3f3f3f3f3f;
int n, m, K, X, Y;
int ad[200005];
int v[200005];
vector<int> vec[4];
int d[200005], dcnt;
struct Segment_Tree {
int s[800005], sv[800005];
void Add(int o, int l, int r, int x, int y) {
if (l == r) return s[o] += y, sv[o] += d[x] * y, void();
int mid = (l + r) >> 1;
if (x <= mid) Add(o << 1, l, mid, x, y);
else Add(o << 1 | 1, mid + 1, r, x, y);
s[o] = s[o << 1] + s[o << 1 | 1];
sv[o] = sv[o << 1] + sv[o << 1 | 1];
}
int Query(int o, int l, int r, int k) {
if (l == r) return s[o] >= k ? d[r] * k : inf;
int mid = (l + r) >> 1;
if (s[o << 1] >= k) return Query(o << 1, l, mid, k);
else return Query(o << 1 | 1, mid + 1, r, k - s[o << 1]) + sv[o << 1];
}
} seg;
signed main() {
freopen("decoration.in", "r", stdin);
freopen("decoration.out", "w", stdout);
int ans = inf;
cin >> n >> m >> K;
for (int i = 1; i <= n; i++) cin >> v[i], d[i] = v[i];
sort(d + 1, d + n + 1), dcnt = unique(d + 1, d + n + 1) - d - 1;
cin >> X; for (int i = 1, x; i <= X; i++) cin >> x, ad[x] |= 2;
cin >> Y; for (int i = 1, x; i <= Y; i++) cin >> x, ad[x] |= 1;
for (int i = 1; i <= n; i++) vec[ad[i]].emplace_back(v[i] = lower_bound(d + 1, d + dcnt + 1, v[i]) - d);
for (int i : { 0, 1, 2, 3 }) sort(vec[i].begin(), vec[i].end());
for (int i = 1; i <= n; i++) seg.Add(1, 1, dcnt, v[i], 1);
int cs = 0;
for (int i = 0; i < (int)vec[3].size(); i++) {
seg.Add(1, 1, dcnt, vec[3][i], -1); cs += d[vec[3][i]];
if (i >= K - 1 && m >= i + 1) ans = min(ans, cs + seg.Query(1, 1, dcnt, m - i - 1));
}
for (int i = 0; i < (int)vec[3].size(); i++) seg.Add(1, 1, dcnt, vec[3][i], 1);
cs = 0;
int cur = min(K, (int)vec[3].size());
for (int i = 0; i < cur; i++) seg.Add(1, 1, dcnt, vec[3][i], -1), cs += d[vec[3][i]];
if ((int)vec[1].size() < K - cur || (int)vec[2].size() < K - cur) return cout << (ans == inf ? -1 : ans) << "\n", 0;
if (cur + (K - cur) * 2 > m) return cout << (ans == inf ? -1 : ans) << "\n", 0;
for (int i = 0; i < K - cur; i++) seg.Add(1, 1, dcnt, vec[1][i], -1), cs += d[vec[1][i]];
for (int i = 0; i < K - cur; i++) seg.Add(1, 1, dcnt, vec[2][i], -1), cs += d[vec[2][i]];
for (int i = cur - 1; i >= -1; i--, cur--) {
if (m - cur - 2 * (K - cur) < 0) break;
ans = min(ans, cs + seg.Query(1, 1, dcnt, m - cur - 2 * (K - cur)));
if (i == -1) break;
if ((int)vec[1].size() < K - i || (int)vec[2].size() < K - i) break;
cs -= d[vec[3][i]], cs += d[vec[1][K - cur]], cs += d[vec[2][K - cur]];
seg.Add(1, 1, dcnt, vec[3][i], 1);
seg.Add(1, 1, dcnt, vec[1][K - cur], -1);
seg.Add(1, 1, dcnt, vec[2][K - cur], -1);
}
cout << ans << "\n";
return 0;
}
T3
耍望节
处理出 \(f_{i, j}\) 表示做完前 \(i - 1\) 位到了 \(j\) 状态的情况下,后面有多少合法方案。如果单次询问只需要从高到低枚举每一位填啥就好了。
多次询问,显然只需要考虑最后一个匹配的中的问号和前面的 18 个问号,再前面的一定都是 0。暴力做完这部分剩下的后缀,其中的问号依次把剩下的 \(k\) 的每一位从高到低填进去即可。
代码
#include <iostream>
#include <algorithm>
#include <string.h>
#include <queue>
#define int long long
using namespace std;
const int inf = 1000000000000000001;
const int P = 1000000007;
int n, m, q, K;
string S, T;
queue<int> Q;
int son[25][10];
void Insert(string str) {
int p = 0;
for (auto v : str) {
int t = v - '0';
if (!son[p][t]) son[p][t] = ++m;
p = son[p][t];
}
}
int fail[25];
void Build() {
for (int i = 0; i < 10; i++) if (son[0][i]) Q.push(son[0][i]);
while (!Q.empty()) {
int x = Q.front();
Q.pop();
for (int i = 0; i < 10; i++) {
if (son[x][i]) Q.push(son[x][i]), fail[son[x][i]] = son[fail[x]][i];
else son[x][i] = son[fail[x]][i];
}
}
for (int i = 0; i < 10; i++) son[m][i] = m;
}
int f[50005][21];
int g[50005][21];
int h[50005], pw[50005];
int to[50005];
int l0[50005], l0cnt;
signed main() {
freopen("shuawang.in", "r", stdin);
freopen("shuawang.out", "w", stdout);
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
pw[0] = 1;
for (int i = 1; i <= 50000; i++) pw[i] = pw[i - 1] * 10 % P;
int tc;
cin >> tc;
while (tc--) {
l0cnt = -1; m = 0; memset(son, 0, sizeof son);
memset(f, 0, sizeof f);
memset(g, 0, sizeof g);
memset(h, 0, sizeof h);
memset(to, 0, sizeof to);
memset(fail, 0, sizeof fail);
cin >> n >> q;
cin >> S >> T; K = T.size();
Insert(S), Build(); S = ' ' + S, T = ' ' + T;
f[K + 1][m] = 1;
for (int i = K; i; i--) {
for (int j = 0; j <= m; j++) {
if (T[i] != '?') f[i][j] = f[i + 1][son[j][T[i] - '0']];
else for (int k = 0; k < 10; k++) f[i][j] = min(inf, f[i][j] + f[i + 1][son[j][k]]);
}
}
int p = -1;
for (int t, i = K - m + 1; i; i--) {
for (int j = t = 1; j <= m; j++) t &= (T[i + j - 1] == '?' || S[j] == T[i + j - 1]);
if (!t) continue;
p = i; break;
}
if (p == -1) {
while (q--) cin >> n, cout << "-1\n";
continue;
}
int cnt = 0, s = p;
for (; s && cnt <= 20; --s) cnt += (T[s] == '?');
while (T[s] != '?') ++s;
for (int i = 1; i <= K; i++) {
if (T[i] == '?') {
to[i] = i + 1;
for (int j = 0; j <= m; j++) {
g[i][j] = j;
for (int k = i + 1; k <= K && T[k] != '?'; k++) g[i][j] = son[g[i][j]][T[k] - '0'];
}
for (int k = i + 1; k <= K && T[k] != '?'; k++) {
h[i] = (h[i] + (T[k] - '0') * pw[K - k]) % P;
to[i] = k + 1;
}
} else to[i] = to[i - 1];
}
l0cnt = -1;
for (int i = K; i >= p + m; i--) if (T[i] == '?') l0[++l0cnt] = i;
int _base = 0, _State = 0;
for (int i = 1; i < s; i++) {
_base = (_base + (T[i] == '?' ? 0 : T[i] - '0') * pw[K - i]) % P;
_State = son[_State][T[i] == '?' ? 0 : T[i] - '0'];
}
for (int i = to[p + m - 1]; i <= K; i++) _base = (_base + (T[i] == '?' ? 0 : T[i] - '0') * pw[K - i]) % P;
while (q--) {
int k, ans = _base, cur = _State;
cin >> k;
if (k > f[1][0]) {
cout << "-1\n";
continue;
}
for (int i = s; i <= p + m - 1; i = to[i]) {
for (int j = 0; j < 10; j++) {
if (f[i + 1][son[cur][j]] >= k) {
cur = son[cur][j];
ans = (ans + pw[K - i] * j) % P;
break;
} else k -= f[i + 1][son[cur][j]];
}
cur = g[i][cur];
ans = (ans + h[i]) % P;
}
--k;
int c = 0;
while (k) {
ans = (ans + (k % 10) * pw[K - l0[c]]) % P;
k /= 10; ++c;
}
cout << ans << "\n";
}
}
return 0;
}
T4
学习算法
把所有东西按权值排序,然后相当于按顺序走完每个点,要求走的总距离最小。然后还有一些限制,要求某些点必须在某些点前面走。将限制连边,则边有前向和后向两种。考虑到限制的形式,发现每段区间至多走两次就可以走完里面的点并满足限制。于是观察路径的形态,必然形如 \(s \rightarrow 1 \rightarrow n \rightarrow e\)(或反过来)。这个时候所有正向限制自然容易在正着走的时候满足,只需要考虑逆向限制带来的额外代价。显然从 \(s \rightarrow 1\) 和 \(n \rightarrow e\) 这两段各走两遍,中间的可能会有一些区间由于逆向限制一共要被走三遍。那么固定 \(s, e\) 过后,容易求出中间到底有哪些间隔要被算三次。于是枚举 \(s\),维护最优的 \(e\) 即可。构造方案,就,对着搞一搞。注意 \(s\) 和 \(e\) 不能被逆向限制覆盖,即使这样不优。
代码
#include <iostream>
#include <algorithm>
#include <string.h>
#include <array>
#define int long long
using namespace std;
const int inf = 0x3f3f3f3f3f3f3f3f;
int n, m, Ans = inf, ans[1000005], sz;
int w[1000005], o[1000005], d[1000005];
int _o[1000005], re[1000005];
void work() {
memset(d, 0, sizeof d);
for (int i = 1; i <= n; i++) _o[o[i]] = i;
for (int i = m + 1; i <= n; i++) if (_o[i] < _o[re[i]]) ++d[_o[i]], --d[_o[re[i]]];
for (int i = 1; i <= n; i++) d[i] += d[i - 1];
array<int, 2> tmp = { inf, 0 };
array<int, 3> f = { inf, 0, 0 };
for (int i = n; i; i--) {
tmp[0] += (d[i] ? 3 : 1) * (w[o[i + 1]] - w[o[i]]);
tmp = min(tmp, (array<int, 2>) { (w[o[n]] - w[o[i]]) * 2, i });
f = min(f, (array<int, 3>) { 2 * (w[o[i]] - w[o[1]]) + tmp[0], i, tmp[1] });
}
if (f[0] < Ans) {
Ans = f[0], sz = 0;
int S = f[1], E = f[2];
if (S == E) S = 0, E = 1;
for (int i = S; i; i--) if (o[i] <= m) ans[++sz] = o[i];
for (int i = 1; i <= S; i++) if (o[i] > m) ans[++sz] = o[i];
for (int i = S + 1; i < E; i++) {
if (!d[i]) ans[++sz] = o[i];
else {
int j = i; while (j < E - 1 && d[j]) ++j;
for (int k = j; k >= i; k--) if (o[k] <= m) ans[++sz] = o[k];
for (int k = i; k <= j; k++) if (o[k] > m) ans[++sz] = o[k];
i = j;
}
}
for (int i = E; i <= n; i++) if (o[i] <= m) ans[++sz] = o[i];
for (int i = n; i >= E; i--) if (o[i] > m) ans[++sz] = o[i];
}
}
signed main() {
freopen("learn.in", "r", stdin);
freopen("learn.out", "w", stdout);
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> w[i], o[i] = i;
sort(o + 1, o + n + 1, [](int x, int y) { return w[x] < w[y]; });
for (int i = m + 1; i <= n; i++) cin >> re[i];
work();
for (int i = 1; i <= n; i++) w[i] = -w[i]; reverse(o + 1, o + n + 1);
work();
cout << Ans << "\n";
for (int i = 1; i <= n; i++) cout << ans[i] << " \n"[i == n];
return 0;
}
T4,类 adhoc 状物。但看起来可能也又有点道理。
场上 T1 开 long long MLE 了。应当造极限数据测空间,想清楚到底有无必要开 long long。算空间时记清楚空间限制。
T3 场上代码只写错了一个地方(一个自增运算符的参数写错),但是大样例过了。无论如何还是应该仔细检查代码,而不是相信大样例。

浙公网安备 33010602011771号