总结

集合

考虑枚举子集和,统计有多少个子集的和为当前枚举的子集和,然后我们记个结论:\(x^y=x^{y \mod (p - 1)}\),然后就过了

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 的个数,那么我们初始认为状态时 \(f_{ijk}\) 表示前 \(i\) 个,有 \(j\) 个 2 \(k\) 个 0

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

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

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

总结

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

114

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

514

考虑第一个 \(a_i < i\) 的位置,假设这个位置是 \(i\)

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

整理一下柿子:

\(ans=n+1+\sum\limits_{i=1}^{n}(\sum\limits_{j=1}^{i-1}((n-i+1)^j(n-i+2)^{i-j-1})(n-i)!i)\)

考虑一下换元,设 \(x = n - i + 1\),设 \(y = \frac{x}{x+1}\) 那么可得:

\(ans=n+1+\sum\limits_{i=1}^{n}(\sum\limits_{j=1}^{i-1}((x)^j(x + 1)^{i-j-1})(n-i)!i)\)

\(ans=n+1+\sum\limits_{i=1}^{n}(\sum\limits_{j=1}^{i-1}((x)^j(x + 1)^{i-1}(x+1)^{-j})(n-i)!i)\)

\(ans=n+1+\sum\limits_{i=1}^{n}((x + 1)^{i-1}\sum\limits_{j=1}^{i-1}((x)^j(x+1)^{-j})(n-i)!i)\)

\(ans=n+1+\sum\limits_{i=1}^{n}((x + 1)^{i-1}\sum\limits_{j=1}^{i-1}(\frac{x}{x+1})^{j}(n-i)!i)\)

\(ans=n+1+\sum\limits_{i=1}^{n}((x + 1)^{i-1}\sum\limits_{j=1}^{i-1}y^{j}(n-i)!i)\)

\(ans=n+1+\sum\limits_{i=1}^{n}((x + 1)^{i-1}\sum\limits_{j=1}^{i-1}\frac{y^2-y}{y-1}(n-i)!i)\)

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

1919

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

810

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

总结

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

智乃的差分

构造。What can I say

牛牛的旅行

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

第K排列

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

牛牛的 border

考虑枚举子串作为 border,假设这个出现了 cnt 次,长度为 len,那么以这个为 border 的字串将会有 \(\frac{cnt * (cnt - 1)}{2}\) 个(组合数),然后考虑到是 border,那么我们可以从中删掉一些字符,使得还可以作为 border,所以还要计算删掉一些字符,我们考虑用 SAM 这样就确定了左端点,所以我们就只用删掉后缀,那么便可以用等差数列计算了即:\(\frac{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 + \frac{b + c}{2} + \frac{d}{4} \ge A\)

\(d + \frac{b + c}{2} + \frac{a}{4} \ge D\)

\(b + \frac{a + d}{2} + \frac{c}{4} \ge B\)

\(c + \frac{a + d}{2} + \frac{b}{4} \ge C\)

我们设 \(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;
}
posted @ 2024-09-24 15:11  yabnto  阅读(7)  评论(0编辑  收藏  举报