I will no longer be a tra|

yabnto

园龄:2年8个月粉丝:14关注:17

总结

集合

考虑枚举子集和,统计有多少个子集的和为当前枚举的子集和,然后我们记个结论:xy=xymod(p1),然后就过了

P3488

一眼二分图(网络流启动),但是考虑到图很大,所以我们考虑直接判断是否是二分图,考虑一个区间,如果总数比这个区间所能承载的人都要大,那么肯定会寄,所以用线段树维护每个区间的最大字段和

连通块

考虑没有限制,那么就是一个树上的 dp,加上限制后,我们考虑,每加一个子树相当于加一个dfn序,所以我们可以记录当前dfn最后一个位置,那么如果要加一个子树,那么就可以从限制点的位置-1的地方转移过来,但是我们只要处理限制点,所以我们可以离散化,然后只处理限制点

跳棋

考虑整个序列是如何变动的,我们观察到,当两个 1 组在一起时,是可以一直动的,因为碰到一个 1 时,可以从跳跃换成接替,那么我们便可以将 11 压在一起变成 2,那么数组终将会有四种元素:0,1,2,?

考虑没有问号的情况,那么整个问题变成了 2 的放置,我们发现 1 没有贡献,所以我们只用看 0 和 2 的个数,那么变成了在 0 和 2 的个数和中选取 2 的个数个位置放置 2,那么我们可以用 dp 的方式来给 ? 分配符号,考虑到答案是与 0 的个数, 和 2 的个数有关,所以我们记录下 0 的个数和 2 的个数,那么我们初始认为状态时 fijk 表示前 i 个,有 j 个 2 k 个 0

但是我们要思考 jk 要如何统计,那么统计我们需要知道上一位是什么,但是考虑到 2 的统计是当前面有偶数个 1 的时候才能凑齐 2,所以我们要加一个前面有奇数偶数个 1 所以状态变成 fijk 0/1 0/1

考虑转移,那么从当前是什么来考虑,分几种当前是什么的情况,然后注意当只有当前面有偶数个 1 的时候才能统计 2 的个数

最后对于目标状态,由于已经不存在 ? 所以可以直接用组合数做,然后要滚动数组

总结

由于思路错误,导致大部分时间在想 Dinic 如何解决删人问题,所以其它题基本没有想(不过我Dinic写对了!)。

114

我们手玩的知,选取方式如 AABABABAB 的形式,而 0 的存在可以将最坏情况交给对面,所以 0 起调整作用,然后在 1 和 2 中选择一个进行判断,如果存在一个数可以使得先手必胜,那么先手必胜

514

考虑第一个 ai<i 的位置,假设这个位置是 i

根据题意我们得知:a1 可以填的数种类为 n1+1a2 可以填的数种类为 n2+1,于是我们知道 i1 可以填 ni+2,但是 i 已经占掉了一个前面的,所以变成了 ni+1,然后同理我们可以得到 i1 可以放 ni+2 种,但是由于 i1 又占了一个,所以也为 ni+1 以此类推我们可以得到前 j 的方案数:(ni+1)j,不过我们在实际情况下会存在一个断点,是的前面的是小于等于 j 的填数,而其他的在后面,那么其他的为 n(i1)+1 种数,那么方案数便是 (ni+2)ij1

整理一下柿子:

ans=n+1+i=1n(j=1i1((ni+1)j(ni+2)ij1)(ni)!i)

考虑一下换元,设 x=ni+1,设 y=xx+1 那么可得:

ans=n+1+i=1n(j=1i1((x)j(x+1)ij1)(ni)!i)

ans=n+1+i=1n(j=1i1((x)j(x+1)i1(x+1)j)(ni)!i)

ans=n+1+i=1n((x+1)i1j=1i1((x)j(x+1)j)(ni)!i)

ans=n+1+i=1n((x+1)i1j=1i1(xx+1)j(ni)!i)

ans=n+1+i=1n((x+1)i1j=1i1yj(ni)!i)

ans=n+1+i=1n((x+1)i1j=1i1y2yy1(ni)!i)

然后就可以 O(n) 解决了

1919

考虑将一个区间查询变成前缀异或和,那么设 S(x)1x 的答案,考虑每次消掉最大的fib,我们每次删掉最大的fib,然后分为两段进行计算,然后计算被消掉的数量,如果为奇数则异或后不为 0 所以异或即可。

810

完全背包的优化,如果有连续的 min 个的可以,那么接下来的肯定都能被表示,所以直接剪枝

总结

数学场我是一点都写不了啊!

智乃的差分

构造。What can I say

牛牛的旅行

一眼淀粉质,但是可以将贡献拆成点带来的和边带来的,那么可以对每条边求出经过次数,对于每个点,从小到大排序,然后将以这个为路径上最大的点对计算贡献,具体来说就是将并查集合并在合并的过程中,计算两个并查集经过最大点的点对数

第K排列

用 dp 优化搜索,我们可以想到一个暴力,就是暴力枚举填什么,然后最后再去判,这个的依据是 k 很小,不过我们发现可能判出很多没必要的,所以我们可以计算一下从后往前的最大填法,如果用最大填法都无法达到目标就没有必要继续填了,所以我们先维护一个从后往前的最大填法 dp,然后暴力从前往后填,好像是叫 A* 吧。

牛牛的 border

考虑枚举子串作为 border,假设这个出现了 cnt 次,长度为 len,那么以这个为 border 的字串将会有 cnt(cnt1)2 个(组合数),然后考虑到是 border,那么我们可以从中删掉一些字符,使得还可以作为 border,所以还要计算删掉一些字符,我们考虑用 SAM 这样就确定了左端点,所以我们就只用删掉后缀,那么便可以用等差数列计算了即:len(len+1)2(因为对于 1 ~ len 的长度都会出现在删掉后缀的 border 中),然后如果要用 SA 就需要处理一些重复的位置,我的评价是,SAM 这个数据结构,甚至说 trie 树都直接将这个多余的一部给剩了(重复的将会合并在条路径中)

#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 呜呜呜。

考虑推个柿子:
a+b+c2+d4A

d+b+c2+a4D

b+a+d2+c4B

c+a+d2+b4C

我们设 a=4k+d,然后进行推柿子,于是我们可以通过关于 BC 的柿子和 k 的范围来判断可行性,然后就用二分暴力算即可

奇怪的函数

哇,这道题没做出来简直唐诗(其实我没怎么看题)。

一眼线段树,于是考虑信息与标记,我们会发现可以将最终的函数看作一个取值范围的区间,但是考虑到有加减操作,于是我们单纯维护取值范围,并用一个标记维护加减操作

  1. 信息与信息

如果信息维护的区间有相交,那么合并后的区间将会是相交部分吗,如果没有的话,那么肯定是左边的所有可能值到右边后全变小或全变大,所以区间将会是变小后的值或是变大后的值

  1. 信息与标记

考虑到信息维护的区间是不加加减操作的取值范围,所以在采用加减操作时只要把左右两端都加上加减操作即可。

  1. 标记与标记

直接相加,因为都是加减操作

#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

考虑将贡献转化,转换为每一个位置的答案会对总答案产生多少贡献,那么我们枚举哪一个位置将会留下,设计状态 fid,i,j 表示 id 留下,i 次删,当前留下的数的排名,那么每次我们有两种转移,一种删前面的一种删后面的,删前面的时候我们定义删的是 k,那么会有 (1pi)k1pi 的概率成功,那么对于所有的排名小于当前的我们可以统一计算概率,整理一下得:1qpow(1p[j1],k) 的概率,而在后面的也同理易得

C

分治板子,我们考虑每次删掉一个不合法的,将区间分为两半,然后就可以求出来最大的 k 了,这样的时间复杂度我们发现,每次划分当且仅当出现了一种新的出现次数,那么如果要求出现次数都不同,那么分布将会是 (n) 级别的,所以时间复杂度是 O(n(n)) 级别的。

考虑如何优化,我们用启发式合并,只动小的,那么启发式合并带给我们的将会是总和 nlogn 的时间复杂度

遇到区间,不带单调性,还带明显特征的分段点的题,分治启动(虽然后面寄了,因为是单峰)

砖块摆放

手玩,发现规律:z=3xy 二项式秒了

植物收集

嗨嗨嗨!现场推三分过了!

我们想一个错解,我们每次只给一个施肥,那么我们可以用dij解决,但是我们发现,我们一次给的是一堆施肥,所以我们的施肥费用其实之和最大是施肥数有关,那么我们可以将贡献非为两类,一个是最大施肥次数,一个是种子购买数,我们枚举最大施肥次数,然后对于每一个进行判断,我们发现将是在一个区间上求最小值,然后我们观察可得,这个区间最小值将会是一个下凸壳,而最大施肥次数将会是一个线性函数,两个加在一起将会是一个单峰函数,然后三分启动

本文作者:yabnto

本文链接:https://www.cnblogs.com/ybtarr/p/18428981

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   yabnto  阅读(23)  评论(0编辑  收藏  举报
历史上的今天:
2023-09-24 P1631 序列合并
  1. 1 イエスタデイ(翻自 Official髭男dism) 茶泡饭,春茶,kobasolo
  2. 2 光辉岁月 Audio artist
  3. 3 名前を呼ぶよ Audio artist
  4. 4 战歌 Audio artist
  5. 5 時を越えた想い Audio artist
  6. 6 所念皆星河 Audio artist
  7. 7 See you again Audio artist
战歌 - Audio artist
00:00 / 00:00
An audio error has occurred, player will skip forward in 2 seconds.

Not available

点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起