202512/202601集训部分做题记录
20251216
A - 软件包管理器
树剖板子。
代码
#include<bits/stdc++.h>
using namespace std;
int n,q;
vector<int> to[100005];
int siz[100005],dep[100005],hs[100005],p[100005];
void dfs1(int x=1,int fa=0){
siz[x]=1,dep[x]=dep[fa]+1;p[x]=fa;
for(auto ed:to[x]){
if(ed==fa)continue;
dfs1(ed,x);
siz[x]+=siz[ed];
if(!hs[x]||siz[hs[x]]<siz[ed])hs[x]=ed;
}
}
int dfn[100005],edn[100005],tp[100005],cnt;
void dfs2(int x=1,int fa=0,int top=1){
dfn[x]=++cnt;tp[x]=top;
if(hs[x])dfs2(hs[x],x,top);
for(auto ed:to[x]){
if(ed==fa||ed==hs[x])continue;
dfs2(ed,x,ed);
}
edn[x]=cnt;
}
int v[400005],tg[400005];
void pushup(int x){v[x]=v[x<<1]+v[x<<1|1];}
void pushdn(int x,int l,int r){
if(tg[x]==-1)return;
int mid=(l+r)>>1;
v[x<<1]=(mid-l+1)*tg[x],tg[x<<1]=tg[x];
v[x<<1|1]=(r-mid)*tg[x],tg[x<<1|1]=tg[x];
tg[x]=-1;
}
void chg(int L,int R,int op,int x=1,int l=1,int r=n){
if(L>R)return;
if(L<=l&&r<=R)return void((v[x]=(r-l+1)*op,tg[x]=op));
pushdn(x,l,r);
int mid=(l+r)>>1;
if(L<=mid)chg(L,R,op,x<<1,l,mid);
if(mid<R)chg(L,R,op,x<<1|1,mid+1,r);
pushup(x);
}
int que(int L,int R,int x=1,int l=1,int r=n){
if(L>R)return 0;
if(L<=l&&r<=R)return v[x];
pushdn(x,l,r);
int mid=(l+r)>>1,res=0;
if(L<=mid)res+=que(L,R,x<<1,l,mid);
if(mid<R)res+=que(L,R,x<<1|1,mid+1,r);
return res;
}
int Set(int x){
int res=0;
while(x){
int tmp=que(dfn[tp[x]],dfn[x]);
res+=dfn[x]-dfn[tp[x]]+1-tmp;
chg(dfn[tp[x]],dfn[x],1);
x=p[tp[x]];
}
return res;
}
int rSet(int x){
int res=que(dfn[x],edn[x]);
chg(dfn[x],edn[x],0);
return res;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
fill(tg,tg+400000,-1);
cin>>n;
for(int i=2;i<=n;i++){
int u;
cin>>u;u++;
to[u].push_back(i);
to[i].push_back(u);
}
dfs1();dfs2();
cin>>q;
while(q--){
string op;int x;
cin>>op>>x;x++;
if(op[0]=='i')cout<<Set(x)<<'\n';
else cout<<rSet(x)<<'\n';
}
}
B - 保卫王国
想扩展到更大的子树,需要的信息只有根的颜色,所以很容易设计一个 DP。
然后是经典的树剖优化树上 DP。此类题目主要要分开轻重边转移,然后进一步的处理。
本题是带修,所以考虑重链剖分+线段树维护 DDP 即可。
代码
#include<bits/stdc++.h>
#define int long long
const int inf=1e16;
using namespace std;
int n,q,w[100005];
string typ;
vector<int> to[100005];
int siz[100005],dep[100005],hs[100005],p[100005];
void dfs1(int x=1,int fa=0){
siz[x]=1,dep[x]=dep[fa]+1;p[x]=fa;
for(auto ed:to[x]){
if(ed==fa)continue;
dfs1(ed,x);
siz[x]+=siz[ed];
if(!hs[x]||siz[hs[x]]<siz[ed])hs[x]=ed;
}
}
int dfn[100005],edn[100005],tp[100005],dn[100005],cnt;
void dfs2(int x=1,int fa=0,int top=1){
dfn[x]=++cnt;tp[x]=top;dn[top]=x;
if(hs[x])dfs2(hs[x],x,top);
for(auto ed:to[x]){
if(ed==fa||ed==hs[x])continue;
dfs2(ed,x,ed);
}
edn[x]=cnt;
}
struct mat{
int a[2][2];
mat(int x=inf){for(int i=0;i<2;i++)for(int j=0;j<2;j++)a[i][j]=(i==j?x:inf);}
int *operator[](int x){return a[x];}
};
mat operator*(mat a,mat b){
mat r;
r[0][0]=min(a[0][0]+b[0][0],a[0][1]+b[1][0]);
r[0][1]=min(a[0][0]+b[0][1],a[0][1]+b[1][1]);
r[1][0]=min(a[1][0]+b[0][0],a[1][1]+b[1][0]);
r[1][1]=min(a[1][0]+b[0][1],a[1][1]+b[1][1]);
return r;
}
mat v[400005];
int f[100005][2],g[100005][2];
void chg(int pos,mat vl,int x=1,int l=1,int r=n){
if(l==r)return void(v[x]=vl);
int mid=(l+r)>>1;
if(pos<=mid)chg(pos,vl,x<<1,l,mid);
else chg(pos,vl,x<<1|1,mid+1,r);
v[x]=v[x<<1|1]*v[x<<1];
}
mat que(int L,int R,int x=1,int l=1,int r=n){
if(L>R)return inf;
if(L<=l&&r<=R)return v[x];
int mid=(l+r)>>1;mat res=0;
if(mid<R)res=res*que(L,R,x<<1|1,mid+1,r);
if(L<=mid)res=res*que(L,R,x<<1,l,mid);
return res;
}
void build(int x=1,int fa=0){
for(auto ed:to[x]){
if(ed==fa)continue;
build(ed,x);
f[x][0]+=f[ed][1];
f[x][1]+=min(f[ed][0],f[ed][1]);
}
f[x][1]+=w[x];
for(auto ed:to[x]){
if(ed==fa||ed==hs[x])continue;
g[x][0]+=min(f[ed][0],f[ed][1]);
g[x][1]+=f[ed][1];
}
mat tpm;
tpm[0][0]=inf;tpm[1][0]=g[x][1];tpm[0][1]=tpm[1][1]=g[x][0]+w[x];
chg(dfn[x],tpm);
}
set<pair<int,int> > conr;
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>q>>typ;
for(int i=1;i<=n;i++)cin>>w[i];
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;conr.insert({u,v});conr.insert({v,u});
to[u].push_back(v);
to[v].push_back(u);
}
dfs1();dfs2();build();
while(q--){
int a,x,b,y;
cin>>a>>x>>b>>y;
if(x==0&&y==0&&conr.find({a,b})!=conr.end()){cout<<"-1\n";continue;}
auto change=[](int a,int x){
if(x==0){
mat tpm=inf;tpm[1][0]=g[a][1];
chg(dfn[a],tpm);
}
else if(x==1){
mat tpm=inf;tpm[0][1]=tpm[1][1]=g[a][0]+w[a];
chg(dfn[a],tpm);
}
else{
mat tpm=inf;tpm[1][0]=g[a][1];tpm[0][1]=tpm[1][1]=g[a][0]+w[a];
chg(dfn[a],tpm);
}
a=tp[a];
while(p[a]){
mat trs=que(dfn[a],dfn[dn[a]]);
int f0=min(trs[0][0],trs[1][0]),f1=min(trs[0][1],trs[1][1]);
g[p[a]][0]-=min(f[a][0],f[a][1]);
g[p[a]][1]-=f[a][1];
f[a][0]=f0,f[a][1]=f1;
g[p[a]][0]+=min(f[a][0],f[a][1]);
g[p[a]][1]+=f[a][1];
mat tpm=inf;tpm[1][0]=g[p[a]][1];tpm[0][1]=tpm[1][1]=g[p[a]][0]+w[p[a]];
chg(dfn[p[a]],tpm);
a=tp[p[a]];
}
mat trs=que(dfn[a],dfn[dn[a]]);
int f0=min(trs[0][0],trs[1][0]),f1=min(trs[0][1],trs[1][1]);
f[a][0]=f0,f[a][1]=f1;
};
change(a,x);
change(b,y);
int ans=min(f[1][0],f[1][1]);
cout<<ans<<'\n';
change(b,2);
change(a,2);
}
}
C - 楼房重建
显然,我们希望维护类似斜率的单调栈信息。由于带修,所以我们还得维护区间信息。
但是我们不能维护出很多栈的具体元素值。
一个思路是暴力数据结构比如分块。分块的好处是个数少,可以维护出具体的栈信息。
我们不妨直接按照 \(B\) 对序列进行分块。单点修改就直接对所在块暴力重构,查询考虑从左到右扫描块,维护已处理部分的栈顶(最大值),每次新扫到一个块就二分一下要被删除的部分。这样修改是 \(O(B)\),查询是 \(O(\frac{n}{B}\log{n})\),取 \(B=\sqrt{n\log{n}}\) 做到 \(O(q\sqrt{n\log{n}})\)。
另一个思路是线段树,需要用到一个递归 pushup 的技巧。
具体来说,我们在线段树每个节点上只维护对应栈的大小和栈顶,剩下的部分则交给递归结构。
然后考虑这玩意怎么 pushup。首先左半部分的栈结构不会改变,主要是右半部分的结构。此时我们继续分治右半区间,如果我们可以通过讨论使得我们只用向其中一侧递归那么 pushup 的复杂度就可以做到 \(O(\log^2{n})\)。
考虑有可能左半部分完全被弹掉了。即左半部分对应栈的栈顶小了,可以直接向右半部分递归。
否则左半部分有保留的点。那么对于右半部分,只用考虑左半部分对其的影响,贡献就是 \(len_{当前区间}-len_{左半部分}\)。然后向左递归。
然后就没了,\(O(q\log^2{n})\)。
代码 q(logn)^2
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,m,h[100005];
int mxp[400005],len[400005];
void build(int x=1,int l=1,int r=n){
mxp[x]=l;len[x]=1;
if(l==r)return;
int mid=(l+r)>>1;
build(x<<1,l,mid);build(x<<1|1,mid+1,r);
}
void pushup(int x,int l,int r){
int mid=(l+r)>>1;
mxp[x]=mxp[x<<1];len[x]=len[x<<1];
int xx=x<<1|1,ll=mid+1,rr=r,rem=mxp[x];
while(1){
if(ll==rr){
if(h[mxp[x]]*mxp[xx]<h[mxp[xx]]*mxp[x]){
len[x]++;
if(h[rem]*mxp[xx]<h[mxp[xx]]*rem)rem=mxp[xx];
}
break;
}
int mmid=(ll+rr)>>1;
if(h[mxp[x]]*mxp[xx<<1]>=h[mxp[xx<<1]]*mxp[x])xx=xx<<1|1,ll=mmid+1;
else{
len[x]+=len[xx]-len[xx<<1];
if(len[xx]-len[xx<<1]&&h[rem]*mxp[xx<<1|1]<h[mxp[xx<<1|1]]*rem)rem=mxp[xx<<1|1];
xx=xx<<1;rr=mmid;
}
}
mxp[x]=rem;
}
void chg(int pos,int y,int x=1,int l=1,int r=n){
if(l==r)return void(h[pos]=y);
int mid=(l+r)>>1;
if(pos<=mid)chg(pos,y,x<<1,l,mid);
else chg(pos,y,x<<1|1,mid+1,r);
pushup(x,l,r);
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
build();
while(m--){
int x,y;
cin>>x>>y;
chg(x,y);
cout<<len[1]-(h[1]==0)<<'\n';
}
}
D - 火车管理
就是还是线段树维护区间答案,叶子处维护栈还有正常节点维护 push tag 的话就用平衡树。
pushdn 就是平衡树合并。但是这样会点数爆炸,就可持久化一下,新建的点数就是 log 了。
这样得到一个时空均为 \(O(n\log^2{n})\) 的做法。我写了一发 70pts。
代码
#include<bits/stdc++.h>
using namespace std;
const int N=5e5;
int n,m,ty;
namespace DS{
//bst
mt19937 rnd(time(0));
struct node{int v,hv,ls,rs,sz;}tn[N*100+5];
int cnt;
#define ls(x) tn[x].ls
#define rs(x) tn[x].rs
void pushup(int x){
tn[x].sz=tn[ls(x)].sz+tn[rs(x)].sz+1;
}
void spiltsz(int rt,int sz,int &x,int &y){
if(!rt)return void(x=y=0);
int nd=++cnt;tn[nd]=tn[rt];
if(tn[ls(rt)].sz+1<=sz)x=nd,spiltsz(rs(rt),sz-(tn[ls(rt)].sz+1),rs(x),y);
else y=nd,spiltsz(ls(rt),sz,x,ls(y));
pushup(nd);
}
int merge(int x,int y){
if(!x||!y)return (cnt++,tn[cnt]=tn[x+y],cnt);
int nd=++cnt;
if(tn[x].hv<tn[y].hv)return (tn[nd]=tn[x],rs(nd)=merge(rs(x),y),pushup(nd),nd);
else return (tn[nd]=tn[y],ls(nd)=merge(x,ls(y)),pushup(nd),nd);
}
int maxn(int x){
if(!rs(x))return tn[x].v;
return maxn(rs(x));
}
//sgt
int v[(N<<2)+5],tg[(N<<2)+5];
void exc(int x,int l,int r,int nd){
tg[x]=merge(tg[x],nd);
v[x]=maxn(nd)*(r-l+1);
}
void pushdn(int x,int l,int r){
if(!tg[x])return;
int mid=(l+r)>>1;
exc(x<<1,l,mid,tg[x]);
exc(x<<1|1,mid+1,r,tg[x]);
tg[x]=0;
}
void push(int L,int R,int vl,int x=1,int l=1,int r=n){
if(L>R)return;
if(L<=l&&r<=R)return exc(x,l,r,(cnt++,tn[cnt]={vl,(int)rnd(),0,0,1},cnt));
pushdn(x,l,r);
int mid=(l+r)>>1;
if(L<=mid)push(L,R,vl,x<<1,l,mid);
if(mid<R)push(L,R,vl,x<<1|1,mid+1,r);
v[x]=v[x<<1]+v[x<<1|1];
}
void pop(int pos,int x=1,int l=1,int r=n){
if(l==r){
if(!tg[x])return;
int sz=tn[tg[x]].sz,sv,er;
spiltsz(tg[x],sz-1,sv,er);
tg[x]=sv;
v[x]=maxn(tg[x]);
return;
}
pushdn(x,l,r);
int mid=(l+r)>>1;
if(pos<=mid)pop(pos,x<<1,l,mid);
else pop(pos,x<<1|1,mid+1,r);
v[x]=v[x<<1]+v[x<<1|1];
}
int que(int L,int R,int x=1,int l=1,int r=n){
if(L>R)return 0;
if(L<=l&&r<=R)return v[x];
pushdn(x,l,r);
int mid=(l+r)>>1,res=0;
if(L<=mid)res+=que(L,R,x<<1,l,mid);
if(mid<R)res+=que(L,R,x<<1|1,mid+1,r);
return res;
}
}using namespace DS;
int lans;
signed main(){
// freopen("d/C3.in","r",stdin);
// freopen("trashbin.out","w",stdout);
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m>>ty;
while(m--){
int op;
cin>>op;
if(op==1){
int l,r;
cin>>l>>r;
l=(l+lans*ty)%n+1;
r=(r+lans*ty)%n+1;
if(l>r)swap(l,r);
cout<<(lans=que(l,r))<<'\n';
}
else if(op==2){
int l;
cin>>l;
l=(l+lans*ty)%n+1;
pop(l);
}
else{
int l,r,x;
cin>>l>>r>>x;
l=(l+lans*ty)%n+1;
r=(r+lans*ty)%n+1;
if(l>r)swap(l,r);
push(l,r,x);
}
}
}
说实话代码不算太长,得分也不低,不失为一种得分手段。
考虑 100 pts 咋做。
考虑可持久化线段树,维护当前栈顶和插入时间。
那么 push 就是区间修改。pop 就是找到前面的那个版本,把信息复制过来,答案的话另外用一颗线段树维护就好。
调了很久,主要是我的可持久化线段树区间修改的设计比较奇怪。
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=5e5;
int n,m,ty;
namespace DS{
ll v[(N<<2)+5],tg[(N<<2)+5];
void chg(int L,int R,int vl,int x=1,int l=1,int r=n){
if(L>R)return;
if(L<=l&&r<=R)return void((v[x]=(r-l+1)*vl,tg[x]=vl));
int mid=(l+r)>>1;
if(tg[x]!=-1){
v[x<<1]=(mid-l+1)*tg[x],v[x<<1|1]=(r-mid)*tg[x];
tg[x<<1]=tg[x<<1|1]=tg[x];
tg[x]=-1;
}
if(L<=mid)chg(L,R,vl,x<<1,l,mid);
if(mid<R)chg(L,R,vl,x<<1|1,mid+1,r);
v[x]=v[x<<1]+v[x<<1|1];
}
ll que(int L,int R,int x=1,int l=1,int r=n){
if(L>R)return 0;
if(L<=l&&r<=R)return v[x];
int mid=(l+r)>>1;
if(tg[x]!=-1){
v[x<<1]=(mid-l+1)*tg[x],v[x<<1|1]=(r-mid)*tg[x];
tg[x<<1]=tg[x<<1|1]=tg[x];
tg[x]=-1;
}
ll res=0;
if(L<=mid)res+=que(L,R,x<<1,l,mid);
if(mid<R)res+=que(L,R,x<<1|1,mid+1,r);
return res;
}
int tim[N*100+5],ls[N*100+5],rs[N*100+5],cnt,gt;
int hv[N*100+5];
int gf;
void push(int L,int R,int vl,int ft,int rtl,int &rtr,int l=1,int r=n){
if(L>R)return;
rtr=++cnt;
if(rtl){tim[rtr]=tim[rtl];hv[rtr]=hv[rtl];ls[rtr]=ls[rtl];rs[rtr]=rs[rtl];}
else{
tim[rtr]=tim[gf];hv[rtr]=hv[gf];ls[rtr]=rs[rtr]=0;
}
if(L<=l&&r<=R){
tim[rtr]=ft;hv[rtr]=vl;ls[rtr]=rs[rtr]=0;
return;
}
int mid=(l+r)>>1;
gf=rtr;
if(L<=mid)push(L,R,vl,ft,ls[rtl],ls[rtr],l,mid);
gf=rtr;
if(mid<R)push(L,R,vl,ft,rs[rtl],rs[rtr],mid+1,r);
}
int rt[N+5];
#define Push(l,r,x) do{gt++;push(l,r,x,gt,rt[gt-1],rt[gt]);chg(l,r,x);}while(0)
pair<int,int> quer(int pos,int rt,int l=1,int r=n){
if(!rt)return make_pair(0,0);
if(l==r)return make_pair(tim[rt],hv[rt]);
int mid=(l+r)>>1;
if(pos<=mid){
if(!ls[rt])return make_pair(tim[rt],hv[rt]);
return quer(pos,ls[rt],l,mid);
}
else{
if(!rs[rt])return make_pair(tim[rt],hv[rt]);
return quer(pos,rs[rt],mid+1,r);
}
}
void pop(int pos){
int sd=quer(pos,rt[gt]).first;
if(!sd)return;
pair<int,int> tmp=quer(pos,rt[sd-1]);
gt++;push(pos,pos,tmp.second,tmp.first,rt[gt-1],rt[gt]);chg(pos,pos,tmp.second);
}
}using namespace DS;
ll lans;
signed main(){
// freopen("d/C3.in","r",stdin);
// freopen("trashbin.out","w",stdout);
fill(tg,tg+(N<<2)+1,-1);
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m>>ty;
while(m--){
int op;
cin>>op;
if(op==1){
int l,r;
cin>>l>>r;
l=(l+lans*ty)%n+1;
r=(r+lans*ty)%n+1;
if(l>r)swap(l,r);
cout<<(lans=que(l,r))<<'\n';
}
else if(op==2){
int l;
cin>>l;
l=(l+lans*ty)%n+1;
pop(l);
}
else{
int l,r,x;
cin>>l>>r>>x;
l=(l+lans*ty)%n+1;
r=(r+lans*ty)%n+1;
if(l>r)swap(l,r);
Push(l,r,x);
}
}
}
E - CPU 监控
历史最值板子。本质是维护向量 \((1,now,his)\),修改可以使用矩阵刻画。
对矩阵讨论后不难发现变化的只有 4 个位置。
代码
#include<bits/stdc++.h>
#define ll long long
const ll inf=1e16;
using namespace std;
int n;
struct mat{
ll a,b,c,d;
mat(ll a=-inf,ll b=-inf,ll c=-inf,ll d=-inf):a(a),b(b),c(c),d(d){}
};
mat adj(mat x){
x.a=max(x.a,-inf);
x.b=max(x.b,-inf);
x.c=max(x.c,-inf);
x.d=max(x.d,-inf);
return x;
}
mat operator+(mat x,mat y){x.a=max(x.a,y.a);x.b=max(x.b,y.b);x.c=max(x.c,y.c);x.d=max(x.d,y.d);return adj(x);}
mat operator*(mat x,mat y){mat r;r.a=max(y.a,x.a+y.c);r.b=max({x.b,y.b,x.a+y.d});r.c=x.c+y.c;r.d=max(x.c+y.d,x.d);return adj(r);}
#define Emat mat(-inf,-inf,0,-inf)
mat v[400005],tg[400005];
void pushup(int x){v[x]=v[x<<1]+v[x<<1|1];}
void pushdn(int x){
v[x<<1]=v[x<<1]*tg[x];tg[x<<1]=tg[x<<1]*tg[x];
v[x<<1|1]=v[x<<1|1]*tg[x];tg[x<<1|1]=tg[x<<1|1]*tg[x];
tg[x]=Emat;
}
void chg(int L,int R,mat vl,int x=1,int l=1,int r=n){
if(L>R)return;
if(L<=l&&r<=R)return void((v[x]=v[x]*vl,tg[x]=tg[x]*vl));
pushdn(x);
int mid=(l+r)>>1;
if(L<=mid)chg(L,R,vl,x<<1,l,mid);
if(mid<R)chg(L,R,vl,x<<1|1,mid+1,r);
pushup(x);
}
mat que(int L,int R,int x=1,int l=1,int r=n){
if(L>R)return mat();
if(L<=l&&r<=R)return v[x];
pushdn(x);
int mid=(l+r)>>1;
mat res=mat();
if(L<=mid)res=res+que(L,R,x<<1,l,mid);
if(mid<R)res=res+que(L,R,x<<1|1,mid+1,r);
return res;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
int st;
cin>>st;
chg(i,i,mat(st,st,-inf,-inf));
}
int q;
cin>>q;
while(q--){
char op;int l,r;
cin>>op>>l>>r;
if(op=='Q')cout<<que(l,r).a<<'\n';
if(op=='A')cout<<que(l,r).b<<'\n';
if(op=='P'){
int x;
cin>>x;
chg(l,r,mat(-inf,-inf,x,x));
}
if(op=='C'){
int x;
cin>>x;
chg(l,r,mat(x,x,-inf,-inf));
}
}
}
G - 切树游戏
单点修改,查询连通块异或和为 \(k\) 的数量。
首先考虑询问咋做。其实感觉除了 DP 没啥思路。DP 就是一个简单的树形 DP,记 \(f_{i,j}\) 表示 \(i\) 子树中异或和为 \(j\) 的包含 \(i\) 的连通块个数。转移直接暴力就是 \(O(nm^2)\),结合 FWT 不难做到 \(O(nm\log{m})\) 之类的复杂度。
由于每个点只有一个值,这个不难手动 FWT,转移一直保持点值状态就是 \(O(nm)\) 的了。根据 xor FWT 的位矩阵可以知道 \(a_i=(-1)^{i \wedge v}\)。\(\wedge\) 是按位与。
还有个修改,又是 DP,很难不往 DDP 上想。考虑树剖能否对先前的转移模式进行优化。由于过程一直是点值状态所以第二维间是独立的。
我们发现转移相当简单,就是加和乘两种线性运算(集合幂级数乘上 \((1+F)\),\(1\) 像前面一样手动 FWT 后累加上去就好),于是容易挂上树剖去做。维护重链顶的 DP 结果和轻边过来的系数就好。
那么修改就可以做到 \(O(m\log{n})\),非常优秀。
求解随便暴力逆运算。
最终就是 \(O(nm+qm\log{nm})\)。
补充一些具体细节
首先我们要维护这样一些东西:当前根的答案多项式(集合幂级数)\(f_u\) 和子树内所有点的答案多项式之和 \(g_u\)。
区分轻重边转移,我们先将轻边转移叠过来,即求出 \(h_u=\prod_{v \in son_u \wedge v \neq hs_u}(1+f_v)\) 和 \(l_u=\sum_{v \in son_u \wedge v \neq hs_u}g_v\)。
那么转移就可以具体表示为:
\[f_u = x^{v_u}h_u(1+f_{hs_u})\\ g_u = l_u+g_{hs_u}+f_u \]都可以预先使用 FWT,于是每一位就分离开变成单独的问题了。对于其中的一位,转移就是(\(1\) 的 xor FWT 是每一位都是 \(1\),假设这一位是 \(c\),\(x^{v_u}\) 就是 \((-1)^{c \wedge v_u}\)):
\[f'_u = (-1)^{c \wedge v_u}h'_u(1+f'_{hs_u})\\ g'_u = l'_u+g'_{hs_u}+f'_u \]对着式子可以知道维护三维向量 \((1,f'_u,g'_u)\) 的转移矩阵即可。
然后写代码的时候发现还有 \(h\) 的乘法撤销问题。这个很简单就是把 \(0\) 视为级数 \(x\) 就好,这应该是算经典技巧了。只用对 \(h\) 做,因为其他的要么好撤销要么不用撤销。
感觉挺简单的,可能是因为前面预先想到了 FWT 导致后面契合上 DDP 就很简单,如果没想到 FWT 后面的 DDP 可能就想不出来了。所以可以知道 DFT 之类的另一个伟大作用就是独立各个位。
代码
#include<bits/stdc++.h>
const int mod=10007;
using namespace std;
int inv[mod],pc[128];
inline void add(int &x,int y){(x+=y)>=mod&&(x-=mod);}
inline void sub(int &x,int y){(x-=y)<0&&(x+=mod);}
inline int pls(int x,int y){return (x+y>=mod?x+y-mod:x+y);}
inline int subb(int x,int y){return (x<y?x+mod-y:x-y);}
struct mat{
int a,b,c,d;
mat(int a=0,int b=0,int c=0,int d=0):a(a),b(b),c(c),d(d){}
};
mat operator*(mat x,mat y){
mat r;
r.a=pls(1ll*x.a*y.c%mod,y.a);
r.b=pls(pls(x.b,y.b),1ll*x.a*y.d%mod);
r.c=1ll*x.c*y.c%mod;
r.d=pls(1ll*x.c*y.d%mod,x.d);
return r;
}
int n,m,v[30005];
vector<int> to[30005];
int sz[30005],hs[30005],dep[30005],p[30005];
void dfs1(int x=1,int fa=0){
sz[x]=1;dep[x]=dep[fa]+1;p[x]=fa;
for(auto ed:to[x]){
if(ed==fa)continue;
dfs1(ed,x);sz[x]+=sz[ed];
if(!hs[x]||sz[hs[x]]<sz[ed])hs[x]=ed;
}
}
int dfn[30005],cnt,tp[30005],dn[30005];
void dfs2(int x=1,int fa=0,int top=1){
dfn[x]=++cnt;tp[x]=top;dn[top]=x;
if(hs[x])dfs2(hs[x],x,top);
for(auto ed:to[x]){
if(ed==fa||ed==hs[x])continue;
dfs2(ed,x,ed);
}
}
int f[30005][128],g[30005][128],h[30005][128][2],l[30005][128];
#define Val(arr) (arr[1]>0?0:arr[0])
mat vv[128][120005];
void chg(int pos,mat vl,int id,int x=1,int l=1,int r=n){
if(l==r)return void(vv[id][x]=vl);
int mid=(l+r)>>1;
if(pos<=mid)chg(pos,vl,id,x<<1,l,mid);
else chg(pos,vl,id,x<<1|1,mid+1,r);
vv[id][x]=vv[id][x<<1|1]*vv[id][x<<1];
}
mat que(int L,int R,int id,int x=1,int l=1,int r=n){
if(L>R)return 0;
if(L<=l&&r<=R)return vv[id][x];
int mid=(l+r)>>1;mat res=mat(0,0,1,0);
if(mid<R)res=res*que(L,R,id,x<<1|1,mid+1,r);
if(L<=mid)res=res*que(L,R,id,x<<1,l,mid);
return res;
}
mat budmat(int x,int c){
int vl=0;
if(pc[c&v[x]]&1)sub(vl,Val(h[x][c]));
else add(vl,Val(h[x][c]));
mat r(vl,vl,vl,vl);
add(r.b,l[x][c]);
return r;
}
void build(int x=1,int fa=0){
for(int i=0;i<m;i++)
f[x][i]=((pc[v[x]&i]&1)?mod-1:1),
h[x][i][0]=1,
l[x][i]=h[x][i][1]=0;
if(hs[x]){
build(hs[x],x);
for(int i=0;i<m;i++)
f[x][i]=1ll*pls(f[hs[x]][i],1)*f[x][i]%mod,
g[x][i]=g[hs[x]][i];
}
for(auto ed:to[x]){
if(ed==fa||ed==hs[x])continue;
build(ed,x);
for(int i=0;i<m;i++){
int vl=pls(1,f[ed][i]);
if(!vl)h[x][i][1]++;
else h[x][i][0]=1ll*vl*h[x][i][0]%mod;
add(l[x][i],g[ed][i]);
}
}
for(int i=0;i<m;i++)
f[x][i]=1ll*f[x][i]*Val(h[x][i])%mod,
add(g[x][i],f[x][i]),
add(g[x][i],l[x][i]),
chg(dfn[x],budmat(x,i),i);
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
inv[1]=1;
for(int i=2;i<mod;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
for(int i=1;i<128;i++)pc[i]=pc[i>>1]+(i&1);
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>v[i];
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
to[u].push_back(v);
to[v].push_back(u);
}
dfs1();dfs2();build();
int q;
cin>>q;
while(q--){
string op;int x,y;
cin>>op>>x;
if(op[0]=='C'){
cin>>y;
v[x]=y;
for(int i=0;i<m;i++)chg(dfn[x],budmat(x,i),i);
x=tp[x];
while(p[x]){
for(int i=0;i<m;i++){
mat rec=que(dfn[x],dfn[dn[x]],i);
int vl=pls(1,f[x][i]);
if(!vl)h[p[x]][i][1]--;
else h[p[x]][i][0]=1ll*inv[vl]*h[p[x]][i][0]%mod;
sub(l[p[x]][i],g[x][i]);
f[x][i]=rec.a;
g[x][i]=rec.b;
vl=pls(1,f[x][i]);
if(!vl)h[p[x]][i][1]++;
else h[p[x]][i][0]=1ll*vl*h[p[x]][i][0]%mod;
add(l[p[x]][i],g[x][i]);
chg(dfn[p[x]],budmat(p[x],i),i);
}
x=tp[p[x]];
}
for(int i=0;i<m;i++){
mat rec=que(dfn[x],dfn[dn[x]],i);
f[x][i]=rec.a;
g[x][i]=rec.b;
}
}
else{
int res=0;
for(int i=0;i<m;i++)
if(pc[i&x]&1)sub(res,g[1][i]);
else add(res,g[1][i]);
cout<<1ll*res*inv[m]%mod<<'\n';
}
}
}
H - Niyaz and Small Degrees
这个是之前做过的题目,先咕了。
20251216 Ex
A - 动态图连通性
线段树分治的板子。
代码
#include<bits/stdc++.h>
using namespace std;
int n,m;
int op[500005],_x[500005],_y[500005],ans[500005];
map<pair<int,int>,int> cz;
vector<pair<int,int> > v[2000005];
void joi(int L,int R,pair<int,int> vl,int x=1,int l=1,int r=m){
if(L>R)return;
if(L<=l&&r<=R)return void(v[x].push_back(vl));
int mid=(l+r)>>1;
if(L<=mid)joi(L,R,vl,x<<1,l,mid);
if(mid<R)joi(L,R,vl,x<<1|1,mid+1,r);
}
int fa[5005],sz[5005],tot;
pair<int,int> stk[500005];
int find(int x){return (x==fa[x]?x:find(fa[x]));}
int merge(int x,int y){
if(find(x)==find(y))return 0;
x=find(x),y=find(y);
if(sz[x]>sz[y])swap(x,y);
fa[x]=y;
sz[y]+=sz[x];
stk[++tot]=make_pair(x,y);
return 1;
}
void pop(){
auto top=stk[tot--];
sz[top.second]-=sz[top.first];
fa[top.first]=top.first;
}
void sol(int x=1,int l=1,int r=m){
int ct=0;
for(auto ed:v[x])ct+=merge(ed.first,ed.second);
if(l==r){
if(op[l]==2){
ans[l]=(find(_x[l])==find(_y[l]));
}
while(ct--)pop();
return;
}
int mid=(l+r)>>1;
sol(x<<1,l,mid);sol(x<<1|1,mid+1,r);
while(ct--)pop();
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++)fa[i]=i,sz[i]=1;
tot=0;
for(int i=1;i<=m;i++){
cin>>op[i]>>_x[i]>>_y[i];
if(_x[i]>_y[i])swap(_x[i],_y[i]);
if(op[i]==0)cz[make_pair(_x[i],_y[i])]=i;
if(op[i]==1)
joi(cz[make_pair(_x[i],_y[i])],i-1,make_pair(_x[i],_y[i])),
cz.erase(make_pair(_x[i],_y[i]));
}
for(auto ed:cz)joi(ed.second,m,ed.first);
sol();
for(int i=1;i<=m;i++)if(op[i]==2)cout<<(ans[i]?"Y":"N")<<'\n';
}
B - Binary Table (CF662C)
直觉上首先是 \(n\) 很小可以状压(枚举),然后按照 \(m\) 那一维进行扫描。
假设已经知道行作用后的每一列 \(\{A\}\),答案就是 \(\sum \min(|A_i|,n-|A_i|)\),\(|\circ|\) 是 popcount。
这样不难做到 \(O(2^nm)\) 之类的复杂度。
试图把 \(m\) 消掉。把 \(m\) 列中行作用前为 \(c\) 的个数记为 \(a_c\)。不难做到 \(O(4^n)\)。(反而更劣了)
记上面那个 min 为 \(b_{a_i}\)。
上面的和式写出来就是 \(f_{a}=\sum_{c} a_c b_{c \oplus a}=\sum_{c \oplus d=a}a_c b_d\)。
直接 xor FWT 没了。
代码
#include<bits/stdc++.h>
const int mod=998244353;
#define popc __builtin_popcount
using namespace std;
int fpow(int a,int b=mod-2){
int r=1;
while(b){
if(b&1)r=1ll*r*a%mod;
a=1ll*a*a%mod;
b>>=1;
}
return r;
}
int n,m;
char s[25][100005];
int a[1<<20],b[1<<20];
inline int add(int x,int y){return (x+y>=mod?x+y-mod:x+y);}
inline int sub(int x,int y){return (x<y?x+mod-y:x-y);}
#define fwt(a,n,op) do{\
for(int FWT_len=2,FWT_mid=1;FWT_len<=(1<<n);FWT_len<<=1,FWT_mid<<=1)\
for(int FWT_i=0;FWT_i<(1<<n);FWT_i+=FWT_len)\
for(int FWT_j=0;FWT_j<FWT_mid;FWT_j++){\
int FWT_y=a[FWT_i|FWT_j|FWT_mid];\
a[FWT_i|FWT_j|FWT_mid]=sub(a[FWT_i|FWT_j],FWT_y),a[FWT_i|FWT_j]=add(a[FWT_i|FWT_j],FWT_y);\
}\
op(a,n);\
}while(0)
void dft(int *a,int n){}
void idft(int *a,int n){int t=fpow(fpow(2,n));for(int i=0;i<(1<<n);i++)a[i]=1ll*a[i]*t%mod;}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(int i=0;i<n;i++)cin>>(s[i]+1);
for(int i=1;i<=m;i++){
int bs=0;
for(int j=0;j<n;j++)bs=bs<<1|(s[j][i]-'0');
a[bs]++;
}
for(int i=0;i<(1<<n);i++)b[i]=min(popc(i),n-popc(i));
fwt(a,n,dft);fwt(b,n,dft);
for(int i=0;i<(1<<n);i++)a[i]=1ll*a[i]*b[i]%mod;
fwt(a,n,idft);
int ans=n*m;
for(int i=0;i<(1<<n);i++)ans=min(ans,a[i]);
cout<<ans;
}
C - 点分树 | 震波
单点修改,求 k 邻域和。
这个点分树套动态开点线段树是不是就好了。时空都是 \(O(n\log^2{n})\) 的吧。
是不是写边分树更好玩啊,二叉的话更好写吧。
正好之前只写过一次边分树(可持久化),再写一遍练练手。
代码
#include<bits/stdc++.h>
const int N=1e5,VN=N*70,VM=N*70;
using namespace std;
struct grp{
int n,m=1;
int hd[4*N+5],nx[4*N+5],to[4*N+5],w[4*N+5];
void addedge(int u,int v,int c){
to[++m]=v;w[m]=c;
nx[m]=hd[u];
hd[u]=m;
}
}g1,g2;
int n,m,v[N+5];
namespace edt{
//sgt
namespace sgt{
int v[VN+5],ch[VN+5][2],cnt;
vector<int> bin;
int newnode(){
int x;
if(!bin.empty())x=bin.back(),bin.pop_back();
else x=++cnt;
v[x]=ch[x][0]=ch[x][1]=0;
return x;
}
void merge(int &x,int y,int l=0,int r=n){
if(!x||!y)return void(x=x+y);
v[x]+=v[y];
merge(ch[x][0],ch[y][0]);
merge(ch[x][1],ch[y][1]);
bin.push_back(y);
return;
}
void ins(int &x,int pos,int vl,int l=0,int r=n){
if(!x)x=newnode();
if(l==r)return void(v[x]+=vl);
int mid=(l+r)>>1;
if(pos<=mid)ins(ch[x][0],pos,vl,l,mid);
else ins(ch[x][1],pos,vl,mid+1,r);
v[x]=v[ch[x][0]]+v[ch[x][1]];
}
int que(int &x,int L,int R,int l=0,int r=n){
if(!x||L>R)return 0;
if(L<=l&&r<=R)return v[x];
int mid=(l+r)>>1,res=0;
if(L<=mid)res+=que(ch[x][0],L,R,l,mid);
if(mid<R)res+=que(ch[x][1],L,R,mid+1,r);
return res;
}
}
//edt
void adj(int x,int fa){
int lst=x,fg=0;
for(int i=g1.hd[x];i;i=g1.nx[i]){
int j=g1.to[i];
if(j==fa)continue;
if(fg)g2.n++,g2.addedge(lst,g2.n,0),g2.addedge(g2.n,lst,0),lst=g2.n;
fg=1;
g2.addedge(lst,j,1);
g2.addedge(j,lst,1);
adj(j,x);
}
}
#define Adj() (g2.n=g1.n,adj(1,0))
bool vis[4*N+5];
vector<int> fx[4*N+5];
vector<int> ds[4*N+5];
int sz[4*N+5],rte,rtv,nn;
void getm(int x,int fa){
sz[x]=1;
for(int i=g2.hd[x];i;i=g2.nx[i]){
int j=g2.to[i];
if(j==fa||vis[i])continue;
getm(j,x);sz[x]+=sz[j];
if(max(sz[j],nn-sz[j])<rtv)rte=i,rtv=max(sz[j],nn-sz[j]);
}
}
#define Getm(x,fa) (getm(x,fa),nn=sz[x],rte=-1,rtv=1e9,getm(x,fa))
void dfs(int x,int fa,int typ,int dis){
if(x<=n){
fx[x].push_back(typ);
ds[x].push_back(dis);
}
for(int i=g2.hd[x];i;i=g2.nx[i]){
int j=g2.to[i];
if(j==fa||vis[i])continue;
dfs(j,x,typ,dis+g2.w[i]);
}
}
void conquer(int x,int fa){
Getm(x,fa);
if(rte==-1)return;
vis[rte]=vis[rte^1]=1;
int u=g2.to[rte],v=g2.to[rte^1],w=g2.w[rte];
dfs(u,v,0,0);dfs(v,u,1,w);
conquer(u,v);conquer(v,u);
}
int tr[VM+5][2],ch[VM+5][2],cnt;
vector<int> bin;
int newnode(){
int x;
if(!bin.empty())x=bin.back(),bin.pop_back();
else x=++cnt;
tr[x][0]=tr[x][1]=ch[x][0]=ch[x][1]=0;
return x;
}
int build(int x,int vl,int st=0){
if(st==(int)fx[x].size())return 0;
int nw=newnode();
sgt::ins(tr[nw][fx[x][st]],ds[x][st],vl);
ch[nw][fx[x][st]]=build(x,vl,st+1);
return nw;
}
void merge(int &x,int y){
if(!x||!y)return void(x=x+y);
sgt::merge(tr[x][0],tr[y][0]);
sgt::merge(tr[x][1],tr[y][1]);
merge(ch[x][0],ch[y][0]);
merge(ch[x][1],ch[y][1]);
bin.push_back(y);
}
}using namespace edt;
int rt;
int que(int rt,int x,int k,int st=0){
if(st==(int)fx[x].size())return 0;
int res=sgt::que(tr[rt][fx[x][st]^1],0,k-ds[x][st]);
return res+que(ch[rt][fx[x][st]],x,k,st+1);
}
int lans;
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;g1.n=n;
for(int i=1;i<=n;i++)cin>>v[i];
for(int i=1;i<n;i++){
int u,v;
cin>>u>>v;
g1.addedge(u,v,1);
g1.addedge(v,u,1);
}
Adj();
conquer(1,0);
for(int i=1;i<=n;i++)merge(rt,build(i,v[i]));
while(m--){
int op,x,k;
cin>>op>>x>>k;x^=lans;k^=lans;
if(op==0)cout<<(lans=que(rt,x,k)+v[x])<<'\n';
else{
int tmp=build(x,k-v[x]);
merge(rt,tmp);
v[x]=k;
}
}
}
D - 捉迷藏
单点修改+查询黑点直径。
树上直径板子,直径是可合并信息。
代码
#include<bits/stdc++.h>
#define fin(x) freopen(#x".in","r",stdin)
#define fout(x) freopen(#x".out","w",stdout)
#define file(x) (fin(x),fout(x),0)
#define pii pair<int,int>
#define int long long
using namespace std;
const int N=1e5,W=17,inf=1e9;
int n,q,w,lans;
//维护树形态以及树上距离
int P,v[262144],tg[262144];
void add(int l,int r,int vl){
l=l+P-1,r=r+P+1;int siz=1;
while(l^r^1){
if(l&1^1)v[l^1]+=siz*vl,tg[l^1]+=vl;
if(r&1)v[r^1]+=siz*vl,tg[r^1]+=vl;
l>>=1,r>>=1,siz<<=1;
v[l]=v[l<<1]+v[l<<1|1]+tg[l]*siz;
v[r]=v[r<<1]+v[r<<1|1]+tg[r]*siz;
}
for(l>>=1;l;l>>=1)v[l]=v[l<<1]+v[l<<1|1]+tg[l]*siz;
}
int que(int pos){
pos=pos+P;int res=v[pos];
for(pos>>=1;pos;pos>>=1)res+=tg[pos];
return res;
}
vector<int> to[N+5];
int U[N+5],V[N+5];
int dep[N+5],dfn[N+5],edn[N+5],fid[N+5],cnt;
int sz[N+5],hs[N+5],f[N+5];
void dfs(int x=1,int fa=0){
dfn[x]=++cnt;fid[cnt]=x;dep[x]=dep[fa]+1;
sz[x]=1;f[x]=fa;
for(auto ed:to[x]){
if(ed==fa)continue;
dfs(ed,x);
sz[x]+=sz[ed];
if(!hs[x]||sz[hs[x]]<=sz[ed])hs[x]=ed;
}
edn[x]=cnt;
}
int tp[N+5];
void dfS(int x=1,int fa=0,int top=1){
tp[x]=top;
if(hs[x])dfS(hs[x],x,top);
for(auto ed:to[x]){
if(ed==fa||ed==hs[x])continue;
dfS(ed,x,ed);
}
}
int lca(int x,int y){
while(tp[x]!=tp[y]){
if(dep[tp[x]]>dep[tp[y]])x=f[tp[x]];
else y=f[tp[y]];
}
return (dep[x]<dep[y]?x:y);
}
int dis(int x,int y){
if(!x||!y)return -inf;
int l=lca(x,y);
return que(dfn[x])+que(dfn[y])-2*que(dfn[l]);
}
//标记
struct mes{int a,b,ds;};
mes operator+(mes x,mes y){
pair<int,pii> res={x.ds,{x.a,x.b}},tpm;
tpm={dis(x.a,y.a),{x.a,y.a}};res=max(res,tpm);
tpm={dis(x.a,y.b),{x.a,y.b}};res=max(res,tpm);
tpm={dis(x.b,y.a),{x.b,y.a}};res=max(res,tpm);
tpm={dis(x.b,y.b),{x.b,y.b}};res=max(res,tpm);
tpm={y.ds,{y.a,y.b}};res=max(res,tpm);
return {res.second.first,res.second.second,res.first};
}
mes vv[(N<<2)+5];
int sgn[N+5];
void build(int x=1,int l=1,int r=n){
if(l==r)return void(vv[x]={fid[l],fid[l],0});
int mid=(l+r)>>1;
build(x<<1,l,mid);build(x<<1|1,mid+1,r);
vv[x]=vv[x<<1]+vv[x<<1|1];
}
void upd(int L,int R,int x=1,int l=1,int r=n){
if(L>R)return;
if(L<=l&&r<=R){
if(l==r){
if(sgn[l])vv[x]={fid[l],fid[l],0};
else vv[x]={0,0,-inf};
}
else vv[x]=vv[x<<1]+vv[x<<1|1];
return;
}
int mid=(l+r)>>1;
if(L<=mid)upd(L,R,x<<1,l,mid);
if(mid<R)upd(L,R,x<<1|1,mid+1,r);
vv[x]=vv[x<<1]+vv[x<<1|1];
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
P=1;while(P<=n+1)P<<=1;
for(int i=1;i<=n;i++)sgn[i]=1;
for(int i=1;i<n;i++){
cin>>U[i]>>V[i];
to[U[i]].push_back(V[i]);
to[V[i]].push_back(U[i]);
}
dfs();dfS();
for(int i=1;i<n;i++){
if(dep[U[i]]>dep[V[i]])add(dfn[U[i]],edn[U[i]],1);
else add(dfn[V[i]],edn[V[i]],1);
}
build();
cin>>q;
while(q--){
char op;
int x;
cin>>op;
if(op=='G'){
int res=vv[1].ds;
if(res==-inf)cout<<"-1\n";
else cout<<res<<'\n';
}
else{
cin>>x;
sgn[dfn[x]]^=1;
upd(dfn[x],dfn[x]);
}
}
}
E - Souvenirs
其实是先做了 F 再来做这题的。所以看起来就觉得像是支配对。
\((a,b)\) 支配 \((x,y)\) 肯定就是 \(x \leq a<b \leq y,|v_a-v_b|<|v_x-v_y|\)。
有一个直觉是 \(v_a,v_b\) 不能被 \(v_x,v_y\) 夹在中间,必要性显然。
其实在弱化一点把 \(a=x\) 或者 \(b=y\) 那就是中间不能有数。那么前面比我大的可行的一定是个递增序列,比我小的一定递减。
我们先考虑前面比我大的。那么 \(i < j < k,a_k < a_i < a_j,a_i - a_k < a_j - a_k,a_j - a_i\)。
所以就是 \(a_k<a_i<\min(a_j,\frac{a_j+a_k}{2})\)。
考虑这个差分 \(a_i-a_k<\frac{a_j-a_k}{2}\),这个差分缩小的势能是 \(\log{V}\) 的,所以没啥问题。
小于部分不是一样的吗?做完了,两只 log。
关于咋写?卧槽我好像真不会写啥正常做法。
感觉上你偏序都有两只 log 了这里多带只 log 也没啥,就直接分治归并维护序列,然后二分(平衡树)往前找就好吧。但这样写起来比 f 的点分还达芬吧。
写一半突然想起每次插入的是最小值,所以就是 pop_front 吧。但是不管了,平衡树可以 spilt 爽。
比较好的是看到别人的题解发现可以做值域翻转,这样就不用再写一次小于了。
还有记得 \(a_i=a_j\) 会杀死比赛。但我懒得写进前面那个复杂的过程里。所以最后记得把 \(O(n)\) 个相等加进去。
代码
#include<bits/stdc++.h>
#define pii pair<int,int>
#define poi pair<pii,int>
#define int long long
const int inf=1e16;
using namespace std;
int n,a[100005];
vector<poi> res;
int m;
struct Que{int l,r,id;}Q[300005];
int ans[300005];
int v[400005];
void ins(int pos,int vl,int x=1,int l=1,int r=n){
if(l==r)return void(v[x]=min(v[x],vl));
int mid=(l+r)>>1;
if(pos<=mid)ins(pos,vl,x<<1,l,mid);
else ins(pos,vl,x<<1|1,mid+1,r);
v[x]=min(v[x<<1],v[x<<1|1]);
}
int que(int L,int R,int x=1,int l=1,int r=n){
if(L>R)return inf;
if(L<=l&&r<=R)return v[x];
int mid=(l+r)>>1,res=inf;
if(L<=mid)res=min(res,que(L,R,x<<1,l,mid));
if(mid<R)res=min(res,que(L,R,x<<1|1,mid+1,r));
return res;
}
int id[100005],tid[100005];
vector<int> gd[100005];
//
mt19937 rnd(time(0));
struct node{int v,pos,hv,ls,rs,sz;}tn[100005];
#define ls(x) tn[x].ls
#define rs(x) tn[x].rs
int cnt;
int newnode(int vl,int pos){
tn[++cnt]={vl,pos,(int)rnd(),0,0,1};
return cnt;
}
void pushup(int x){tn[x].sz=tn[ls(x)].sz+tn[rs(x)].sz+1;}
void spilt(int rt,int vl,int &x,int &y){
if(!rt)return void(x=y=0);
if(tn[rt].v<=vl)x=rt,spilt(rs(rt),vl,rs(x),y);
else y=rt,spilt(ls(rt),vl,x,ls(y));
pushup(rt);
}
void spiltP(int rt,int pos,int &x,int &y){
if(!rt)return void(x=y=0);
if(tn[rt].pos<=pos)x=rt,spiltP(rs(rt),pos,rs(x),y);
else y=rt,spiltP(ls(rt),pos,x,ls(y));
pushup(rt);
}
int merge(int x,int y){
if(!x||!y)return x+y;
if(tn[x].hv<tn[y].hv)return (rs(x)=merge(rs(x),y),pushup(x),x);
return (ls(y)=merge(x,ls(y)),pushup(y),y);
}
int maxn(int x){
if(!rs(x))return tn[x].pos;
return maxn(rs(x));
}
void sol(int l=1,int r=n){
if(l==r)return void(id[l]=l);
int mid=(l+r)>>1;
sol(l,mid);sol(mid+1,r);
int pl=l-1,pr=mid,pn=l-1,stk=cnt=0;
while(pl<mid&&pr<r){
if(a[id[pl+1]]>a[id[pr+1]]){
pl++;tid[++pn]=id[pl];
int xx,yy;
spiltP(stk,id[pl],xx,yy);
stk=merge(newnode(a[id[pl]],id[pl]),yy);
}
else{
pr++;tid[++pn]=id[pr];
int t,vln=a[id[pr]],xx,yy;
if(gd[id[pr]].empty())t=inf;
else t=a[gd[id[pr]].back()];
while(1){
spilt(stk,(t+vln-1)/2,xx,yy);
if(!xx){
stk=yy;
break;
}
int tmp=maxn(xx);
res.push_back({{tmp,id[pr]},abs(a[id[pr]]-a[tmp])});
gd[id[pr]].push_back(tmp);
t=a[tmp];
stk=merge(xx,yy);
}
}
}
while(pl<mid){
pl++;tid[++pn]=id[pl];
int xx,yy;
spilt(stk,a[id[pl]],xx,yy);
stk=merge(newnode(a[id[pl]],id[pl]),yy);
}
while(pr<r){
pr++;tid[++pn]=id[pr];
int t,vln=a[id[pr]],xx,yy;
if(gd[id[pr]].empty())t=inf;
else t=a[gd[id[pr]].back()];
while(1){
spilt(stk,(t+vln-1)/2,xx,yy);
if(!xx){
stk=yy;
break;
}
int tmp=maxn(xx);
res.push_back({{tmp,id[pr]},abs(a[id[pr]]-a[tmp])});
gd[id[pr]].push_back(tmp);
t=a[tmp];
stk=merge(xx,yy);
}
}
memcpy(id+l,tid+l,(r-l+1)*sizeof(int));
}
map<int,int> lst;
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
fill(v+1,v+400001,inf);
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++){
if(lst.count(a[i]))res.push_back({{lst[a[i]],i},0});
lst[a[i]]=i;
}
sol();
for(int i=1;i<=n;i++)gd[i].clear(),a[i]=-a[i];
sol();
int m;
cin>>m;
for(int i=1;i<=m;i++)cin>>Q[i].l>>Q[i].r,Q[i].id=i;
sort(res.begin(),res.end());
sort(Q+1,Q+1+m,[](Que x,Que y){return x.l<y.l;});
for(int r=(int)res.size()-1,l=r+1,i=m+1;r>=0;r=l-1,l=r+1){
while(l>0&&res[l-1].first.first==res[r].first.first){
l--;
ins(res[l].first.second,res[l].second);
}
while(i-1&&(l==0||Q[i-1].l>res[l-1].first.first)){
i--;
ans[Q[i].id]=que(Q[i].l,Q[i].r);
}
}
for(int i=1;i<=m;i++)cout<<ans[i]<<'\n';
}
F - Tree Distance
支配对+二维偏序。
考虑到 \((x,y),(a,b),x \leq a<b \leq y,dis(x,y) \geq dis(a,b)\) 那么 \((x,y)\) 就没啥用了。
但是这个还是很难受只能 \(O(n^2)\) 搞。
我们考虑树分治。可以直接把 \(dis(x,y)\) 变成 \(d_x+d_y\)。一般来说还要满足 \(x,y\) 来自不同子树,但是这里求的是 min,可以把这个限制弱化掉。
就变成了 \((x,y),(a,b),x \leq a < b \leq y,d_x+d_y \geq d_a+d_b\)。然后考虑极端情况 \(x=a/y=b\),可以发现如果 \((x,y)\) 有用那么他们是区间最小和次小值。
考虑笛卡尔树,相当于 \(x\) 只能和第一个左父亲/右父亲匹配,只有 \(O(n)\) 个点对。所以这样求出的支配对就是 \(O(n\log{n})\) 个的。具体写起来,就是排序后跑笛卡尔树栈构建算法,把右链遍历到的都选出来就好了。
把支配对跑出来后直接二维偏序就好。
代码
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define poi pair<pii,int>
const int N=2e5,inf=1e16;
using namespace std;
int n;
vector<pii> to[N+5];
vector<poi> res;
bool vis[N+5];
int sz[N+5],mson[N+5],rtp,rtv,nn;
void getm(int x,int fa){
sz[x]=1;mson[x]=0;
for(auto ed:to[x]){
if(ed.first==fa||vis[ed.first])continue;
getm(ed.first,x);sz[x]+=sz[ed.first];mson[x]=max(mson[x],sz[ed.first]);
}
mson[x]=max(mson[x],nn-sz[x]);
if(mson[x]<rtv)rtp=x,rtv=mson[x];
}
#define Getm(x,fa) (getm(x,fa),nn=sz[x],rtp=-1,rtv=1e9,getm(x,fa))
vector<pii> tmp;
void dfs(int x,int fa,int dis){
tmp.push_back({x,dis});
for(auto ed:to[x]){
if(ed.first==fa||vis[ed.first])continue;
dfs(ed.first,x,dis+ed.second);
}
}
//??????.
int stk[N+5],tot;
void conquer(int x,int fa){
Getm(x,fa);
int nw=rtp;vis[nw]=1;
tmp.clear();
tmp.push_back({nw,0});
for(auto ed:to[nw]){
if(vis[ed.first])continue;
dfs(ed.first,nw,ed.second);
}
sort(tmp.begin(),tmp.end());
tot=0;
for(int i=0,si=(int)tmp.size();i<si;i++){
while(tot&&tmp[stk[tot]].second>=tmp[i].second){
res.push_back({{tmp[stk[tot]].first,tmp[i].first},tmp[stk[tot]].second+tmp[i].second});
tot--;
}
if(tot)res.push_back({{tmp[stk[tot]].first,tmp[i].first},tmp[stk[tot]].second+tmp[i].second});
stk[++tot]=i;
}
for(auto ed:to[nw]){
if(vis[ed.first])continue;
conquer(ed.first,nw);
}
}
struct Que{int l,r,id;}Q[N*5+5];
int ans[N*5+5];
int v[(N<<2)+5];
void ins(int pos,int vl,int x=1,int l=1,int r=n){
if(l==r)return void(v[x]=min(v[x],vl));
int mid=(l+r)>>1;
if(pos<=mid)ins(pos,vl,x<<1,l,mid);
else ins(pos,vl,x<<1|1,mid+1,r);
v[x]=min(v[x<<1],v[x<<1|1]);
}
int que(int L,int R,int x=1,int l=1,int r=n){
if(L>R)return 1e16;
if(L<=l&&r<=R)return v[x];
int mid=(l+r)>>1,res=1e16;
if(L<=mid)res=min(res,que(L,R,x<<1,l,mid));
if(mid<R)res=min(res,que(L,R,x<<1|1,mid+1,r));
return res;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
fill(v+1,v+1+(N<<2),1e16);
cin>>n;
for(int i=1;i<n;i++){
int u,v,w;
cin>>u>>v>>w;
to[u].push_back({v,w});
to[v].push_back({u,w});
}
conquer(1,0);
sort(res.begin(),res.end());
int q;
cin>>q;
for(int i=1;i<=q;i++)cin>>Q[i].l>>Q[i].r,Q[i].id=i;
sort(Q+1,Q+1+q,[](Que x,Que y){return x.l<y.l;});
for(int r=(int)res.size()-1,l=r+1,i=q+1;r>=0;r=l-1,l=r+1){
while(l>0&&res[l-1].first.first==res[r].first.first){
l--;
ins(res[l].first.second,res[l].second);
}
while(i>1&&(l==0||Q[i-1].l>res[l-1].first.first)){
i--;
if(Q[i].l>=Q[i].r)ans[Q[i].id]=-1;
else ans[Q[i].id]=que(Q[i].l,Q[i].r);
}
}
for(int i=1;i<=q;i++)
cout<<ans[i]<<'\n';
}

浙公网安备 33010602011771号