深度好题
突然想起来自己还不会回滚莫队,于是刷了几道,顺便昨天刚看的换根也练了几道题,这里挑了几个典型的说一下
窝太菜廖,可能觉得是深度好题的原因仅仅是我会打而已
不过起码没贺题解
思路显然是看了的,我不认为我有独立切掉紫题的能力
当然,不是紫题的题显然是没看题解
P1494 小 Z 的袜子
维护每种颜色在 \([L,R]\) 中出现的次数 \(c_i\),答案即为 \(\frac{\sum\limits C^{2}_{c_i}}{C_{r-l+1}^2}\)
莫队,单点增删的时候只需要维护 \(C^2_{c_{a_{i}}}\) 的改变量即可
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
int c[50001];
int cnt[50001];
int len;
int locate[50001];
int ans=0;
struct ques{
int l,r;
int id;
bool operator<(const ques&A)const{
if(locate[l]==locate[A.l]) return r<A.r;
return locate[l]<locate[A.l];
}
}q[50001];
void prework(){
len=sqrt(n);
for(int i=1;i<=n;++i){
locate[i]=i/len;
}
}
void change(int pos,bool isadd){
// cout<<"change "<<pos<<" "<<isadd<<endl;
ans-=max(0ll,cnt[c[pos]]*(cnt[c[pos]]-1));
cnt[c[pos]]+=(isadd?1:-1);
ans+=max(0ll,cnt[c[pos]]*(cnt[c[pos]]-1));
// cout<<"newans: "<<ans<<endl;
}
pair<int,int> answer[50001];
void printanswer(int l,int r,int id){
int z=ans,m=(r-l+1)*(r-l);
if(z==0){
answer[id]={0,1};
return;
}
int g=__gcd(z,m);
answer[id]={z/g,m/g};
}
signed main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;++i){
cin>>c[i];
}
for(int i=1;i<=m;++i){
cin>>q[i].l>>q[i].r;
q[i].id=i;
}
prework();
sort(q+1,q+m+1);
int l=1,r=0;
for(int i=1;i<=m;++i){
// cout<<"goal "<<q[i].l<<" "<<q[i].r<<endl;
while(r<q[i].r) change(++r,true);
while(l>q[i].l) change(--l,true);
while(l<q[i].l) change(l++,false);
while(r>q[i].r) change(r--,false);
printanswer(q[i].l,q[i].r,q[i].id);
}
for(int i=1;i<=m;++i){
cout<<answer[i].first<<"/"<<answer[i].second<<'\n';
}
}
JOI sc2014_c 歴史の研究
维护每个值出现的次数,可以发现在增加元素的时候,最大值是很方便更新的,唯一的瓶颈在于删除的时候无法快速找到一个新的最大值
因此回滚莫队
其实这好像是我第一次写回滚莫队,之前甚至不知道怎么回滚
现在会了,钦定一个端点在块内,另一个点保持单调性(不回滚)就行
一开始学的就是 stack 写法(相对于 memcpy 写法而言),感觉应该会快很多
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,Q,len;
int a[100001];
int locate[100001],rb[100001];
struct ques{
int l,r;
int id;
bool operator<(const ques&A)const{
if(locate[l]==locate[A.l]) return r<A.r;
return locate[l]<locate[A.l];
}
}q[100001];
int ans[100001];
inline void prework(){
len=sqrt(n);
for(int i=1;i<=n;++i){
locate[i]=i/len;
rb[locate[i]]=i;
}
}
int b[100001];
int cnt[100001];
int vis2[100001];
int cnt2[100001];
int maxn;
int maxn2;
inline void add(int pos){
// cout<<"add "<<a[pos]<<endl;
cnt[a[pos]]++;
maxn=max(maxn,cnt[a[pos]]*b[a[pos]]);
// for(int i=1;i<=n;++i){
// cout<<cnt[i]<<' ';
// }
// cout<<endl;
}
int memoryans;
stack<int>st;
signed main(){
ios::sync_with_stdio(false);
cin>>n>>Q;
for(int i=1;i<=n;++i){
cin>>a[i];
b[i]=a[i];
}
sort(b+1,b+n+1);
int tmp=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;++i){
a[i]=lower_bound(b+1,b+tmp+1,a[i])-b;
}
for(int i=1;i<=Q;++i){
cin>>q[i].l>>q[i].r;
q[i].id=i;
}
prework();
sort(q+1,q+Q+1);
int lastloc=-1;
int l,r;
int backl,backr;
for(int i=1;i<=Q;++i){
if(locate[q[i].l]==locate[q[i].r]){
maxn2=0;
for(int j=q[i].l;j<=q[i].r;++j){
if(vis2[a[j]]!=i){
vis2[a[j]]=i;
cnt2[a[j]]=0;
}
cnt2[a[j]]++;
maxn2=max(maxn2,cnt2[a[j]]*b[a[j]]);
}
ans[q[i].id]=maxn2;
}
else{
if(locate[q[i].l]!=lastloc){
// cout<<"nre locate"<<endl;
memset(cnt,0,sizeof cnt);
maxn=0;
lastloc=locate[q[i].l];
backl=rb[locate[q[i].l]]+1;
backr=rb[locate[q[i].l]];
l=backl;r=backr;
}
while(r<q[i].r) add(++r);
memoryans=maxn;
while(l>q[i].l){
add(--l);
st.push(l);
}
ans[q[i].id]=maxn;
maxn=memoryans;
l=backl;
while(!st.empty()){
cnt[a[st.top()]]--;
st.pop();
}
}
}
for(int i=1;i<=Q;++i){
cout<<ans[i]<<'\n';
}
}
P8078 秃子酋长
暴力思路是直接上莫队,对值域维护一个 set,插入或删除一个元素的贡献都直接在 set 里查前驱和后继,然后统计它们的距离差
暴力
#include<bits/stdc++.h>
using namespace std;
int n,m,len;
int a[500001];
int locate[500001],l[500001],r[500001];
struct ques{
int l,r;
int id;
bool operator<(const ques&A)const{
if(locate[l]==locate[A.l]) return r<A.r;
return l<A.l;
}
}q[500001];
int pos[500001];
int ans[500001];
set<int>s;
int anss;
void prework(){
len=sqrt(n);
memset(l,-1,sizeof l);
for(int i=1;i<=n;++i){
locate[i]=i/len;
if(l[locate[i]]==-1) l[locate[i]]=i;
r[locate[i]]=i;
}
}
inline void modify(int __pos,bool isadd){
if(isadd){
s.insert(a[__pos]);
auto iter=s.lower_bound(a[__pos]);
if(iter!=s.begin() and next(iter)!=s.end()){
anss+=abs(pos[*prev(iter)]-__pos);
anss+=abs(pos[*next(iter)]-__pos);
anss-=abs(pos[*prev(iter)]-pos[*next(iter)]);
}
else if(iter!=s.begin()){
anss+=abs(pos[*prev(iter)]-__pos);
}
else if(next(iter)!=s.end()){
anss+=abs(pos[*next(iter)]-__pos);
}
}
else{
auto iter=s.lower_bound(a[__pos]);
if(iter!=s.begin() and next(iter)!=s.end()){
anss-=abs(pos[*prev(iter)]-__pos);
anss-=abs(pos[*next(iter)]-__pos);
anss+=abs(pos[*prev(iter)]-pos[*next(iter)]);
}
else if(iter!=s.begin()){
anss-=abs(pos[*prev(iter)]-__pos);
}
else if(next(iter)!=s.end()){
anss-=abs(pos[*next(iter)]-__pos);
}
s.erase(a[__pos]);
}
}
int main(){
ios::sync_with_stdio(false);
cin>>n>>m;
for(int i=1;i<=n;++i){
cin>>a[i];
pos[a[i]]=i;
}
for(int i=1;i<=m;++i){
cin>>q[i].l>>q[i].r;
q[i].id=i;
}
prework();
sort(q+1,q+m+1);
int l=1,r=0;
for(int i=1;i<=m;++i){
while(r<q[i].r) modify(++r,true);
while(l>q[i].l) modify(--l,true);
while(r>q[i].r) modify(r--,false);
while(l<q[i].l) modify(l++,false);
// for(auto i:s) cout<<i<<' ';
// cout<<endl;
ans[q[i].id]=anss;
}
for(int i=1;i<=m;++i){
cout<<ans[i]<<'\n';
}
}
由于这个 set 复杂度太高,注意到我们用这个 set 主要是为了在值域上查前驱和后继,然而对值域开一个双向链表也能做到这一点
由于链表不支持随机访问,插入元素可能会十分麻烦,复杂度比 set 还高,但是可以很方便地删除,因此想到做只删除的回滚莫队
具体来说,先开一个链表,维护出全局的链表状态和答案,然后跑回滚的时候,左端点固定到块的左端点,右端点跑递减,回滚的时候(这时候你不得不在链表上做增加操作了,否则复杂度又降回 nq 了,但是这里回滚的增加操作比较方便做,因为你不需要统计答案,只需要还原链表的状态,答案可以直接暴力赋值成回滚前记录的答案)
可能有点卡常这个题,不过作为 lxl 出的题还是太松了
#include<bits/stdc++.h>
using namespace std;
template<typename T>
inline void read(T &x){
int ans=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) ans=ans*10+ch-'0',ch=getchar();
x=ans;
}
#define int long long
int n,m,len;
int a[500001];
int locate[500001],l[500001],r[500001];
int pos[500001];
int ans[500001];
struct ques{
int l,r;
int id;
inline bool operator<(const ques&A)const{
if(locate[l]==locate[A.l]) return r>A.r;
return l<A.l;
}
}q[500001];
struct hdklist{
int act_l,act_r;
int head,tail;
struct list_t{
int l,r;
}ls[500001];
int ans=0;
inline void del(int __pos){
if(__pos!=tail) ans-=abs(pos[ls[__pos].r]-pos[__pos]);
if(__pos!=head) ans-=abs(pos[ls[__pos].l]-pos[__pos]);
if(__pos!=tail and __pos!=head) ans+=abs(pos[ls[__pos].l]-pos[ls[__pos].r]);
if(__pos==tail) tail=ls[__pos].l;
if(__pos==head) head=ls[__pos].r;
ls[ls[__pos].l].r=ls[__pos].r;
ls[ls[__pos].r].l=ls[__pos].l;
}
inline void add(int __pos){ //这个东西是个废物函数
ls[ls[__pos].l].r=__pos;//啥也不管,得从外面记录值然后修改
ls[ls[__pos].r].l=__pos;//因为这么干复杂度很低所以这么干了
}
inline void operator=(const hdklist&A){
for(int i=1;i<=n;++i){
ls[i].l=A.ls[i].l;
ls[i].r=A.ls[i].r;
}
head=A.head;
tail=A.tail;
act_l=A.act_l;
act_r=A.act_r;
ans=A.ans;
}
};
hdklist t1,t2;
inline void prework(){
len=sqrt(2*n);
memset(l,-1,sizeof l);
for(int i=1;i<=n;++i){
locate[i]=i/len;
if(l[locate[i]]==-1) l[locate[i]]=i;
r[locate[i]]=i;
}
}
stack<int>st;
signed main(){
read(n);read(m);
for(int i=1;i<=n;++i){
read(a[i]);
pos[a[i]]=i;
}
for(int i=1;i<=m;++i){
read(q[i].l);read(q[i].r);
q[i].id=i;
}
prework();
sort(q+1,q+m+1);
for(int i=1;i<=n;++i){
if(i!=1) t1.ls[i].l=i-1;
if(i!=n){
t1.ls[i].r=i+1;
t1.ans+=abs(pos[i]-pos[i+1]);
}
}
t1.head=1,t1.tail=n;
t1.act_l=1,t1.act_r=n;
int lastloc=-1;
for(int i=1;i<=m;++i){
if(locate[q[i].l]!=lastloc){
lastloc=locate[q[i].l];
while(t1.act_l<l[locate[q[i].l]]) t1.del(a[t1.act_l++]);
t2=t1;
}
while(t2.act_r>q[i].r) t2.del(a[t2.act_r--]);
int act_l=t2.act_l,act_r=t2.act_r,anss=t2.ans,head=t2.head,tail=t2.tail;
while(t2.act_l<q[i].l){
st.push(a[t2.act_l]);
t2.del(a[t2.act_l++]);
}
ans[q[i].id]=t2.ans;
while(!st.empty()){
t2.add(st.top());
st.pop();
}
t2.act_l=act_l;
t2.act_r=act_r;
t2.ans=anss;
t2.head=head;
t2.tail=tail;
}
for(int i=1;i<=m;++i){
printf("%lld\n",ans[i]);
}
}
CF916E Jamie and Tree
小杂烩题
迎面而来的第一个问题是换根 LCA
尝试分讨,设当前根为 \(r\)
- 如果 \(x,y\) 均在 \(r\) 的子树内,答案为 \(\text{lca}(x,y)\)
- 如果 \(x,y\) 中只有一个在 \(r\) 的子树内,答案为 \(r\)
- 如果 \(x,y\) 均不在 \(r\) 的子树内
- 当 \(\text{lca}(x,r)=\text{lca}(y,r)\) 时,\(x,y\) 与 \(r\) 不在当前根节点的同一颗子树内,此时 \(x,y\) 所在子树形态不变,答案仍为 \(\text{lca}(x,y)\)
- 当 \(\text{lca}(x,r)\neq\text{lca}(y,r)\) 时,\(r\) 一定与某一点位于同一子树内,此时换根后另一点必经过该子树根节点才能到达 \(r\),因此答案为 \(\text{lca}(x,r),\text{lca}(y,r)\) 中较深者
可以发现,分类讨论中出现的公共祖先只有 \(\text{lca}(x,y),\text{lca}(x,r),\text{lca}(y,r)\),因此直接贪心地取深度最大者即可(这三个一定是新树上的合法公共祖先,只是有可能不再是最近的)
迎面而来的第二个问题是换根子树内修改
仍然分讨,设当前根为 \(r\),要修改的子树为 \(x\)
- 当 \(r=x\),直接修改整颗树(注意这种情况必须单独拉出来考虑,放在下面直接按深度差减一算出负数了)
- 当 \(r\) 不在 \(x\) 的子树内,换根后形态不变,仍然修改 \(x\) 的子树
- 当 \(r\) 在 \(x\) 的子树内,此时需要修改的节点是整棵树除去从 \(r\) 到 \(x\) 路径上最靠近 \(x\) 的节点(可以是 \(r\))的子树之后的所有节点,这么说太绕了,但是确实不好表述,建议自行对这种情况画图理解
对于前两种情况,直接树剖后建线段树就行了
对于第三种情况,这个点就相当于是 \((x,r)\) 路径上 \(x\) 点的直属子节点,可以通过类似快速幂的倍增来实现(维护一个 \(fa_{i,x}\)),相当于从 \(r\) 点向上跳 \(deep_r-deep_x-1\)(这就是第一种情况不能分讨进来的原因),情况里的子树减法可以直接转化成贡献相减,修改操作直接对需要减掉的子树做一个负修改就好了
写到这里就做完了,主要是思维难度,码力要求实际上不是很高,没调多久就过了
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,q;
vector<int>e[100001];
int w[100001],wnew[100001];
int fa[20][100001],deep[100001],siz[100001];
int maxson[100001];
int id[100001],idcnt;
int top[100001];
void dfs1(int now,int last){
// cout<<"dfs1 "<<now<<" "<<last<<endl;
fa[0][now]=last;
deep[now]=deep[last]+1;
siz[now]=1;
int maxsonsize=0;
for(int i:e[now]){
if(i!=last){
dfs1(i,now);
siz[now]+=siz[i];
if(siz[i]>maxsonsize){
maxsonsize=siz[i];
maxson[now]=i;
}
}
}
}
void dfs2(int now,int nowtop){
id[now]=++idcnt;
wnew[id[now]]=w[now];
top[now]=nowtop;
if(!maxson[now]) return;
dfs2(maxson[now],nowtop);
for(int i:e[now]){
if(i!=fa[0][now] and i!=maxson[now]){
dfs2(i,i);
}
}
}
inline int kth_ancestor(int x,int k){
int base=0;
// cout<<x<<"- "<<k<<endl;
while(k){
if(k&1){
assert(base<=19);
x=fa[base][x];
}
base++;
k>>=1;
}
return x;
}
inline int lca(int x,int y){
while(top[x]!=top[y]){
if(deep[top[x]]<deep[top[y]]) swap(x,y);
x=fa[0][top[x]];
}
return deep[x]<deep[y]?x:y;
}
inline int deep_maxn(int x,int y){
return deep[x]>deep[y]?x:y;
}
namespace stree{
struct tree{
int sum;
int lazy;
}t[100001*4];
#define tol (id*2)
#define tor (id*2+1)
#define mid(l,r) mid=((l)+(r))/2
void build(int id,int l,int r){
// cout<<"build "<<id<<" "<<l<<" "<<r<<endl;
assert(l<=r);
if(l==r){
t[id].sum=wnew[l];
return;
}
int mid(l,r);
build(tol,l,mid);
build(tor,mid+1,r);
t[id].sum=t[tol].sum+t[tor].sum;
}
void pushdown(int id,int l,int r){
if(t[id].lazy){
int mid(l,r);
t[tol].sum+=t[id].lazy*(mid-l+1);
t[tol].lazy+=t[id].lazy;
t[tor].sum+=t[id].lazy*(r-(mid+1)+1);
t[tor].lazy+=t[id].lazy;
t[id].lazy=0;
}
}
void change(int id,int l,int r,int L,int R,int val){
if(L<=l and r<=R){
t[id].sum+=val*(r-l+1);
t[id].lazy+=val;
return;
}
int mid(l,r);
pushdown(id,l,r);
if(R<=mid) change(tol,l,mid,L,R,val);
else if(L>=mid+1) change(tor,mid+1,r,L,R,val);
else{
change(tol,l,mid,L,mid,val);
change(tor,mid+1,r,mid+1,R,val);
}
t[id].sum=t[tol].sum+t[tor].sum;
}
int ask(int id,int l,int r,int L,int R){
// cout<<"ask "<<id<<" "<<l<<" "<<r<<" "<<L<<" "<<R<<endl;
if(L<=l and r<=R){
return t[id].sum;
}
int mid(l,r);
pushdown(id,l,r);
if(R<=mid) return ask(tol,l,mid,L,R);
else if(L>=mid+1) return ask(tor,mid+1,r,L,R);
return ask(tol,l,mid,L,mid)+ask(tor,mid+1,r,mid+1,R);
}
}
inline void change_subtree(int x,int val){
stree::change(1,1,n,id[x],id[x]+siz[x]-1,val);
}
inline int ask_subtree(int x){
// cout<<"asksubtree "<<x<<" "<<siz[x]<<endl;
return stree::ask(1,1,n,id[x],id[x]+siz[x]-1);
}
inline bool isin_subtree(int x,int subroot){
if(deep[x]<deep[subroot]) return false;
if(kth_ancestor(x,deep[x]-deep[subroot])==subroot) return true;
return false;
}
int root;
signed main(){
// freopen("in.in","r",stdin);
ios::sync_with_stdio(false);
cin>>n>>q;
for(int i=1;i<=n;++i){
cin>>w[i];
}
for(int i=1;i<=n-1;++i){
int x,y;
cin>>x>>y;
e[x].push_back(y);
e[y].push_back(x);
}
dfs1(1,0);
dfs2(1,1);
// cout<<"siz: ";
// for(int i=1;i<=n;++i) cout<<siz[i]<<' ';
// cout<<endl;
root=1;
for(int i=1;i<=19;++i){
for(int j=1;j<=n;++j){
fa[i][j]=fa[i-1][fa[i-1][j]];
}
}
stree::build(1,1,n);
while(q--){
int opt;cin>>opt;
if(opt==1){
cin>>root;
}
else if(opt==2){
int x,y,v;
cin>>x>>y>>v;
int __lca=deep_maxn(deep_maxn(lca(x,y),lca(x,root)),lca(y,root));
if(__lca==root){
change_subtree(1,v);
}
else if(!isin_subtree(root,__lca)){
change_subtree(__lca,v);
}
else{
change_subtree(1,v);
change_subtree(kth_ancestor(root,deep[root]-deep[__lca]-1),-v);
}
}
else{
int x;cin>>x;
if(x==root){
cout<<ask_subtree(1)<<'\n';
}
else if(!isin_subtree(root,x)){
cout<<ask_subtree(x)<<'\n';
}
else{
cout<<ask_subtree(1)-ask_subtree(kth_ancestor(root,deep[root]-deep[x]-1))<<'\n';
}
}
}
}