2025杭电第5场
写一下学到了新东西的几个题目,数学太差所以很简单的数学也不会
1001
树上笛卡尔树+倍增
对于大根堆笛卡尔树(后称为新树)而言,子树内的点都可以吃掉,所以如果能做出这么一个结构,对每个查询x,就只需要找到新树上x到根的路径上最深的\(a[fa[x]]-sum[x]>y\)的点对应的x,答案为\(sum[x]+y\)
- 建树方法:
类似点分治,但是可以直接并查集。
按照点权从小到大排序,每次加入的点就必然是当前子树的最大值(根),维护父子节点信息即可。
(其实树上笛卡尔树就是一直给子树换根,根为子树内的最大值的一个做法) - 倍增维护:
考虑到所求内容并非单调,但发现我们只关心最大值和y的关系,所以维护最大值就有单调性可以倍增。 - 代码里还学了新的排序方法不用写struct了,处理倍增也不用递归了
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ll long long
#define pii pair<int, int>
const int N = 1e5 + 7, INF = 1e9+7, MOD = 998244353;
int n, m, fa[N][22], rt, id[N];
ll y, q, sum[N], mx[N][22], a[N], b[N];
int ff[N];
bool vis[N];
bool cmp(const ll &x, const ll &y) {
return a[x] < a[y];
}
vector<int> E[N];
struct DSU{
int find(int x){return (x == ff[x]? x : ff[x]= find(ff[x]));}
}dsu;
void init() {
a[0] = 1e17;
for (int i = 1; i <= n; i++) {
vis[i] = sum[i] = 0, ff[i] = i, fa[i][0] = 0;
E[i].clear();
}
for (int i = 0; i <= 19; i++) mx[0][i] = 1e17;
}
void solve() {
cin >> n >> q;
init();
for (int i = 1; i <= n; i++)
cin >> a[i], id[i] = i;
for (int i = 1; i <= n; i++)
cin >> b[i];
for (int i = 1; i < n; i++) {
int u, v; cin >> u >> v;
E[u].push_back(v); E[v].push_back(u);
}
sort(id + 1, id + n + 1, cmp);
rt = id[n];
for (int i = 1; i <= n; i++) {
sum[id[i]] = b[id[i]];
for (auto j : E[id[i]]) {
if (vis[j]) {
int f = dsu.find(j);
ff[f] = id[i];
fa[f][0] = id[i];
sum[id[i]] += sum[f];
mx[f][0] = a[id[i]] - sum[f];
}
}
vis[id[i]] = 1;
}
fa[rt][0] = 0;
for (int j = 1; (1 << j) <= n; j++)
for (int i = 1; i <= n; i++) {
fa[i][j] = fa[fa[i][j - 1]][j - 1];
mx[i][j] = max(mx[i][j - 1], mx[fa[i][j - 1]][j - 1]);
}
while(q--) {
int x;
cin >> x >> y;
if (y >= a[x]) {
for (int i = 19; i >= 0; i--) {
if (fa[x][i] && mx[x][i] <= y) {
x = fa[x][i];
}
}
cout << sum[x] + y << endl;
}
else cout << y << endl;
}
init();
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T = 1;
cin >> T;
while(T--) {
solve();
}
}
1007
组合数学推式子
枚举mex可得所求内容为:\(\sum_{mex=0}^{k}mex\frac{C_{n-mex}^{k-mex}}{C_n^k}\)
因为组合数带因数求和无法计算(佬说的)所以想到拆分成mex个组合数相加
然后分为k+1层,因为\(C_{n-mex}^{k-mex}=C_{n-mex}^{n-k}\),对于相邻mex底数差1,上面的数相同,所以可以求和为一个组合数
对于若干层也可以用这样的方式求和,最后结果是两个组合数相除。
1008
AC自动机
好像有人用sam做,但是我只会ac自动机。
首先要对前缀和后缀进行减枝,这样trie树上每个节点要么不被接受,要么只有一个可接受状态。
用ac自动机做的话无法避免会算重的问题,可以通过在后缀ac自动机上跑所有前缀字符串来减去算重的部分。
这题暴力跳fail边不会t,大概是因为题目不能支持拓扑排序优化
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 1e6 + 20;
int l, r;
string pre[N], suf[N], ss;
struct AC{
int t[N][26], fail[N], tot, len[N], id[N];
ll mat[N];
bool vis[N];
void init() {
for (int i = 0; i <= tot + 10; i++) {
for (int j = 0; j < 26; j++) t[i][j] = 0;
fail[i] = len[i] = id[i] = 0;
}
tot = 0;
}
void build_trie(string s, int idx){
int p = 0;
for (int i = 0; i < s.size(); i++) {
int c = s[i] - 'a';
if (t[p][c]) p = t[p][c];
else t[p][c] = ++tot, p = tot;
}
// cout << s << ' ' << p <<" ???"<< endl;
len[p] = s.size();
id[p] = idx;
}
void build_AC() {
queue<int> q;
for (int i = 0; i < 26; i++) {
if (t[0][i]) q.push(t[0][i]);
}
while(!q.empty()) {
int p = q.front();
q.pop();
for (int i = 0; i < 26; i++) {
if (t[p][i]) {
fail[t[p][i]] = t[fail[p]][i];
q.push(t[p][i]);
}
else t[p][i] = t[fail[p]][i];
}
}
}
void rebuild_trie() {
queue<int> q;
for (int i = 0; i < 26; i++)
if (t[0][i]) q.push(t[0][i]);
while(!q.empty()) {
int p = q.front();
q.pop();
if (id[p]) {
vis[id[p]] = 1;
continue;
}
for (int i = 0; i < 26; i++)
if (t[p][i]) q.push(t[p][i]);
}
init();
}
int que(string s) {
int res = 0;
int u = 0;
for (int i = 0; i < s.size() - 1; i++) {
u = t[u][s[i] - 'a'];
for (int j = u; j; j = fail[j]) {
if (len[j]) res++;
}
}
return res;
}
void quep(string s){
int u = 0;
for (int i = 0; i <= s.size(); i++) mat[i] = 0;
for (int i = 0; i < s.size(); i++) {
u = t[u][s[i] - 'a'];
for (int j = u; j; j = fail[j]) {
if (len[j]) mat[i - len[j] + 1] = id[j];
}
}
}void ques(string s){
int u = 0;
for (int i = 0; i <= s.size(); i++) mat[i] = 0;
for (int i = 0; i < s.size(); i++) {
u = t[u][s[i] - 'a'];
for (int j = u; j; j = fail[j]) {
if (len[j]) mat[i - len[j] + 1]++;
}
}
}
}p, s;
void solve() {
cin >> l >> r;
for (int i = 0; i <= max(l, r); i++) s.vis[i] = p.vis[i] = 0;
p.init(); s.init();
for (int i = 1; i <= l; i++) {
cin >> pre[i];
p.build_trie(pre[i], i);
}
for (int i = 1; i <= r; i++) {
cin >> suf[i];
reverse(suf[i].begin(), suf[i].end());
s.build_trie(suf[i], i);
}
p.rebuild_trie(); s.rebuild_trie();
for (int i = 1; i <= l; i++) if (p.vis[i]) p.build_trie(pre[i], i);
for (int i = 1; i <= r; i++)
if (s.vis[i]) reverse(suf[i].begin(), suf[i].end()), s.build_trie(suf[i], i);
p.build_AC(); s.build_AC();
cin >> ss;
p.quep(ss); s.ques(ss);
for (int i = ss.size() - 1; i >= 0; i--) {
s.mat[i] += s.mat[i + 1];
// cout << i << ' '<<s.mat[i] << " kkk\n";
}
// cout << '\n';
ll ans = 0;
for (int i = 0; i < ss.size(); i++) {
if (p.mat[i]) {
// cout << i << ' ' << p.mat[i] << " xxx\n " ;
ans += s.mat[i];
ans -= s.que(pre[p.mat[i]]);
}
}
cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T; cin >> T;
while(T--) solve();
}
1010
期望dp,对答案dp取min,转移时只需考虑当前时间是否需要提交

浙公网安备 33010602011771号