pkusc&thusc 2023
pkusc2023
D1T1
给两个串 \(S,T\),对每个 \(i\) 求出 \(S_i\) 换成 \(T_i\) 后 \(S\) 的 border。
\(|S|,|T|\le 10^6\)。
场上有点 nt 没想出来。
这个题,非常显然,我们可以直接 hash。
另外一种做法是,考虑一个长度会贡献到哪些位置。你发现如果三个位置不同就寄了,两个不同必须是重合部分,一个不同就比较好,零个不同还得看自己有没有被干掉。反正分讨一下。
D1T2
\(n\) 个人,第 \(m\) 个是狼人,剩下随机一人是预言家,预言家知道自己的身份。他每天随一个区间知道有没有狼,期望几天确定狼是谁。
\(n\le 150\)。
前情提要:我大概知道这是个 minmax 容斥题。
我们先考虑最朴素的做法,你发现,预言家知道的信息是一个集合 \(S\) 代表当前有狼人嫌疑的人都有谁。显然的一点是当 \(S=1\) 的时候你就知道狼人是谁了。然后你发现预言一个区间 \([l,r]\) 带来的影响,如果区间内有狼人,那就是 \(S\leftarrow S\cap [l,r]\),否则就是 \(S\leftarrow S\cap \lnot[l,r]\)。根据这个,我们可以列出转移,高斯消元即可。但复杂度可能是 \(O(2^{3n})\),明显无法接受。
现在我们可以考虑两条路,第一条是去优化这个东西,看看能不能减少无用状态或者合并一些状态。第二条是 minmax 容斥。
第一条路我稍微想了一下,感觉这个关联还是很紧密的,不太好做,我们尝试第二条路。
你发现如果我们设 \(T_i\) 表示知道第 \(i\) 个人身份的时间,那么要求的就是 \(E(\max(T_i))\),容斥之后要求 \(E(\min_{i\in S}(T_i))\),那就是说这些人的身份一直都不确定。你考虑加上之后仍然无法确定这些人身份的区间有多少个,发现这样的区间要么卡在两个中间,要么这个区间里有狼,并且把所有数都覆盖了。现在,这相当于一个伯努利试验,我们只要算出来这样的区间有多少个就可以了。这就转化成了一个计数问题。
这里还有一个小问题。你发现一个事,\(\max(T_i)=T_m\),所以上面那一车容斥写出来之后是一坨东西等于 0,容了跟容了一样。正当我打算换一个变量去做的时候,我发现另外一个事:\(T_m\ge T_i\),同时存在一个 \(i\neq m,T_m=T_i\)。这个很好理解,如果别的人都确定了,那狼的位置也确定了。所以我们重新把要求的东西写成 \(E(\max_{i\neq m}(T_i))\),然后对这个进行 minmax 容斥即可。
我们发现这个计数问题是容易 dp 解决的。具体来说,我们枚举第一个被选中的人在哪里,设 \(dp_{i,s}\) 表示最后一个被选中的是 \(i\),区间数是 \(s\) 的方案数。这样我们可以在 \(O(n^5)\) 时间内做出来,是一个质的飞跃。
我们注意到,这个方案的第一部分,也就是卡在两个之间的,这是好做的,可以 \(O(n^4)\) 解决。问题出在第二部分,区间里有狼并且把所有数覆盖,这个方案数计算需要我们同时知道 \(l,r\),多出来一个 \(n\) 就来自这里。
我们考虑把这个贡献拆开。拆不开。
看了 sol。
我们记 \(f(x)=\frac{(x-1)x}{2}\),把狼人看成选过的,没有选的点的连续段长度依次是 \(w_1,\dots,w_t\),我们的答案是 \(\sum f(w_i)+(w_1+1)(w_t+1)\),然后你发现 \(f(w_1)+f(w_t)+(w_1+1)(w_t+1)=f(w_1+w_t+1)\),这相当于把这个东西看成一个环,并且强制一个位置不能被选。我们知道,狼人位置一定被选,所以把环从这个位置断开就可以了,这样我们可以做到 \(O(n^4)\)。这个做法肯定是依赖于这个 \(f\) 的形式的,但是还是很奇妙,让我再想想。
如果没有 \(f\) 的这个性质,我们可以考虑从狼人位置向两边 dp,然后你把每个 \(dp_{i,s}\) 看成多项式 \(F_i,G_i\),这样我们就是枚举起点和终点 \(l,r\),把这两个多项式相乘 \(F_l\times G_r\),这样可以做到 \(O(n^4\log n)\)。
D1T3
给 \(n\) 个点的树,每个非叶节点都有偶数个儿子。每个点的权值有 \(p_i\) 概率为 1,\(1-p_i\) 为 0,随机完权值之后,从下到上每个点把权值修改成自己和儿子的众数。要支持单点修改,每次求最后根是 \(1\) 的概率。
\(n\le 2\times 10^5\),\(q\) 小一点。
这个看起来像是个动态 dp。
你先考虑静态版本能做到多少。正常而言你需要 dp 一轮,这个复杂度是 \(O(n^2)\) 的。如果你把每个儿子看成一个 \((1-p_v+p_vx)\) 的话,那这个就是做一个分治 NTT,复杂度是 \(O(n\log^2n)\)。
你考虑修改操作,暴力修改会影响 \(O(n)\) 个点的信息,每个点会除一个二项式再乘一个二项式,这个感觉很痛苦,因为你这么做是 \(O(n)\) 的,一次修改就是 \(O(n^2)\) 的,还不如每次重构整棵树来的快。如果我们在外面套上动态 DP 的话,那可能你需要修改 \(O(\log n)\) 个点的信息,每次修改 \(O(n)\) 左右,复杂度可能是 \(O(n\log n)\),还是很逊。
后面不会了,sol 里这个题也没怎么讲。
D2T1
维护一个序列,支持如下操作:在编号 \(x\) 后插入一个人,把一次插入操作中的 \(x\) 修改为 \(y\),查询编号为 \(x\) 的人前面多少人。
\(n\le 3\times10^5\)。
我们先找到一个方便表示这个操作的方法。你发现这相当于是一个树形结构,然后对应的序列就是按照编号从大到小的顺序遍历自己的儿子。我们一次修改操作相当于把一个子树拿出来接到某个点下面。然后查询操作是一个到根节点的链上的点数加上到根节点的链上在它之前的儿子的 sz 和。
然后这个东西很神秘啊,要对一个动态树做一个链求和是我做不到的东西,可能 LCT 能做?这个时候就需要我们回到原题再看看,你发现每次相当于是把一段区间平移到另外一个位置,我们只要知道这个区间的具体位置,这个操作是可以很方便用 Treap 维护的。而我们刚刚的做法其实已经提供了一个给出区间具体位置的方法:我们只需要查询子树 sz 即可。
这要真在场上发现又回到了查询子树 sz 的问题,我就上根号做法了。但问题是我现在知道这个题是 log 做法,让我再想想。
不会了,sol 里面说用 ETT 就可以了。呃呃我也知道啊。
翻了翻别人的游记,里面有一个说法,说是你去维护括号序就可以了。这个确实很有道理,那我们还剩下一个问题就是怎么利用括号序算出来一个点的 dfn 序。这个也比较简单,你考虑把左括号当成 1 右括号当成 0 就可以了。
呃呃,感觉这个东西我好像确实不太想的出来。积累一下吧。
下午写一下这个东西。
D2T3
给定素数 \(P\) 和 \(5\) 个方程,每个方程形如:
\[a_ix+(x\bmod b_i+1)(x^i\bmod \lfloor\sqrt{x}\rfloor)\equiv c_i\ (\bmod \ P) \]找到 \([1,P-1]\) 的解,保证唯一,保证数据随机。
\(9\times 10^{17}\le P\le 10^{18},1\le a_i,b_i,c_i<P,1\le b_i\le 10 或 91\le b_i\le 100\)。
为什么先说 T3 呢?因为 T2 是原神题。
这个题一脸数论不可做的样子,我们随便写两下好了。
部分分里有一个 \(b=1,x\le 10^{16}\),我们就写这一档好了。
我们考虑枚举 \(x^i\bmod\lfloor\sqrt{x}\rfloor\),显然这个数不会超过根号。
然后方程变成了 \(a_ix+t\equiv c_i\ (\bmod P)\),我们只需要求逆元就可以把 \(x\) 解出来,然后你去 check 一下这个 \(t\) 是不是满足要求的,并且再代入另外几个方程试一下就行了。那我们基本上会做这一档了。
接下来想点不切实际的,我们发现上面的算法其实只利用了一个方程,然后去 check。那我们只关心最简单的第一个方程,看看能不能做好一点。
不太行,就这吧。
D2T2
在一款开放世界冒险游戏中,一个角色有 \(L\) 个圣遗物位置。第 \(i\) 个位置有 \(n_i\) 种,暴击率和暴击伤害分别是 \((a_{i,1},b_{i,1}),\dots,(a_{i,n_i},b_{i,n_i})\)。现在有 \(Q\) 次询问,每次给出初始暴击率和暴击伤害 \(A,B\),你需要每个位置选一个圣遗物,设最后圣遗物暴击率总和为 \(a\),暴击伤害总和为 \(b\),那么期望伤害是 \(D=(A+a)(B+b)\)。
给出一种方案,使得你的 \(D\) 与 \(D\) 的最大值差不超过 \(2500\)。
\(a,b\) 小数点后最多两位。
多测,\(1\le \sum L\le 50000,1\le a,b\le100,n_i\le 10,Q\le 10,1\le A,B\le 10^7\)。
第一个想法是直接背包,但是复杂度反正比较爆。
你发现这个题的问法太神秘了!一点想法都没有!我们想想能不能乱搞!
这东西感觉有点像凸包,想一想能不能证。好像很难说。
我们随机贪心。按照随机顺序选取圣遗物,每次选加上之后最大的那一个!我不知道效果怎么样。
额,这个维护凸包的做法是对的,这里这个 \(2500\) 其实是 \(\frac{V^2}4\),你只需要证明在两个点的连线上的所有点的解不会超过两点解最大值再加 \(\frac{V^2}4\) 即可。
thusc2023
T1
给一个长 \(n\) 序列,\(q\) 次操作,每次区间加,或者查询一个区间至少用多少次区间 \(\pm 1\) 的操作可以变成 \(x\)。
\(n,q\le 3\times10^5\)。
我们肯定先看看没有修改,只有一次全局查询是怎么做的。那你肯定要把每个数改写成需要 \(\pm v\) 的形式。你发现这个时候答案就是每个连续段的最值的绝对值。
首先我们得到的一个启示是,这个 \(x\) 是假的,我们全局减 \(x\),查询 \(0\),全局加 \(x\) 即可。
然后你觉得一个区间的答案好像没什么用简单的值来表示的方法,观察到问的是区间进行多少次操作,我们猜测这个信息可以合并。事实上确实可以。那我们写一棵线段树就行了。
看了一眼 sol,这个做法更好一点。他发现区间的答案是在前后加一个 \(x\) 之后,相邻两数差的绝对值之和,再除以 2。事实上我们也可以推出这个结论,因为这个相当于走上最值再走下来。
T2
给一个 \(n+m+1\) 点带权无向图,边权 \(w_i\) 表示要消耗 \(w_i\) 燃料和时间。\(n\) 个点是游乐园,\(m\) 个点是补给站,还有一个基地,你要按照顺序在每个游乐园表演,这会消耗 \(v_i\) 能量。到达补给站可以花 \(t_i\) 时间补满燃料,基地可以花 \(t_0\) 时间补满燃料和能量。燃料、能量上限分别为 \(L,V\)。
问最短要多少时间表演完。
\(n\le 200,m\le 50\)。
这个题我第一眼看觉得是很厉害的图论题,因为漏看了按顺序的条件。
有按顺序之后,我们肯定是要做一个 dp 的。你考虑如何划分 phase,一种是按照返回基地划分一次,一种是按照返回补给站划分。我们可以考虑做两个 dp,外层的按返回基地划分。那我们现在要求出 \(w(l,r)\) 表示最开始在基地补满的状态,现在要依次去 \(l,\dots,r\) 表演,中间不再在基地补给,最后返回基地,最短时间。求出这个之后,我们可以做一个 \(O(n^2)\) dp 就行了。
然后现在我们要求出 \(O(n^2)\) 个 \(w(l,r)\),我们考虑枚举上一次去补给站在什么时候以及去的是哪个补给站,这个复杂度是 \(O(n^3m)\)。有信仰的话其实可以试试能不能过。
额你发现上面这个东西稍微有一点问题,我们需要再求一个 \(f(l,r)\) 表示最开始在基地补满,然后要去 \(l,\dots,r\),并且现在在 \(r\),并且刚刚去了某个站补满。你发现这个也有一点问题,根本原因出在我们不能记录当前剩余的燃料,只有在刚刚补满的情况下可以知道这个,所以我们记录的状态断点必须是补给站。那你发现有点爆,因为我们要记 \(f(l,r,x)\) 表示目前已经走过的点是 \([l,r]\) 当前在补给站 \(x\),然后直观感受你往后需要枚举下一个走到的位置,下一个要去的补给站,但是你发现你不需要枚举下一个位置,因为我们知道下一个补给站是谁之后,一定是走满的(这里好像要用一个距离的三角形不等式),你可以预处理一个 \(to_{r,x}\) 表示走到谁。这样复杂度是 \(O(n^2m^2)\) 的。感觉还是比较简单的。
这个可能还有一点小问题,我们要采用填表法,这样可以找到一个能够更新的区间,然后还要加上一个贡献。你发现贡献是可以拆开的,你随便用一个数据结构优化一下就行了。现在可能还有点意思。
T3
\(n\) 个人 \(m\) 张纸条,纸条初始在 \(p_i\) 人手里,写着数字 \(q_i\)。第 \(i\) 个人有 \(d_i\) 种策略 \(a_{i,j},b_{i,j}\),每一轮,他会把手里的纸条 \(j\) 传给 \(a_{i,q_j\bmod d_i}\),并且把上面的数改成 \(b_{i,q_j\bmod d_i}\)。
问 \(t\) 时刻内,多少个时刻,所有纸条都在 \(1\) 号人手中。
\(n\le 80,m\le 800,d_i\le 54,T\le 10^{100}\)。
首先我们直接把这个图连出来,那很显然是一个基环树森林。我们先把所有在树上的步走完,让所有点都在环上,然后变成了,现在有 \(k\) 个环,点数分别是 \(n_1,\dots,n_k\),这些环里 \(1\) 号的个数分别是 \(m_1,\dots,m_k\)。你设走的步数是 \(x\),发现每个环最多可以列出 \(m_i\) 个模 \(n_i\) 意义下的方程,但是这些方程之间是或的关系。你发现我们直接解的话可能得到 \(2^{\frac d2}\) 个不同的解,这个显然是无法接受的。
但是我们经过仔细思考,发现在做中国剩余定理的时候,如果模数是互质的,那么最终能得到的解数,就是原本的两个方程的解数的乘积。但是你发现这个东西并没有什么用,你并不能拆成模数互质的若干组。
我们试试折半。呃呃不太会,看 sol,没完全懂。数论题扔了吧。
T4
TBD。
code
这里只写了 pkusc d2t1,写了 0.5h 调了 0.5h,wtcl。拍了一堆没问题,简单造了一点大数据跑得还算可以。
#include <set>
#include <map>
#include <list>
#include <queue>
#include <cmath>
#include <time.h>
#include <random>
#include <bitset>
#include <vector>
#include <cstdio>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <memory.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <unordered_map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
#define mk make_pair
#define fi first
#define se second
inline int read(){
int t = 0,f = 1;
register char c = getchar();
while (c < 48 || c > 57) f = (c == '-') ? -1 : 1,c = getchar();
while (c >= 48 && c <= 57) t = (t << 1) + (t << 3) + (c ^ 48),c = getchar();
return f * t;
}
const int N = 3e5 + 10,NN = N << 1;
int n,ncnt,ffa[N],ido[N],idc[N];
mt19937 myrand(1);
set <int> son[N];
struct FHQ_Treap{
int rt,ncnt,rnd[NN],ch[NN][2],sum[NN],sz[NN],val[NN],fa[NN];
int newnode(int x) {++ncnt,rnd[ncnt] = myrand(),sum[ncnt] = val[ncnt] = x,sz[ncnt] = 1;return ncnt;}
void pushup(int x){
sum[x] = val[x],sz[x] = 1;
if (ch[x][0]) sum[x] += sum[ch[x][0]],sz[x] += sz[ch[x][0]];
if (ch[x][1]) sum[x] += sum[ch[x][1]],sz[x] += sz[ch[x][1]];
}
pair<int,int> split(int rt,int x){//<=x,>x
pair<int,int> ans = mk(0,0);
if (!rt) return mk(0,0);
if (sz[ch[rt][0]] + 1 <= x){
ans = split(ch[rt][1],x - sz[ch[rt][0]] - 1);
ch[rt][1] = ans.fi,ans.fi = rt,fa[ch[rt][1]] = rt;
}else{
ans = split(ch[rt][0],x);
ch[rt][0] = ans.se,ans.se = rt,fa[ch[rt][0]] = rt;
}
pushup(rt);return ans;
}
int merge(int x,int y){
if (!x || !y) return x | y;
if (rnd[x] < rnd[y]){
ch[x][1] = merge(ch[x][1],y);pushup(x),fa[ch[x][1]] = x;
return x;
}else{
ch[y][0] = merge(x,ch[y][0]);pushup(y),fa[ch[y][0]] = y;
return y;
}
}
int queryrk(int rt,int x){
int cur = x,ans = sz[ch[x][0]] + 1;
while (cur != rt){
int t = fa[cur];
if (ch[t][1] == cur) ans += sz[ch[t][0]] + 1;
cur = t;
}
return ans;
}
int queryp(int rt,int x){
int cur = x,ans = sum[ch[x][0]] + val[x];
while (cur != rt){
int t = fa[cur];
if (ch[t][1] == cur) ans += sum[ch[t][0]] + val[t];
cur = t;
}
return ans;
}
void ins(int p,int x){
int n1 = newnode(1),n2 = newnode(0);
ido[x] = n1,idc[x] = n2;
int trt = merge(n1,n2);
int rk = queryrk(rt,ido[p]);
pair<int,int> tmp = split(rt,rk);
rt = merge(tmp.fi,merge(trt,tmp.se));
}
void modify(int i,int x){
int l = queryrk(rt,ido[i]),r = queryrk(rt,idc[i]);
son[ffa[i]].erase(i),ffa[i] = x,son[x].insert(i);
pair<int,int> t1 = split(rt,r);
pair<int,int> t2 = split(t1.fi,l - 1);
auto p = son[x].upper_bound(i);
int pre = p == son[x].end() ? ido[x] : idc[*p];
int trt = merge(t2.fi,t1.se);
int pos = queryrk(trt,pre);
pair<int,int> ttt = split(trt,pos);
rt = merge(ttt.fi,merge(t2.se,ttt.se));
}
}TR;
int main(){
#ifndef ONLINE_JUDGE
freopen("in.in","r",stdin);
freopen("out.out","w",stdout);
#endif
n = read();
int n1 = TR.newnode(0),n2 = TR.newnode(0);
TR.rt = TR.merge(n1,n2),ido[0] = n1,idc[0] = n2;
for (int i = 1;i <= n;i++){
int opt = read(),x = read();
if (opt == 1){
TR.ins(x,++ncnt);
ffa[ncnt] = x,son[x].insert(ncnt);
}
if (opt == 2){
int y = read();
TR.modify(x,y);
}
if (opt == 3){
printf("%d\n",TR.queryp(TR.rt,ido[x]));
}
}
return 0;
}

浙公网安备 33010602011771号