总结
集合
考虑枚举子集和,统计有多少个子集的和为当前枚举的子集和,然后我们记个结论:
P3488
一眼二分图(网络流启动),但是考虑到图很大,所以我们考虑直接判断是否是二分图,考虑一个区间,如果总数比这个区间所能承载的人都要大,那么肯定会寄,所以用线段树维护每个区间的最大字段和
连通块
考虑没有限制,那么就是一个树上的
跳棋
考虑整个序列是如何变动的,我们观察到,当两个 1 组在一起时,是可以一直动的,因为碰到一个 1 时,可以从跳跃换成接替,那么我们便可以将 11 压在一起变成 2,那么数组终将会有四种元素:
考虑没有问号的情况,那么整个问题变成了 2 的放置,我们发现 1 没有贡献,所以我们只用看 0 和 2 的个数,那么变成了在 0 和 2 的个数和中选取 2 的个数个位置放置 2,那么我们可以用
但是我们要思考
考虑转移,那么从当前是什么来考虑,分几种当前是什么的情况,然后注意当只有当前面有偶数个 1 的时候才能统计
最后对于目标状态,由于已经不存在 ? 所以可以直接用组合数做,然后要滚动数组
总结
由于思路错误,导致大部分时间在想 Dinic 如何解决删人问题,所以其它题基本没有想(不过我Dinic写对了!)。
114
我们手玩的知,选取方式如
514
考虑第一个
根据题意我们得知:
整理一下柿子:
考虑一下换元,设
然后就可以
1919
考虑将一个区间查询变成前缀异或和,那么设
810
完全背包的优化,如果有连续的
总结
数学场我是一点都写不了啊!
智乃的差分
构造。What can I say
牛牛的旅行
一眼淀粉质,但是可以将贡献拆成点带来的和边带来的,那么可以对每条边求出经过次数,对于每个点,从小到大排序,然后将以这个为路径上最大的点对计算贡献,具体来说就是将并查集合并在合并的过程中,计算两个并查集经过最大点的点对数
第K排列
用 dp 优化搜索,我们可以想到一个暴力,就是暴力枚举填什么,然后最后再去判,这个的依据是 k 很小,不过我们发现可能判出很多没必要的,所以我们可以计算一下从后往前的最大填法,如果用最大填法都无法达到目标就没有必要继续填了,所以我们先维护一个从后往前的最大填法 dp,然后暴力从前往后填,好像是叫 A* 吧。
牛牛的 border
考虑枚举子串作为 border,假设这个出现了 cnt 次,长度为 len,那么以这个为 border 的字串将会有
#include <iostream> using namespace std; using ll = long long; const int MaxN = 1e5 + 10; namespace SAM { ll nxt[MaxN << 1][27], fail[MaxN << 1], len[MaxN << 1], cnt[MaxN << 1], t[MaxN << 1], p[MaxN << 1], tot; void copy(int x, int y) { for (int i = 0; i < 27; i++) nxt[x][i] = nxt[y][i]; fail[x] = fail[y]; } void insert(string s) { tot = 1; for (int i = 0, c, to, p, lstp = 1; i < s.size(); i++, nxt[lstp][c] = to, lstp = to) { c = s[i] - 'a', len[to = ++tot] = len[p = lstp] + 1, cnt[to] = 1; for (p; p && !nxt[p][c]; p = fail[p]) { nxt[p][c] = to; } if (!p && (fail[to] = 1)) continue; int v = nxt[p][c], cl = ++tot; if (len[v] == len[p] + 1 && (tot--, fail[to] = v)) continue; for (copy(cl, v), len[cl] = len[p] + 1; p && nxt[p][c] == v; p = fail[p]) { nxt[p][c] = cl; } fail[to] = fail[v] = cl; } for (int i = 1; i <= tot; i++) t[len[i]]++; for (int i = 1; i <= tot; i++) t[i] += t[i - 1]; for (int i = 1; i <= tot; i++) p[t[len[i]]--] = i; for (int i = tot; i >= 1; i--) cnt[fail[p[i]]] += cnt[p[i]]; } } // namespace SAM using namespace SAM; string s; ll ans; int n; int main() { ios::sync_with_stdio(0), cin.tie(0); freopen("border.in", "r", stdin); freopen("border.out", "w", stdout); cin >> n >> s; insert(s); for (int i = 1; i <= tot; i++) { ans += cnt[p[i]] * (cnt[p[i]] - 1) / 2 * (len[p[i]] + len[fail[p[i]]] + 1) * (len[p[i]] - (len[fail[p[i]]] + 1) + 1) / 2; } cout << ans << endl; return 0; }
总结
死磕 B 题,然后写了个淀粉质常数巨大后 T 爆,A 题想到了可还是有情况没想全,最后爆 0
你相信()吗
考场上推了三个小时,然后差一步,最后 30 呜呜呜。
考虑推个柿子:
我们设
奇怪的函数
哇,这道题没做出来简直唐诗(其实我没怎么看题)。
一眼线段树,于是考虑信息与标记,我们会发现可以将最终的函数看作一个取值范围的区间,但是考虑到有加减操作,于是我们单纯维护取值范围,并用一个标记维护加减操作
- 信息与信息
如果信息维护的区间有相交,那么合并后的区间将会是相交部分吗,如果没有的话,那么肯定是左边的所有可能值到右边后全变小或全变大,所以区间将会是变小后的值或是变大后的值
- 信息与标记
考虑到信息维护的区间是不加加减操作的取值范围,所以在采用加减操作时只要把左右两端都加上加减操作即可。
- 标记与标记
直接相加,因为都是加减操作
#include <iostream> using namespace std; const int MaxN = 3e5 + 10, inf = 1e9; struct S { int l, r, w; S operator+(const S &j) const { if (r < j.l - w) return {j.l - w, j.l - w, w + j.w}; if (l > j.r - w) return {j.r - w, j.r - w, w + j.w}; return {max(j.l - w, l), min(j.r - w, r), w + j.w}; } } d[MaxN << 2]; int n, m, op, p, x; void update(int k, int op, int x, int l = 1, int r = n, int p = 1) { if (l == r) { if (op == 1) d[p] = {-inf, inf, x}; if (op == 2) d[p] = {-inf, x, 0}; if (op == 3) d[p] = {x, inf, 0}; return; } int mid = l + r >> 1; if (k <= mid) update(k, op, x, l, mid, p << 1); if (k > mid) update(k, op, x, mid + 1, r, p << 1 | 1); d[p] = d[p << 1] + d[p << 1 | 1]; } int main() { freopen("function.in", "r", stdin); freopen("function.out", "w", stdout); cin >> n; for (int i = 1; i <= n; i++) { cin >> op >> x, update(i, op, x); } for (cin >> m; m; m--) { cin >> op; if (op == 4) { cin >> x; cout << (x < d[1].l ? d[1].l + d[1].w : x > d[1].r ? d[1].r + d[1].w : x + d[1].w) << endl; } else { cin >> p >> x; update(p, op, x); } } return 0; }
博弈
淀粉质板子,但是不会哈希技巧而死!
没啥好说的,就是可以给边权附一个随机的数值,然后哈希即可
强烈谴责 -+ 不断取消我的成绩
#include <ctime> #include <iostream> #include <unordered_map> #include <random> #include <vector> using namespace std; using ll = long long; const int MaxN = 5e5 + 10; struct S { ll v, w; }; ll sz[MaxN], tmpp[MaxN], tot, n, k, ans, SUM, t; vector<S> g[MaxN]; bool vis[MaxN]; unordered_map<ll, ll> st, cnt; int find_fatbigest(int x, int fa) { sz[x] = 1; ll maxs = 0, res = -1; for (auto i : g[x]) { if (i.v == fa || vis[i.v]) continue; res = find_fatbigest(i.v, x); if (res != -1) { return res; } sz[x] += sz[i.v], maxs = max(maxs, sz[i.v]); } maxs = max(maxs, n - sz[x]); if (maxs * 2 <= n) { res = x; sz[fa] = n - sz[x]; } return res; } void G(int x, int fa, ll sum) { tmpp[++tot] = sum; ans += SUM - cnt[sum] + bool(sum); for (auto i : g[x]) { if (i.v == fa || vis[i.v]) continue; G(i.v, x, sum ^ i.w); } } void DFS(int x) { for (auto i : g[x]) { if (vis[i.v]) continue; int tmp = tot; G(i.v, x, i.w); for (int i = tmp + 1; i <= tot; i++) { cnt[tmpp[i]]++; SUM++; } } SUM = 0; unordered_map<ll, ll>().swap(cnt); tot = 0; vis[x] = 1; for (auto i : g[x]) { if (vis[i.v]) continue; n = sz[i.v]; DFS(find_fatbigest(i.v, x)); } } int main() { freopen("game.in", "r", stdin); freopen("game.out", "w", stdout); ios::sync_with_stdio(0), cin.tie(0); mt19937_64 rnd(time(0)); for (cin >> t; t; t--) { cin >> n, ans = 0; for (int i = 1; i <= n; i++) { vector<S>().swap(g[i]), vis[i] = 0; } unordered_map<ll, ll>().swap(st); for (int i = 1, u, v, w; i < n; i++) { cin >> u >> v >> w; if (!st.count(w)) st[w] = uniform_int_distribution<ll>(1, 1e18)(rnd); g[u].push_back({v, st[w]}); g[v].push_back({u, st[w]}); } DFS(find_fatbigest(1, 0)); cout << ans << '\n'; } return 0; }
跳跃
考虑贪心,我们肯定是在一个最大子段和中跳来跳去,那么我们需要思考什么时候第一时间到一个点和此时的最大和,然后就可以暴力dp
大陆
我们对于大于等于 B 的分一块,然后将小于 B 的传上去,如果根这一连通块大小小于 B ,我们可以将其加给任意一个,因为任意一个都肯定小于 2B,加一个后也小于等于 3B
排列
我是智障,循环右移原来是一段区间的移动
考虑线段树,然后发现无法移动,所以转成 FHQ,但是 FHQ 需要用下标作为分裂合并
考虑如何维护答案,我们可以先求二元的,那么将会是一个最大最小值,那么三元的我们可以维护小于最大值的最右值,和大于最小值的最左值,然后进行一个拼凑,在 FHQ 上搜索即可
#include <algorithm> #include <ctime> #include <random> #include <iostream> using namespace std; mt19937 myrand(time(0)); uniform_int_distribution<int> randpp(1, 1e9); const int MaxN = 2e5 + 10; const int inf = 1e9; struct Tree { struct Node { int minx, miny, maxx, maxy, w, x, v, l, r, sum; } a[MaxN]; int root, tot; Tree() { root = 0; a[root].minx = a[root].miny = inf; a[root].maxx = a[root].maxy = -inf; } int pre(int x, int l) { if (!x) return -inf; if (a[x].x < l) return max(pre(a[x].l, l), a[x].x); return pre(a[x].r, l); } int nxt(int x, int r) { if (!x) return inf; if (a[x].x > r) return min(nxt(a[x].r, r), a[x].x); return nxt(a[x].l, r); } void update(int k, int ls = 0, int rs = 0) { ls = a[k].l, rs = a[k].r; a[k].sum = a[ls].sum + a[rs].sum + 1; a[k].w = a[ls].w | a[rs].w; if (min(a[k].x, a[ls].minx) < a[rs].maxy || a[ls].miny < max(a[k].x, a[rs].maxx) || (a[ls].minx < a[k].x && a[k].x < a[rs].maxx)) a[k].w = 1; a[k].minx = min({a[k].x, a[ls].minx, a[rs].minx}), a[k].maxx = max({a[k].x, a[ls].maxx, a[rs].maxx}); a[k].miny = min(a[ls].miny, a[rs].miny), a[k].maxy = max(a[ls].maxy, a[rs].maxy); (a[k].x > a[ls].minx) && (a[k].miny = min(a[k].miny, a[k].x)); (a[k].x < a[rs].maxx) && (a[k].maxy = max(a[k].maxy, a[k].x)); a[k].miny = min(a[k].miny, nxt(rs, min(a[ls].minx, a[k].x))); a[k].maxy = max(a[k].maxy, pre(ls, max(a[rs].maxx, a[k].x))); } void split(int k, int v, int &x, int &y) { if (!k) { x = y = 0; return; } if (a[a[k].l].sum < v) { x = k; split(a[k].r, v - a[a[k].l].sum - 1, a[k].r, y); } else { y = k; split(a[k].l, v, x, a[k].l); } update(k); } void merge(int &k, int x, int y) { if (!x || !y) { k = x + y; return; } if (a[x].v > a[y].v) { merge(a[x].r, a[x].r, y); k = x; } else { merge(a[y].l, x, a[y].l); k = y; } update(k); } void insert(int id, int v) { a[++tot] = {v, inf, v, -inf, 0, v, int(randpp(myrand) % inf), 0, 0, 1}; merge(root, root, tot); } } tr; int n, m, l, r, k; int main() { freopen("permutation.in", "r", stdin); freopen("permutation.out", "w", stdout); ios::sync_with_stdio(0), cin.tie(0); cin >> n; for (int i = 1, w; i <= n; i++) { cin >> w; tr.insert(i, w); } for (cin >> m; m; m--) { cin >> l >> r >> k; int A = 0, B = 0, C = 0, D = 0, E = 0, F = 0, G = 0; tr.split(tr.root, r, F, G); tr.split(F, r - k, D, E); tr.split(D, l - 1, B, C); tr.merge(B, B, E); tr.merge(C, C, G); tr.merge(tr.root, B, C); cout << (tr.a[tr.root].w ? "YES" : "NO") << '\n'; } return 0; }
躲避技能
我们考虑一个贪心的想法,我们让可以不出子树的地方尽量不出子树,那么出子树的情况当且仅当位置不够,所以我们维护一下点数和坑数的差就行了
帮助
考虑将其在图中表示,然后就可以获得一个扫描线板子
跑步路线
我靠!没看到边权不同!!!!!!以为最小生成树不唯一!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
fountain
我们可以使用一个高级的设元,然后就过了
subset
这个题分为两个模块,第一个是当函数为 0 时的计算,另一部分为不断删掉集合中的一些数,然后进行计算,
A
我们可以将删掉立方体改成一个二维的矩形体现在数轴上,然后我们将每一面的二维矩阵进行处理,会发现是对于每一个 x 的最大的 y 然后删掉,那么我们对于每一个面进行延申到其他面,我们会发现,当一个横截面移动时,三条线的移动都是单调的,那么我们便可以通过横截面的移动,用预处理出来的三条线确定我们删掉的体积。
B
考虑将贡献转化,转换为每一个位置的答案会对总答案产生多少贡献,那么我们枚举哪一个位置将会留下,设计状态
C
分治板子,我们考虑每次删掉一个不合法的,将区间分为两半,然后就可以求出来最大的 k 了,这样的时间复杂度我们发现,每次划分当且仅当出现了一种新的出现次数,那么如果要求出现次数都不同,那么分布将会是
考虑如何优化,我们用启发式合并,只动小的,那么启发式合并带给我们的将会是总和
遇到区间,不带单调性,还带明显特征的分段点的题,分治启动(虽然后面寄了,因为是单峰)
砖块摆放
手玩,发现规律:
植物收集
嗨嗨嗨!现场推三分过了!
我们想一个错解,我们每次只给一个施肥,那么我们可以用dij解决,但是我们发现,我们一次给的是一堆施肥,所以我们的施肥费用其实之和最大是施肥数有关,那么我们可以将贡献非为两类,一个是最大施肥次数,一个是种子购买数,我们枚举最大施肥次数,然后对于每一个进行判断,我们发现将是在一个区间上求最小值,然后我们观察可得,这个区间最小值将会是一个下凸壳,而最大施肥次数将会是一个线性函数,两个加在一起将会是一个单峰函数,然后三分启动
本文作者:yabnto
本文链接:https://www.cnblogs.com/ybtarr/p/18428981
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
2023-09-24 P1631 序列合并