今天没有牛客可以 vp 了,🐸说打一场个人训练吧,于是就有了这篇博客。
A. 折半枚举:四数之和为零 补
当时被卡常卡空间,各种卡,卡绝望了……赛时 TLE 和 MLE 共计 12 发,补题的时候还有两发。
#include <iostream>#include <vector>#include <algorithm>
using namespace std;
const int N = 4010;int v1[N * N], v2[N * N];int a[4][N];int len;
inline int read() { int x = 0, op = 1; char c = getchar(); while (!isdigit(c)) { if (c == '-') op = -1; c = getchar(); } while (isdigit(c)) x = x * 10 + c - 48, c = getchar(); return x * op;}
int main() { int n = read(); for (int i = 1; i <= n; ++i) { for (int j = 0; j < 4; ++j) { a[j][i] = read(); } } for (int i = 1; i <= n; ++i) { for (int j = 1; j <= n; ++j) { v1[len] = a[0][i] + a[1][j]; v2[len] = -a[2][i] - a[3][j]; len++; } } sort(v1, v1 + len); sort(v2, v2 + len); long long res = 0; for (int i = 0, j = 0, ri, rj; i < len && j < len; ) { // 不用双指针就会喜提 TLE if (v1[i] == v2[j]) { ri = i + 1, rj = j + 1; while (ri < len && v1[ri] == v1[i]) ri++; while (rj < len && v2[rj] == v2[j]) rj++; res += (long long)(ri - i) * (rj - j); i = ri, j = rj; } else if (v1[i] < v2[j]) { int ri = i + 1; while (ri < len && v1[ri] == v1[i]) ri++; i = ri; } else { int rj = j + 1; while (rj < len && v2[rj] == v2[j]) rj++; j = rj; } } printf("%lld\n", res); return 0;}B. 会议
朴实无华的换根 dp
#include <iostream>#include <vector>using namespace std;const int N = 50010;vector<int> ed[N];long long f[N];int cnt[N];long long res;int idx, n;
void dp(int x, int fa) { cnt[x] = 1; for (int y : ed[x]) { if (y == fa) continue; dp(y, x); f[x] += f[y] + cnt[y]; cnt[x] += cnt[y]; }}
void chrt(int x, int fa) { for (int y : ed[x]) { if (y == fa) continue; long long bk = f[y]; f[y] += (f[x] - f[y] - cnt[y]) + (n - cnt[y]); if (f[y] == res && y < idx || f[y] < res) { idx = y; res = f[y]; } chrt(y, x); f[y] = bk; }}
int main() { scanf("%d", &n); for (int i = 1; i < n; ++i) { int a, b; scanf("%d%d", &a, &b); ed[a].emplace_back(b); ed[b].emplace_back(a); } dp(1, 0); idx = 1, res = f[1]; chrt(1, 0); printf("%d %lld\n", idx, res); return 0;}C. 变音量
也是一道朴实无华的 dp 题,记录第 i 次能不能到 j,然后二维 dp 就可以。
#include <iostream>
using namespace std;bool f[70][1010];
int main() { int n, s, m; scanf("%d%d%d", &n, &s, &m); f[0][s] = true; n--; for (int i = 1; i <= n; ++i) { int t; scanf("%d", &t); for (int j = 0; j <= m; ++j) { if (j >= t) f[i][j] |= f[i - 1][j - t]; if (j + t <= m) f[i][j] |= f[i - 1][j + t]; } } for (int j = m; j; --j) { if (f[n][j]) { printf("%d\n", j); return 0; } } printf("-1\n"); return 0;}D. 三角棋盘上的N皇后
直接爆搜就可以解决,我的爆搜 171 ms.
#include <iostream>
using namespace std;const int N = 14;int msk[N];int mx, cur, t;int n;
void dfs(int x, int msk1, int msk2) { if (x == 0) { if (cur > mx) { mx = cur; t = 1; } else if (cur == mx) t++; return; } int mask = msk1 | msk2; if (msk[x]) { if (msk[x] & mask) return; else dfs(x - 1, (msk1 | msk[x]) >> 1, (msk2 | msk[x]) & ((1 << x - 1) - 1)); } else { for (int i = 0; i < x; ++i) { if (mask >> i & 1) continue; else { cur++; dfs(x - 1, (msk1 | (1 << i)) >> 1, (msk2 | (1 << i)) & ((1 << x - 1) - 1)); cur--; } } dfs(x - 1, msk1 >> 1, msk2 & ((1 << x - 1) - 1)); }}
int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) { char s[N + 1]; scanf("%s", s); for (int j = 0; s[j]; ++j) { msk[i] = (msk[i] << 1) | (s[j] == '*'); } } dfs(n, 0, 0); printf("%d\n%d\n", mx, t); return 0;}E. 教主的游乐场
最优的路径一定只往左走一次或者一直往右,从右往左更新一遍状态先处理直接往右的,再从左往右更新一遍先往左再往右的,可以用线段树维护,背板子的题写着就是😋。
我当时查询的循环写的 n 然后 OLE 了一次。
#include <iostream>#include <cstring>
using namespace std;
const int N = 100000;int tr[N * 4];int a[N];
void modify(int u, int l, int r, int p, int v) { if (l == r) tr[u] = min(tr[u], v); else { int mid = l + r >> 1; if (p <= mid) modify(u << 1, l, mid, p, v); else modify(u << 1 | 1, mid + 1, r, p, v); tr[u] = min(tr[u << 1], tr[u << 1 | 1]); }}
int query(int u, int l, int r, int ql, int qr) { if (ql > qr) return 0x3f3f3f3f; if (ql <= l && r <= qr) return tr[u]; else { int mid = l + r >> 1; int res = 0x3f3f3f3f; if (ql <= mid) res = query(u << 1, l, mid, ql, qr); if (qr > mid) res = min(res, query(u << 1 | 1, mid + 1, r, ql, qr)); return res; }}
int main() { memset(tr, 0x3f, sizeof(tr)); int n, m; scanf("%d%d", &n, &m); for (int i = 1; i <= n; ++i) { scanf("%d", &a[i]); if (i + a[i] > n) modify(1, 1, n, i, 1); } for (int i = n; i; --i) { modify(1, 1, n, i, min(query(1, 1, n, 1, i - 1), query(1, 1, n, i + 1, min(n, i + a[i]))) + 1); } for (int i = 1; i <= n; ++i) { modify(1, 1, n, i, min(query(1, 1, n, 1, i - 1), query(1, 1, n, i + 1, min(n, i + a[i]))) + 1); } for (int i = 1; i <= m; ++i) { int q; scanf("%d", &q); printf("%d ", query(1, 1, n, q, q)); } printf("\n"); return 0;}F. 任务 (task)
用类似 dp 的思路贪心,按照 l 排序,维护一下目前所有线程的右端点,每次都找离第 i 个区间的左端点最近的右端点用,如果找不到就只能放弃一个,这个时候检查一下他的右端点是否比最大的小,如果是就把右端点最大的放弃掉把他加进去,否则直接把这个放弃掉。
#include <iostream>#include <algorithm>#include <set>
using namespace std;const int N = 1000010;pair<int, int> a[N];multiset<int> s;
int main() { int n, k; scanf("%d%d", &n, &k); for (int i = 1; i <= n; ++i) { scanf("%d%d", &a[i].first, &a[i].second); } int res = 0; sort(a + 1, a + n + 1); for (int i = 1; i <= n; ++i) { auto p = s.upper_bound(a[i].first); if (p == s.begin()) { if (s.size() < k) s.insert(a[i].second), res++; else if (*s.rbegin() > a[i].second) s.erase(--s.end()), s.insert(a[i].second); } else { s.erase(--p), s.insert(a[i].second); res++; } } printf("%d\n", res); return 0;}G. 祈求者 (invoker)
把状态用三进制压缩,然后直接一遍状压 dp 就解决了,最困难的地方在于三进制状态有 27 个,需要要注意打表的时候会不会抄错。
#include <iostream>#include <map>#include <vector>#include <cstring>using namespace std;
const int N = 100010;
map<char, vector<int>> mp = { {'Y', {0}}, // QQQ {'V', {1, 3, 9}}, // QQW {'G', {2, 6, 18}}, // QQE {'C', {13}}, // WWW {'X', {4, 10, 12}}, // QWW {'Z', {14, 16, 22}}, // WWE {'T', {26}}, // EEE {'F', {8, 20, 24}}, // QEE {'D', {17, 23, 25}}, // WEE {'B', {5, 7, 11, 15, 19, 21}} // QWE};
int f[N][27];
int dis(int x, int y) { vector<int> s1 = {x / 9, x / 3 % 3, x % 3}, s2 = {y / 9, y / 3 % 3, y % 3}; if (s1 == s2) return 1; else if (s1[1] == s2[0] && s1[2] == s2[1]) return 2; else if (s1[2] == s2[0]) return 3; else return 4;}
int main() { ios::sync_with_stdio(0); cin.tie(0), cout.tie(0); string s; cin >> s; int n = s.length(); memset(f, 0x3f, sizeof(f)); for (int i = 1; i <= n; ++i) { auto v = mp[s[i - 1]]; if (i == 1) { for (int j : v) f[i][j] = 4; } else { for (int j : v) { for (int k = 0; k < 27; ++k) { f[i][j] = min(f[i][j], f[i - 1][k] + dis(k, j)); } } } } int res = 0x3f3f3f3f; for (int i = 0; i < 27; ++i) { res = min(res, f[n][i]); } printf("%d\n", res); return 0;}I. 排列计数
画一下样例很容易观察出来,这就是个二叉堆,对于每一个子树都保证根节点比子树的最小值小即可。于是就可以树形 dp 了。
刚在数位 dp 那里爆了一次 int,然后又在这儿爆 int 了……
#include <iostream>#include <vector>
using namespace std;typedef long long LL;const int N = 1000010;
LL power(LL n, LL p, LL mod) { LL res = 1, base = n; while (p) { if (p & 1) res = res * base % mod; base = base * base % mod; p >>= 1; } return res;}
LL jc[N], f[N];vector<int> ed[N];int n, p;int cnt[N];
LL c(int n, int m) { return jc[n] * power(jc[n - m], p - 2, p) % p * power(jc[m], p - 2, p) % p;}
void dp(int x) { f[x] = 1; cnt[x] = 1; bool first = true; for (int y : ed[x]) { dp(y); cnt[x] += cnt[y]; f[x] = (f[x] * f[y]) % p; } if (cnt[x] != 1) f[x] = (f[x] * c(cnt[x] - 1, cnt[ed[x][0]])) % p;}
int main() { scanf("%d%d", &n, &p); jc[0] = 1; for (int i = 1; i <= n; ++i) { jc[i] = jc[i - 1] * i % p; if (i != 1) ed[i / 2].emplace_back(i); } dp(1); printf("%lld\n", f[1]); return 0;}K. 【数位DP】数字计数
这个数位dp很恶心,需要一边统计方案数一边统计数字的出现次数,还需要排除前导零的情况,正好我数位dp不好,被硬控了一上午。好不容易 dp 调对了,又爆 int 了……
表示从最高位开始数填到前 i 位时所有情况指定数字的个数, 表示是否顶到上界, 表示当前位置是不是前导零, 表示满足 j 和 k 约束的数的个数。
状态转移枚举当前位置填的数,0 的时候涉及到前导零的问题要单独算,其他的数一起算。
来欣赏一下一我横向滚动两屏半的超长状态转移方程🤮
#include <iostream>#include <cstring>#include <algorithm>
using namespace std;const int N = 14;long long a[N], len;long long f[N][2][2], g[N][2][2]; // 顶上界 前导零
long long count(long long x, int k) { memset(g, 0, sizeof(g)); if (x == -1) return 0; if (x == 0) return k == 0; len = 0; while (x) { a[++len] = x % 10; x /= 10; } reverse(a + 1, a + len + 1); g[0][1][1] = 1; for (int i = 1; i <= len; ++i) { g[i][0][0] = (g[i - 1][1][0] * (a[i] != 0) + g[i - 1][0][0]) + (g[i - 1][1][0] + g[i - 1][1][1]) * max(0LL, a[i] - 1) + (g[i - 1][0][0] + g[i - 1][0][1]) * 9; g[i][0][1] = g[i - 1][1][1] + g[i - 1][0][1]; g[i][1][0] = 1; f[i][0][0] = (f[i - 1][1][0] * (a[i] != 0) + f[i - 1][0][0]) + g[i - 1][1][0] * (a[i] != 0 && k == 0) + g[i - 1][0][0] * (k == 0) + (f[i - 1][1][1] + f[i - 1][1][0]) * max(0LL, a[i] - 1) + (f[i - 1][0][0] + f[i - 1][0][1]) * 9 + (g[i - 1][1][0] + g[i - 1][1][1]) * (k != 0 && k < a[i]) + (g[i - 1][0][0] + g[i - 1][0][1]) * (k != 0); f[i][1][0] = f[i - 1][1][0] + (a[i] == k); } return f[len][0][0] + f[len][1][0] + (k == 0);}
int main() { long long a, b; cin >> a >> b; for (int i = 0; i < 10; ++i) { cout << count(b, i) - count(a - 1, i) << ' '; } cout << endl; return 0;}部分信息可能已经过时







