山东省队二轮集训10days
就不单发了,10天的都会写在这里
对于一轮的后面几天,因为都是CF原题,所有没有单独写出来
Day1
测试
麻将
一听才知道是傻逼题,直接枚举长度 (n),然后哈希就好,base是 (2)
可以证明 (n) 不会很大
德州扑克
如有错误欢迎指正=-=
对于为正的与斜率为负的线段分别考虑
设原来的矩形为 ((i,j,k,l)),利用二维前缀和,我们现在只需要维护矩形 ((1,1,k,l),(1,1,i,l),(1,1,k,j),(1,1,i,j))
发现题目保证了任意两条线段没有交点或是重合的部分
也就是说如果用一条垂直于坐标轴的扫描线来扫的话,线段的次序是不会发生变化的
维护左端点坐标,然后乘以现在的坐标即可
对于与上边界相交的情况与与右端点相交的情况类似
而与左或下端点相交的情况,由于我们利用了前缀和,这种情况不存在
而斜率为负的线段只需要反过来扫
将棋
对于询问很少的情况,我们只需要利用树序,插入BIT判断即可(反正我是这么做的)
对于点集很小的情况,我们只需要统计一条链对另一条链的贡献即可
于是把这两种中和一下就行了呗
杂题
Uoj群里看到的神秘题
我们用区间所有出现过的元素去异或区间出现奇数次的数的异或和
利用异或的性质,发现后者等价于区间异或和
我们现在要维护的仅仅是区间出现过的元素的异或和
用莫队维护时间复杂度是根号的
JOISC2021 饮食区
离线,对于每个序列分别考虑,也即假设现在考虑到了第 (i) 个序列,我们把与这个序列有关的修改询问全部堆到一起考虑
然后就很简单了,因为我们有出队操作,也就是说这个队列的队头,或称序列的左端点是变化的,于是我们就要维护左端点
现在找到最后一个小于 (0) 的位置,也就是左端点的位置了
对于一次修改,不管加入多少人,我们将它看成一个点,然后就可以很方便的二分维护了
某经典问题
正难则反套路题,这个蒟蒻因为正着硬刚死了
如果我们正着考虑,发现是非常不容易考虑的,因为DAG上的路径条数会指数级增长
于是我们逆着考虑,从终止点回溯,然后合并可持久化平衡树,之后取前 (k) 个点就行了
查找 Search
一开始的思路:全部减去 (dfrac w 2),原问题变为询问区间内是否有数相加为 (0)
莫队维护正数数组与负数数组即可,复杂度根号
但是屑lxl没写强制在线
设 (pre_i) 为距离位置 (i) 最近的,与位置 (i) 上的数相加为给定数的位置
假设这是一个线段 ([pre_i,i]),那么我们只需要维护不交与部分交的线段即可,对于包含的线段,我们取这个存在于这个包含关系中最短的一条
于是会发现每次的修改影响是 (O(1)) 的
对于存在性判定,我们只需要判断 (iin[l,r],max pre_i) 即可
Uoj207
共价大爷游长沙是LCT经典题,这里就不写了
Ynoi2008 stcm
考虑重链剖分,于是轻链连接的子树大小减半,我们可以分别考虑
如果是重链,我们直接暴力处理
否则对于每个点,利用其大小作为权值,仿照哈夫曼树的思想,建立一棵二叉树
分治处理即可
Day2
测试
多项式时间哈密顿回路
场上发现是傻逼题,不过好像有点点卡常?
AI高考数学134分
不难,场上就有十几个人切了
扫描右端点,然后我们维护左端点的答案
此时我们维护一个单调栈
因为我们会发现最小值随着左端点向左移动单调不增,最大值单调不减
此时我们就可以维护这个栈,显然时间复杂度是均摊线性的
对于三个序列的情况,我们分别维护三个栈,然后维护每个位置 (a_ib_ic_i) 的乘积
这时我们会发现,每一次弹出会对一段区间造成影响,而影响贡献是固定的,不随位置的改变而改变
因此我们线段树维护,复杂度一个 (log)
自动化leetcode
又是正难则反套路题,这个蒟蒻赛场上又双叒叕在正着想
现在考虑加入一个变换时发生的情况,左边 (ge a_i) 的会原封不动的减掉,而小于的则会反转
于是利用可持久化平衡树维护即可
对于区间进行分治是经典套路
杂题
CF. 464E
一开始看的时候就觉得是傻逼题,没想到差一点点就是了XD
对于比较,我们利用hash,这里不再赘述
对于加法,我们会发现每次只会发生一段进位(由于原来是 (2^{x_i}))因此我们仅仅需要维护一个主席树,满足单点赋值,区间赋 (0)
对于区间赋 (0),我们可以在一开始就新建一棵空主席树,用的时候直接连边即可
CF. 1446D2
考虑从全局缩,然后现在我们统计出现次数最多的数的个数
设现在最多的次数为 (t)
如果有一个,那么我们肯定是会删除这个数
如果有多个,那么我们肯定是删除一些数,使得恰好有两个数出现的次数为 (t)
所以不管怎样,众数是一定会出现在答案里的,设其出现了 (i) 次
现在我们考虑另一个数,设其出现了 (j) 次
我们将两个数两两配对,此时利用一个数据结构维护前驱删点即可
CF. 765F
考虑形成的有意义的二元组即可
如果三个点 (i,j,k) 不降,那么只需要考虑 ((i,j)) 即可
而如果形成了山峰形,可以发现要么限制了一个下界,要么现在的值域会减半
总共有贡献的二元组是 (log) 的
CF. 176E
不难题
假设有一棵树,我们在树上任取 (k) 个点,那么这 (k) 个点的两两联通最小边集,就是按照 (dfs) 序排序之后的序列按顺序摆放成一个环之后,两两相邻的点的距离和的一半
然后就直接做就行了
CF. 453E
设 (a_i) 表示第 (i) 个位置充 (a_i+1) 秒的能之后满
Day 3
测试
前沿哥
发现是傻逼题
自学生
tyy写的分块,于是问了问,发现好像很玄学
考虑一个点的出边,转为维护这条线段是否与这个块有交
记录dfs序的min,用它去更新low,然后判割边
为了消除冗余计算,我们还要防止重复扫描,而这直接记录位置就可以了
王晓明
考场上想到用set维护的,但是发现不行,因为一个菊花图就可以卡掉
我们考虑点边转化,将每个点的点权转为边权
具体的,我们将每个点的颜色转化成其与父节点连的边的颜色
这样,在单点修改的时候,修改量是 (1)
现在我们考虑维护这个东西,不难发现,一个边连通块中可能会有其他颜色的点,我们称之为杂点
而这,正是连通块的边界,于是我们维护每个连通块中出现的杂点
对于修改连通块的颜色,就转化为了平衡树的合并,查询的话就是维护一堆杂乱的信息即可
杂题
LG. 3245
特判 (2,5)
我们发现一个串 (s_{l,r}) 能够被 (p) 整除,说明 (pre_r-pre_{l-1}) 模 (p) 为 (0)
也就是说 (pre_r) 与 (pre_{l-1}) 模 (p) 都是同一个数
区间不同数的个数即可
LG. 3604
异或维护,枚举 (0,2^0,2^1,cdots,2^{25}) 即可
经典问题
区间逆序对
二次离线即可
经典问题
根号分治,发现度数大于 (sqrt m) 的点不会超过 (sqrt m) 个
于是我们每次修改,暴力枚举即可维护
而对于度数小于 (sqrt m) 的点,直接暴力查询即可
SHOI2006 homework
对 (y) 进行根号分治
发现 (sqrt n) 以内的 (y) 可以每次修改都更新一次
而对于以上的,根据 (mod) 的性质,发现我们只需要维护前驱后继即可
LG. 3591
首先学到了树剖 (k) 级祖先,就是对每个重链开一个数组,维护这个数组的最大值即可
然后我们对 (k) 进行根号分治
对于小的,我们将其离线,然后固定 (k),每个都跑一边,然后复杂度 (O(nsqrt n))
而对于大的,我们会发现跳的次数是 (O(1)sim O(sqrt n)) 的,然后直接跳就行了
Ynoi2011 D2T2
我们发现 (b) 是可以进行根号分治的
对于比较小的 (b),我们直接利用莫队,对每一个 (b) 维护一个bitset
而对于较大的 (b),我们维护区间的bitset,然后将其不断更新
Day4
咕了,没写完
Day5
草越来越废了
今天写summary去了,所以这里写的很少
测试
gp
发现是傻逼题,场上怒码5KB切了
放代码纪念一下/cy
#include<bits/stdc++.h>
#define fx(l,n) inline l n
#define int long long
const int N=3000010,M=500010;
using std::multiset;
using std::max;
int bfc,bfv,colpl[M],colpre[M],colsuf[M],c[N],v[N],n,m,k,ont[M];
fx(int,gi)(){
char c=getchar();int s=0,f=1;
while(c<'0'||c>'9'){
if(c=='-') f=-f;
c=getchar();
}
while(c>='0'&&c<='9') s=(s<<3)+(s<<1)+(c-'0'),c=getchar();
return s*f;
}
struct info{
int col,pl;
info(int b,int c){col=b,pl=c;}
fx(bool,operator) < (const info &b) const{
return col==b.col?pl<b.pl:col<b.col;
}
};
struct Tree{
Tree(){col=val=pre=suf=premx=l=r=0;}
int col,val,pre,suf,premx,l,r;
}t[N];
struct BIT{
int t[N];
fx(int,lb)(int x){return x&-x;}
fx(void,add)(int pl,int val){
while(pl<=n){t[pl]+=val;pl+=lb(pl);}
}
fx(int,ask)(int pl){
int sum=0;
while(pl){sum+=t[pl];pl-=lb(pl);}
return sum;
}
}smm;
multiset<info>ps;
fx(void,update)(int now){
t[now].premx=max(t[now<<1].premx,t[now<<1|1].premx);
}
fx(void,build)(int now,int l,int r){
if(l==r){
ont[l]=now;
t[now].l=t[now].r=l;
t[now].col=c[l],t[now].val=v[l];
t[now].premx=t[now].pre=colpre[l];t[now].suf=colsuf[l];
ps.insert(info(c[l],l));
return ;
}
int mid=(l+r)>>1;
build(now<<1,l,mid);
build(now<<1|1,mid+1,r);
t[now].l=t[now<<1].l;t[now].r=t[now<<1|1].r;
update(now);
}
fx(void,updlink)(int now){
if(~t[now].pre) t[ont[t[now].pre]].suf=t[now].suf;
else if(~t[now].suf) t[ont[t[now].suf]].pre=-1;
if(~t[now].suf) t[ont[t[now].suf]].pre=t[now].pre;
else if(~t[now].pre) t[ont[t[now].pre]].suf=-1;
}
fx(void,getlink)(int now,int pl){
auto suf=ps.lower_bound(info(t[now].col,pl));
if(suf!=ps.end()&&(*suf).col==t[now].col) t[now].suf=(*suf).pl;
else t[now].suf=-1;
if(suf==ps.begin()) t[now].pre=-1;
else if((*(--suf)).col==t[now].col) t[now].pre=(*suf).pl;
else t[now].pre=-1;
}
fx(void,uuupppddd)(int now){
if(t[now].l==1&&t[now].r==n) return;
else {
if(t[now].l^t[now].r) update(now);
uuupppddd(now>>1);
}
}
fx(void,change)(int now,int pl,int c,int v){
if(t[now].l==t[now].r){
updlink(now);
if(~t[now].suf) t[ont[t[now].suf]].premx=t[ont[t[now].suf]].pre,uuupppddd(ont[t[now].suf]);
smm.add(t[now].l,v-t[now].val);
ps.erase(info(t[now].col,t[now].l));
t[now].col=c;t[now].val=v;
getlink(now,t[now].l);
if(~t[now].suf) t[ont[t[now].suf]].pre=pl;
if(~t[now].pre) t[ont[t[now].pre]].suf=pl;
t[now].premx=t[now].pre;
if(~t[now].suf) t[ont[t[now].suf]].premx=t[ont[t[now].suf]].pre,uuupppddd(ont[t[now].suf]);
ps.insert(info(t[now].col,t[now].l));
return ;
}
if(pl<=t[now<<1].r) change(now<<1,pl,c,v);
else change(now<<1|1,pl,c,v);
update(now);
}
fx(int,downdif)(int now,int lim){
if(t[now].l==t[now].r) return t[now].pre<=lim?t[now].l:-1;
int pl;
if(t[now<<1].premx<=lim) return (pl=downdif(now<<1|1,lim))==-1?t[now<<1].r:pl;
else return downdif(now<<1,lim);
}
fx(int,updif)(int now,int lim){
if(t[now].l==1&&t[now].r==n) return n;
int pl;
if(now&1) return updif(now>>1,lim);
else if(t[now|1].premx<=lim) return updif(now>>1,lim);
else return (pl=downdif(now|1,lim))==-1?t[now].r:pl;
}
fx(int,query)(int now,int pl,int k){
if(t[now].l==t[now].r){
int pls[k+1],ret,maxs;
Tree spre[k+1];
pls[0]=updif(now,pl-1);ret=maxs=smm.ask(pls[0])-smm.ask(t[now].l-1);
for(int i=1;i<=k;i++) spre[i].col=-1,pls[i]=0;
for(int i=1;i<=k;i++){
if(pls[i-1]<=n-1){
if(t[ont[t[ont[pls[i-1]+1]].pre]].val<=t[ont[pls[i-1]+1]].val) ret-=t[ont[t[ont[pls[i-1]+1]].pre]].val-t[ont[pls[i-1]+1]].val;
else{
spre[i]=t[ont[pls[i-1]+1]];
t[ont[pls[i-1]+1]]=t[ont[t[ont[pls[i-1]+1]].pre]];
}
if(pls[i-1]<n-1){
if(t[ont[pls[i-1]+2]].pre<pl){
pls[i]=updif(ont[pls[i-1]+2],pl-1);
ret+=smm.ask(pls[i])-smm.ask(pls[i-1]+1);
} else pls[i]=pls[i-1]+1;
}
maxs=max(maxs,ret);
} else break;
}
for(int i=1;i<=k;i++) if(~spre[i].col) t[ont[pls[i-1]+1]]=spre[i];
return maxs;
}
if(pl<=t[now<<1].r) return query(now<<1,pl,k);
else return query(now<<1|1,pl,k);
}
signed main(){
freopen("gp.in","r",stdin);
freopen("gp.out","w",stdout);
n=gi(),m=gi();
for(int i=1;i<=n;i++) c[i]=gi(),v[i]=gi(),smm.add(i,v[i]);
for(int i=1;i<=n;i++){
if(colpl[c[i]]) colpre[i]=colpl[c[i]];
else colpre[i]=-1;
colpl[c[i]]=i;
}
for(int i=1;i<=n;i++) colpl[i]=0;
for(int i=n;i;i--){
if(colpl[c[i]]) colsuf[i]=colpl[c[i]];
else colsuf[i]=-1;
colpl[c[i]]=i;
}
build(1,1,n);int pl,k,ty;
while(m--){
ty=gi();
if(ty==1) {
pl=gi();bfc=gi(),bfv=gi();
change(1,pl,bfc,bfv);
} else {
pl=gi();k=gi();
printf("%lld
",query(1,pl,k));
}
}
}
of
利用阿次自动机,主元列出方程,其他的利用主元的线性表示,然后消元
lxl
压位,分组处理
杂题
脑筋急转弯测试题
发现由于两笔之间必须要有转折,所以每个位置知道了左上与右上可以唯一确定这个格子四条边上有没有边
那么我们从左上第一个点推就行了,因为那个格子的左上与右上的情况我们是知道的,都没有边
Day6
硬刚T2死了
测试
蛋糕
发现如果 (x) 确定的话,现在就相当于我们维护一个十字形的交
十字形也就相当于合法的情况,但是这并不是很好维护
于是我们考虑不合法的情况的并
现在随着 (y) 的扫描,四个边角都是需要我们维护折线的
发现每一个方块在折线上出现的时间是连续的
且两个影响到它的时间段交出来最多还是两段
于是现在我们用一个set去维护,然后用线段树维护 (y) 轴现在扫到的地方的线段的长度
折线上一个位置每一次的退出,或是加入都是需要对这个区间进行区间加法的
现在我们维护显现出的长度的最大值,判断中间是否有空区域,本题就做完了
杂题
subset
我们定义子集和为一个子集中的所有数字相加的和
考虑 (x) 个数字的非空子集,是有 (2^{x}-1) 个的,现在我们要保证这 (2^x-1) 个子集任意两个不同的子集和不同
我们发现值域大约是在 ([1,100000x]) 之中的,现在的任务就是求解 (2^x-1ge 100000x)
发现 (x=22) 就可以了,然后我们选取 (22) 个子集暴力枚举即可
中位数
我们可以变换整个数组,如果其左边的数比这个数大,那么我们设其为 (1),否则为 (0)
现在观察,如果原序列中的一个子串,它的长度大于等于 (2),那么此时这个子串是一定不会被改变的
现在,会改变的地方就是 (010101cdots),也即 (01) 交叉的地方
发现每次变换,这个串长度都是会减的,于是我们维护最长的串就可以了
走路
如果设 (f_{i,j}) 表示一条路径从 (i) 走到 (M),另一条路径从 (M) 走到 (j) 的最小点权和
对于一种情况以外的所有情况,它都是可以转移的
唯一不可以的情况是 (j) 会走 (i) 走过的路
那么现在我们用 (f_{i,j}+ ext{dis}(j,i)) 更新一下 (f_{j,i}) 就好
CCPC final 2019 C
设 (f_{i,j}) 表示打完了前 (i) 个字符,现在粘贴板上是这前 (i) 个字符形成的串的长度为 (j) 的后缀
现在我们可以从 (f_{i,j}) 转移到 (f_{i+1,0})
还可以从 (f_{i,j}) 转移到 (f_{i-j+1,i}),表示把现在复制的东西再粘贴一次
最后我们可以从 (f_{i,j}) 转移到 (f_{i+k,k}),表示复制前面的东西,然后再粘贴
CCPC final 2019 H
枚举单个的 (a),然后让其连 (s) 后或是 (d) 前,然后求 (min(|sa|,|d|)+min(|s|,|ad|))
最后考虑环的情况
注意两组集合只要有一组相等就需要考虑环的情况
CCPC final 2019 B
我们利用队列维护 (k) 然后一直走到 (n+1) 步,现在如果还没有到终点说明在绕圈圈,无限绕下去即可
Day8
杂题
CF. 1503E
首先我们考虑到第一种情况便是两个蓝色连通块无交,也就是形成了两个单峰的蓝色块
然后满足两个两个蓝色块的高度之和等于 (m)
枚举 (m,l,r),然后强制 (l) 处于下方靠左的位置
为了不重复,下面的块的右端点起始强行下降一格,上面的块同理,原因显然
根据对称性,(l) 处于下方靠右的位置即是上一种情况的镜像
于是此部分的答案为
最后乘以二
第二种情况即是两部分相交,高度和大于 (m)
此时答案为
依然要乘以二
最后前缀和优化即可
后面的咕掉了
持续更新中...