数据结构优化 DP 学习笔记

数据结构优化 DP 的题。

I.数据结构维护 DP 态

在前一个 DP 态与后一个 DP 态变化不大时,可以使用数据结构维护。

事实上是整体 DP 在链上的特殊形式。

I.CF115E Linear Kingdom Races

思路1.

f[i][j]表示:

当前DP到第i位,且最右边的一个没有修的路是第j条路,的最大收益。

则有

f[i][i]=maxj=0i1f[i1][j]

这是在i号路不修的情况。

对于其它的情况,有f[i][j]=f[i1][j]ai,其中ai表示修路的代价,且有0j<i

然后考虑举办的比赛。

对于一场比赛(l,i,x),所有的f[i][j](j<l)都能获得x的收益。比赛可以直接在右端点处开vector储存。

这样时空复杂度都是O(n2)的。

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,f[2][200100],val[200100],res;
vector<pair<int,int> >v[200100];
inline void read(int &x){
	x=0;
	char c=getchar();
	while(c>'9'||c<'0')c=getchar();
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
inline void print(int x){
	if(x<=9)putchar('0'+x);
	else print(x/10),putchar('0'+x%10);
}
signed main(){
	read(n),read(m),memset(f,0x80,sizeof(f));
	for(int i=1;i<=n;i++)read(val[i]);
	for(int i=1,l,r,x;i<=m;i++)read(l),read(r),read(x),v[r].push_back(make_pair(l,x));
	f[0][0]=0;
	for(int i=1;i<=n;i++){
		memset(f[i&1],0x80,sizeof(f[i&1]));
		for(int j=0;j<i;j++)f[i&1][i]=max(f[i&1][i],f[!(i&1)][j]);
		for(int j=0;j<i;j++)f[i&1][j]=f[!(i&1)][j]-val[i];
		for(auto j:v[i])for(int k=0;k<j.first;k++)f[i&1][k]+=j.second;
//		for(int j=0;j<=i;j++)printf("%lld ",f[i&1][j]);puts("");
	}
	for(int i=0;i<=n;i++)res=max(res,f[n&1][i]);
	print(res);
	return 0;
} 

思路2.

先把空间复杂度解决掉。

发现i时刻的f数组与i1时刻的f数组区别只有这些:

  1. f[i]的变动。

  2. f[0i1]ai

  3. 比赛的收益。

那么我们完全可以自始至终只用一个f数组。

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,f[200100],val[200100],res;
vector<pair<int,int> >v[200100];
inline void read(int &x){
	x=0;
	char c=getchar();
	while(c>'9'||c<'0')c=getchar();
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
inline void print(int x){
	if(x<=9)putchar('0'+x);
	else print(x/10),putchar('0'+x%10);
}
signed main(){
	read(n),read(m),memset(f,0x80,sizeof(f));
	for(int i=1;i<=n;i++)read(val[i]);
	for(int i=1,l,r,x;i<=m;i++)read(l),read(r),read(x),v[r].push_back(make_pair(l,x));
	f[0]=0;
	for(int i=1;i<=n;i++){
		for(int j=0;j<i;j++)f[i]=max(f[i],f[j]);
		for(int j=0;j<i;j++)f[j]-=val[i];
		for(auto j:v[i])for(int k=0;k<j.first;k++)f[k]+=j.second;
//		for(int j=0;j<=i;j++)printf("%lld ",f[i&1][j]);puts("");
	}
	for(int i=0;i<=n;i++)res=max(res,f[i]);
	print(res);
	return 0;
} 

思路3.

发现所有操作只有三种:单点赋值(1),区间求max(1),区间加/减(2,3)。

而这些都是线段树的常规操作。

于是大力往上一套完事。复杂度O(nlogn)

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define lson x<<1
#define rson x<<1|1
#define mid ((l+r)>>1)
int n,m,val[200100],res;
vector<pair<int,int> >v[200100];
inline void read(int &x){
	x=0;
	char c=getchar();
	while(c>'9'||c<'0')c=getchar();
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
}
inline void print(int x){
	if(x<=9)putchar('0'+x);
	else print(x/10),putchar('0'+x%10);
}
struct SegTree{
	int mx,tag;
}seg[800100];
void pushup(int x){
	seg[x].mx=max(seg[lson].mx,seg[rson].mx);
}
void ADD(int x,int y){
	seg[x].tag+=y,seg[x].mx+=y;
}
void pushdown(int x){
	ADD(lson,seg[x].tag),ADD(rson,seg[x].tag),seg[x].tag=0;
}
void modify(int x,int l,int r,int L,int R,int vl){
	if(l>R||r<L)return;
	if(L<=l&&r<=R){ADD(x,vl);return;}
	pushdown(x),modify(lson,l,mid,L,R,vl),modify(rson,mid+1,r,L,R,vl),pushup(x);
}
int query(int x,int l,int r,int L,int R){
	if(l>R||r<L)return 0x8080808080808080;
	if(L<=l&&r<=R)return seg[x].mx;
	pushdown(x);
	return max(query(lson,l,mid,L,R),query(rson,mid+1,r,L,R));
}
void setup(int x,int l,int r,int P,int vl){
	if(l>P||r<P)return;
	if(l==r){seg[x].mx=vl,seg[x].tag=0;return;}
	pushdown(x),setup(lson,l,mid,P,vl),setup(rson,mid+1,r,P,vl),pushup(x);
}
void build(int x,int l,int r){
	if(l==r){seg[x].mx=0x8080808080808080;return;}
	build(lson,l,mid),build(rson,mid+1,r),pushup(x); 
}
signed main(){
	read(n),read(m);
	for(int i=1;i<=n;i++)read(val[i]);
	for(int i=1,l,r,x;i<=m;i++)read(l),read(r),read(x),v[r].push_back(make_pair(l,x));
	build(1,1,n+1),setup(1,1,n+1,1,0);
	for(int i=1;i<=n;i++){
		setup(1,1,n+1,i+1,query(1,1,n,1,i));
		modify(1,1,n+1,1,i,-val[i]);
		for(auto j:v[i])modify(1,1,n+1,1,j.first,j.second);
//		for(int j=0;j<=i;j++)printf("%lld ",f[i&1][j]);puts("");
	}
	print(query(1,1,n+1,1,n+1));
	return 0;
} 

II.CF809D Hitchhiking in the Baltic States

fi表示长度为i的LIS结尾的最小值。为了方便,设gi表示前一个物品的fi(即滚动数组);

则对于一个[l,r]的物品:

  1. 对于gi1<l的位置,有fi=max(gi,l)

  2. 对于gi1[l,r1]的位置,有fi=max(gi,gi1+1)

  3. 对于gi1r的位置,转移不了,因此直接有fi=gi

考虑用平衡树实现。

对于转移1,因为fi是递增的所以这个转移只对第一个l的位置i生效。效果相当于直接插入一个数字l

对于转移2,因为我们已经插入过一个l了,因此实际上已经全体右移一位了,所以直接打一个+1 tag即可。

对于转移3,直接删去第一个rf即可。

操作采取fhq treap实现(因为要区间修改)。splay也可以实现。

#include<bits/stdc++.h>
using namespace std;
#define lson t[x].ch[0]
#define rson t[x].ch[1]
int cnt,n,root;
struct treap{
	int ch[2],val,rad,sz,tag;
}t[300100];
int newnode(int val){
	cnt++,t[cnt].rad=rand()*rand(),t[cnt].sz=1,t[cnt].val=val;
	return cnt;
}
void pushup(int x){
	t[x].sz=t[lson].sz+t[rson].sz+1;
}
void ADD(int x,int val){
	t[x].tag+=val,t[x].val+=val;
}
void pushdown(int x){
	if(!x)return;
	if(lson)ADD(lson,t[x].tag);
	if(rson)ADD(rson,t[x].tag);
	t[x].tag=0;
}
int merge(int x,int y){
	if(!y)return x;
	if(!x)return y;
	pushdown(x),pushdown(y);
	if(t[x].rad>t[y].rad){t[x].ch[1]=merge(t[x].ch[1],y),pushup(x);return x;}
	else{t[y].ch[0]=merge(x,t[y].ch[0]),pushup(y);return y;}
}
void split(int x,int val,int &u,int &v){//u:the subtree which <val;v:the subtree which >=val
	if(!x){u=v=0;return;}
	pushdown(x);
	if(t[x].val<val)u=x,split(rson,val,rson,v);
	else v=x,split(lson,val,u,lson);
	pushup(x);
}
int kth(int x,int k){
	if(!k)return 0;
	while(true){
		pushdown(x);
		if(t[lson].sz>=k)x=lson;
		else if(t[lson].sz+1<k)k-=t[lson].sz+1,x=rson;
		else return x;
	}
}
int suf(int val){//the largest node >= val
	int u=0,v=0,res;
	split(root,val,u,v);
	if(!v)return 0;
	res=kth(v,1);
	root=merge(u,v);
	return res;
}
void ins(int val){
	int a=0,b=0;
	split(root,val,a,b);
	root=merge(a,merge(newnode(val),b));
}
void del(int val){
	int a=0,b=0,c=0,d=0;
	split(root,val,a,b);
	split(b,val+1,c,d);
	c=merge(t[c].ch[0],t[c].ch[1]);
	root=merge(a,merge(c,d));
} 
void iterate(int x){
	if(!x)return;
	pushdown(x),iterate(lson),printf("%d ",t[x].val),iterate(rson);
}
void add(int l,int r){
	int a=0,b=0,c=0,d=0;
	split(root,l,a,b);
	split(b,r,c,d);
	if(c)ADD(c,1);
//	printf("A "),iterate(a),puts("");
//	printf("B "),iterate(c),puts("");
//	printf("C "),iterate(d),puts("");
	root=merge(a,merge(c,d));
}
int main(){
	scanf("%d",&n);
	for(int i=1,l,r;i<=n;i++){
		scanf("%d%d",&l,&r);
		if(i==1){ins(l);continue;}
		int x=suf(r);
		add(l,r);
		if(x)del(t[x].val);
		ins(l);
	}
	printf("%d\n",t[root].sz);
	return 0;
} 

III.[GYM102155J]stairways

首先,考虑暴力 n3 DP——设 fi,j,k 表示当前DP到第 i 个人,且第一条楼梯上到的最晚的人在时刻 j 到达,第二条楼梯在时刻 k

然后,观察到 j,k 中至少有一个值为前缀 max 的时刻值。故状态可以被压缩到二维——fi,j 表示第一条楼梯上的人是前缀 max,第二条楼梯在时刻 k

设前缀 maxs。然后有

fi,j={fi1,j+sai(j<ai)minjaifi1,j(j=ai)fi1,j+jai(j>ai)

考虑 fi,j,其中肯定应该是 j 越小,值越大,即 fi,j 应是单调递减的。假如在某个地方单调性被破坏,那肯定是在后方的东西更劣,可以直接舍去。

于是我们考虑全程只使用一个 f 数组,随着 i 增加改变里面的东西。设 f 表示老的 f 数组,然后,假设我们已经保证了 f 的单调性,则转移式可以直接变为:

fj={fj+sai(j<ai)fk(j=ai,k是小于等于j的最大元素)fj+jai(j>ai)

然后,ai 可以在最后统一减去,优化得

fj={fj+s(j<ai)fk+ai(j=ai,k是小于等于j的最大元素)fj+j(j>ai)

于是我们现在要解决两个问题,一是区间加定值 s,这个简单用个什么线段树平衡树之类轻松解决;关键是第二个问题,后缀全部增加 j,同时还要维护单调性。

我们对于每个位置 j,设小于它的最大元素是 k,维护一个值为 fkfjjk。当后缀带权加时,明显这个值会减少 1。而当这个值最终减少到 0 时,就说明单调性被破坏,可以删掉该元素了。

在前缀加 s 时,上述值并没有被改变;在修改 fai 的值时,只有 ai 附近的东西被改变了,暴力修改即可;在后缀带权加时,除了起始的地方,其他地方的值是全部减一的,于是仍然暴力修改起始处的值即可。

可以使用线段树,但这样就没法很方便地维护单调性;无论是分块还是平衡树维护都可以起到简单维护单调性的功效,直接上即可。

时间复杂度 O(nn)nlogn,取决于使用哪种数据结构。这里采取平衡树。

代码:

/*
f[i]=f[j] (j is the maximal element smaller than i
f[j]=f[j]+s-i (j<i)
f[j]=f[j]+j-i (j>i)

tms=upper[(f[k]-f[j])/(j-k)] where k<j.
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,a[100100],rt,cnt;
#define lson t[x].ch[0]
#define rson t[x].ch[1]
struct Treap{
	int key,rd,ch[2],sz;
	ll f,tagf,tagt,tms,mn;
}t[100100];
void pushup(int x){
	t[x].mn=t[x].tms,t[x].sz=1;
	if(lson)t[x].mn=min(t[x].mn,t[lson].mn),t[x].sz+=t[lson].sz;
	if(rson)t[x].mn=min(t[x].mn,t[rson].mn),t[x].sz+=t[rson].sz;
}
void ADDF(int x,ll y){if(x)t[x].tagf+=y,t[x].f+=y;}
void ADDT(int x,ll y=1){if(x)t[x].tagt+=y,t[x].tms-=y,t[x].mn-=y,t[x].f+=1ll*y*t[x].key;}
void pushdown(int x){
	ADDF(lson,t[x].tagf),ADDT(lson,t[x].tagt);
	ADDF(rson,t[x].tagf),ADDT(rson,t[x].tagt);
	t[x].tagf=t[x].tagt=0;
}
int merge(int x,int y){
	if(!x||!y)return x+y;
	if(t[x].rd>t[y].rd){pushdown(x),t[x].ch[1]=merge(t[x].ch[1],y),pushup(x);return x;}
	else{pushdown(y),t[y].ch[0]=merge(x,t[y].ch[0]),pushup(y);return y;}
}
void splitbykey(int x,int k,int &u,int &v){//u:<k.
	if(!x){u=v=0;return;}
	pushdown(x);
	if(t[x].key<k)u=x,splitbykey(rson,k,rson,v);
	else v=x,splitbykey(lson,k,u,lson);
	pushup(x);
}
void splitbysize(int x,int k,int &u,int &v){
	if(!x){u=v=0;return;}
	pushdown(x);
	if(t[lson].sz>=k)v=x,splitbysize(lson,k,u,lson);
	else u=x,splitbysize(rson,k-t[lson].sz-1,rson,v);
	pushup(x);
}
int Newnode(int key,ll f){
	int x=++cnt;
	t[x].f=f,t[x].key=key,t[x].rd=rand()*rand();
	pushup(x);
	return x;
}
ll flor(ll x,ll y){//the floor-rounded of x/y.
	if(x<=0)return 0;
	return (x-1)/y+1;
}
void func(int k,int pre){
	int a,b,c,d,e;
	splitbykey(rt,k,b,c);
	ADDF(b,pre);
	splitbysize(b,t[b].sz-1,a,b);
	splitbykey(c,k+1,c,d);
	if(!c)c=Newnode(k,0),t[c].f=t[b].f+k-pre;//because f[b] has already been added by pre.
	else t[c].f+=k;
	t[c].tms=flor(t[b].f-t[c].f,t[c].key-t[b].key);
	pushup(c);
	ADDT(d);
	splitbysize(d,1,d,e);
	if(d)t[d].tms=flor(t[c].f-t[d].f,t[d].key-t[c].key),pushup(d);
	rt=merge(merge(merge(merge(a,b),c),d),e);
}
int getmn(int x){
	pushdown(x);
	if(lson&&t[lson].mn<=0)return getmn(lson);
	if(t[x].tms<=0)return t[x].key;
	return getmn(rson);
}
void reset(){
	while(rt&&t[rt].mn<=0){
		int k=getmn(rt);
		int a,b,c,d,e;
		splitbykey(rt,k,b,c);
		splitbysize(b,t[b].sz-1,a,b);
		splitbykey(c,k+1,c,d);
		splitbysize(d,1,d,e);
		if(d)t[d].tms=flor(t[b].f-t[d].f,t[d].key-t[b].key),pushup(d);
		rt=merge(merge(a,b),merge(d,e));
	}
}
ll getres(){
	int a,b;
	splitbysize(rt,t[rt].sz-1,a,b);
	ll tmp=t[b].f;
	rt=merge(a,b);
	return tmp;
}
void iterate(int x){
	if(!x)return;
	pushdown(x);
	iterate(lson);
	printf("%d:(F:%d TMS:%d MN:%d)\n",t[x].key,t[x].f,t[x].tms,t[x].mn);
	iterate(rson);
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	rt=Newnode(0,a[1]),t[rt].tms=0x3f3f3f3f;
	for(int i=2,j=a[1];i<=n;i++){
		j=max(j,a[i]);
		func(a[i],j);
//		puts("BEF:");iterate(rt);
		reset();
//		puts("AFT:");iterate(rt);puts("");
	}
	ll res=getres();
	for(int i=1;i<=n;i++)res-=a[i];
	printf("%lld\n",res);
	return 0;
}

IV.[ARC115E]LEQ and NEQ

fi,j 表示位置 ij 的方案数。则 fi,j=(kfi1,k)fi1,k。于是我们便考虑线段树优化,只需要实现四种操作:整体求和,整体翻转,整体加,后缀赋零。可以直接用带乘法和加法 tag 的线段树轻松解决。

时间复杂度 O(nlogn)

代码:

#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
int n,m,a[500100];
vector<int>v;
#define lson x<<1
#define rson x<<1|1
#define mid ((l+r)>>1)
#define X x,l,r
#define LSON lson,l,mid
#define RSON rson,mid+1,r
struct SegTree{int mul,add,sum;}seg[2001000];
void ADD(int x,int l,int r,int y){(seg[x].add+=y)%=mod,(seg[x].sum+=1ll*(v[r]-v[l-1])*y%mod)%=mod;}
void MUL(int x,int y){seg[x].mul=1ll*seg[x].mul*y%mod,seg[x].add=1ll*seg[x].add*y%mod,seg[x].sum=1ll*seg[x].sum*y%mod;}
void pushdown(int x,int l,int r){MUL(lson,seg[x].mul),MUL(rson,seg[x].mul),seg[x].mul=1;ADD(LSON,seg[x].add),ADD(RSON,seg[x].add),seg[x].add=0;}
void pushup(int x){seg[x].sum=(seg[lson].sum+seg[rson].sum)%mod;}
void build(int x,int l,int r){seg[x].mul=1,seg[x].add=0,seg[x].sum=(v[r]-v[l-1])%mod;if(l!=r)build(LSON),build(RSON);}
void add(int x,int l,int r,int L,int R,int val){if(l>R||r<L)return;if(L<=l&&r<=R)ADD(X,val);else pushdown(X),add(LSON,L,R,val),add(RSON,L,R,val),pushup(x);}
void mul(int x,int l,int r,int L,int R,int val){if(l>R||r<L)return;if(L<=l&&r<=R)MUL(x,val);else pushdown(X),mul(LSON,L,R,val),mul(RSON,L,R,val),pushup(x);}
int main(){
	scanf("%d",&n),v.push_back(0);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),v.push_back(a[i]);
	sort(v.begin(),v.end()),v.resize(unique(v.begin(),v.end())-v.begin()),m=v.size()-1;
	for(int i=1;i<=n;i++)a[i]=lower_bound(v.begin(),v.end(),a[i])-v.begin();
	build(1,1,m),mul(1,1,m,a[1]+1,m,0);
	for(int i=2,sum;i<=n;i++)sum=seg[1].sum,mul(1,1,m,1,m,mod-1),add(1,1,m,1,m,sum),mul(1,1,m,a[i]+1,m,0);
	printf("%d\n",seg[1].sum);
	return 0;
}

II.数据结构加速转移

在转移简单而有规律时,可以使用数据结构加速转移。

I.[BJOI2017]喷式水战改

这题类似于毒瘤数据结构题,想起来非常简单,但是写起来……

平衡树是必须写的——这种毒瘤的维护肯定要写平衡树。

然后说一下怎么DP吧。在每个节点上维护f[i][j],表示在以该节点为根的子树上,阶段i到阶段j的最大收益。

直接在pushup时维护即可。

主要是这个插入难,要分裂某个节点。

代码:

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define lson t[x].ch[0]
#define rson t[x].ch[1] 
#define ROOT t[0].ch[1]
int n,cnt;
struct SPLAY{
	int num[4],tot,sz,fa,ch[2],f[4][4];
	SPLAY(int a=0,int b=0,int c=0,int d=0){
		num[0]=a,num[1]=b,num[2]=c,num[3]=a,tot=sz=d;
		fa=ch[0]=ch[1]=0;
		memset(f,0,sizeof(f));
		for(int i=0;i<4;i++)for(int j=i;j<4;j++)for(int k=i;k<=j;k++)f[i][j]=max(f[i][j],num[k]*tot);
	}
}t[200100];
void pushup(int x){
	if(!x)return;
	int g[4][4];
	memset(t[x].f,0,sizeof(t[x].f));
	t[x].sz=t[x].tot;
	for(int i=0;i<4;i++)for(int j=i;j<4;j++)for(int k=i;k<=j;k++)t[x].f[i][j]=max(t[x].f[i][j],t[x].num[k]*t[x].tot);
	if(lson){
		memset(g,0,sizeof(g));
		for(int i=0;i<4;i++)for(int j=i;j<4;j++)for(int k=i;k<=j;k++)g[i][j]=max(g[i][j],t[lson].f[i][k]+t[x].f[k][j]);
		for(int i=0;i<4;i++)for(int j=i;j<4;j++)t[x].f[i][j]=max(t[x].f[i][j],g[i][j]);
		t[x].sz+=t[lson].sz;
	}
	if(rson){
		memset(g,0,sizeof(g));
		for(int i=0;i<4;i++)for(int j=i;j<4;j++)for(int k=i;k<=j;k++)g[i][j]=max(g[i][j],t[x].f[i][k]+t[rson].f[k][j]);
		for(int i=0;i<4;i++)for(int j=i;j<4;j++)t[x].f[i][j]=max(t[x].f[i][j],g[i][j]);
		t[x].sz+=t[rson].sz;
	}
}
void connect(int x,int y,int dir){
	if(x)t[x].fa=y;
	t[y].ch[dir]=x;
}
int identify(int x){
	return t[t[x].fa].ch[1]==x;
}
void rotate(int x){
	int y=t[x].fa;
	int z=t[y].fa;
	int dirx=identify(x);
	int diry=identify(y);
	int b=t[x].ch[!dirx];
	connect(b,y,dirx);
	connect(y,x,!dirx);
	connect(x,z,diry);
	pushup(y),pushup(x);
}
void splay(int x,int y){
	y=t[y].fa;
	while(t[x].fa!=y){
		int fa=t[x].fa;
		if(t[fa].fa==y)rotate(x);
		else if(identify(fa)==identify(x))rotate(fa),rotate(x);
		else rotate(x),rotate(x);
	}
}
int findkth(int k){
	if(!k)return 0;
	int x=ROOT;
	while(true){
		if(t[lson].sz>=k)x=lson;
		else if(t[x].tot+t[lson].sz<k)k-=(t[x].tot+t[lson].sz),x=rson;
		else{splay(x,ROOT);return x;}
	}
}
void ins(SPLAY q,int pos){
	if(!ROOT){ROOT=++cnt,t[ROOT]=q;return;}
	if(!pos){
		int x=ROOT;
		while(lson)x=lson;
		splay(x,ROOT);
		++cnt;
		t[cnt]=q;
		connect(cnt,x,0);
		pushup(x);
	}else{
		int x=findkth(pos);
		int left=t[x].tot;
		t[x].tot=pos-t[lson].sz;
		left-=t[x].tot;
		++cnt;
		t[cnt]=q;
		connect(rson,cnt,1);
		connect(cnt,x,1);
		int y=cnt;
		if(left){
			++cnt;
			t[cnt]=SPLAY(t[x].num[0],t[x].num[1],t[x].num[2],left);
			connect(t[y].ch[1],cnt,1);
			connect(cnt,y,1);
			pushup(cnt);
		}
		pushup(y),pushup(x),splay(y,ROOT);
	}
}
void iterate(int x){
	if(lson)iterate(lson);
	for(int i=0;i<t[x].tot;i++)printf("(%lld,%lld,%lld)",t[x].num[0],t[x].num[1],t[x].num[2]);
	if(rson)iterate(rson);
}
signed main(){
	scanf("%lld",&n);
	for(int i=1,lans=0,a,b,c,d,e;i<=n;i++){
		scanf("%lld%lld%lld%lld%lld",&a,&b,&c,&d,&e);
		ins(SPLAY(b,c,d,e),a);
		printf("%lld\n",t[ROOT].f[0][3]-lans);
		lans=t[ROOT].f[0][3];
//		iterate(ROOT);puts("");
	}
	return 0;
}

II.[HEOI2016/TJOI2016]序列

说实话我对于这道题应该归到DP还是树套树时曾经纠结了很久

我们回忆一下正牌的LIS:

对于j<i  ajaif[i]可以从f[j]转移过来。

现在,我们设mxi,mni分别表示位置i所有变化中的最大值以及最小值,

则对于j<i  mxjai  ajmnif[i]可以从f[j]转移过来。

直接暴力转移,复杂度O(n2),期望得分50%

代码:

#include<bits/stdc++.h>
using namespace std;
int n,m,a[100100],mn[100100],mx[100100],f[100100],res;
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),mn[i]=mx[i]=a[i];
	for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),mn[x]=min(mn[x],y),mx[x]=max(mx[x],y);
	for(int i=1;i<=n;i++){
		for(int j=i-1;j;j--)if(mx[j]<=a[i]&&a[j]<=mn[i])f[i]=max(f[i],f[j]);
		f[i]++,res=max(res,f[i]);
	}
	printf("%d\n",res);
	return 0;
}

然后我们发现,如果把(mxj,aj)看成一对坐标的话,它又转变成矩形内部max了。

然后无脑树套树维护一下即可。

我吹爆树状数组套权值线段树!!!好写到爆!!!

#include<bits/stdc++.h>
using namespace std;
const int N=100000;
#define mid ((l+r)>>1)
int n,m,a[100100],mn[100100],mx[100100],f[100100],res,cnt,root[100100];
struct node{
	int lson,rson,mx;
}seg[10010000];
void mod(int &x,int l,int r,int P,int val){
	if(l>P||r<P)return;
	if(!x)x=++cnt;
	seg[x].mx=max(seg[x].mx,val);
	if(l==r)return;
	mod(seg[x].lson,l,mid,P,val),mod(seg[x].rson,mid+1,r,P,val);
}
void MOD(int x,int y,int val){
	while(x<=N)mod(root[x],1,N,y,val),x+=x&-x;
}
int ask(int x,int l,int r,int P){
	if(!x||l>P)return 0;
	if(r<=P)return seg[x].mx;
	return max(ask(seg[x].lson,l,mid,P),ask(seg[x].rson,mid+1,r,P));
}
int ASK(int x,int y){
	int ans=0;
	while(x)ans=max(ans,ask(root[x],1,N,y)),x-=x&-x;
	return ans;
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),mn[i]=mx[i]=a[i];
	for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),mn[x]=min(mn[x],y),mx[x]=max(mx[x],y);
	for(int i=1;i<=n;i++){
		f[i]=ASK(a[i],mn[i]);
		f[i]++,res=max(res,f[i]);
		MOD(mx[i],a[i],f[i]);
	}
	printf("%d\n",res);
	return 0;
}

III.[九省联考2018]秘密袭击coat

首先先讲一种暴力但能过的方法。

很容易就会往每个值各被计算几次的方向去想。于是我们枚举每个节点,计算有多少种可能下该节点是目标节点。

为了避免相同的值的影响,我们在值相同的点间也决出一种顺序,即,若两个值相同的点在作比较,依照上文定下的那种顺序决定。

于是我们考虑从该枚举的点 x 出发,遍历整棵子树同时DP。设 fi,j 表示 i 子树中有 j 个点的危险程度 dx。于是就直接背包转移就行了。

看上去复杂度是 O(n3),但是加上下述两个优化就可以过了:

  1. 第二维最大只枚举到 m(这里的 m 即题面中的 k,因为 k 这个字母我们接下来还要用)

  2. 第二维最大只枚举到子树大小 sz

然后就过了,跑的还比正解都要快。

代码:

#include<bits/stdc++.h>
using namespace std;
const int mod=64123;
int n,m,W,d[2010],f[2010][2010],p[2010],q[2010],res,sz[2010];
vector<int>v[2010];
void dfs(int x,int fa,int lim){
	for(int i=0;i<=sz[x];i++)f[x][i]=0;
	f[x][sz[x]=(q[x]>=lim)]=1;
	for(auto y:v[x]){
		if(y==fa)continue;
		dfs(y,x,lim);
//		printf("%d:",x);for(int i=0;i<=m;i++)printf("%d ",f[x][i]);puts("");
//		printf("%d:",y);for(int i=0;i<=m;i++)printf("%d ",f[y][i]);puts("");
		for(int i=sz[x];i>=0;i--)for(int j=min(m-i,sz[y]);j>=0;j--)(f[x][i+j]+=1ll*f[x][i]*f[y][j]%mod)%=mod;
		sz[x]=min(sz[x]+sz[y],m);
//		printf("%d:",x);for(int i=0;i<=m;i++)printf("%d ",f[x][i]);puts("\n");
	}
}
int main(){
	scanf("%d%d%d",&n,&m,&W);
	for(int i=1;i<=n;i++)scanf("%d",&d[i]),p[i]=i;
	sort(p+1,p+n+1,[](int u,int v){return d[u]<d[v];});
	for(int i=1;i<=n;i++)q[p[i]]=i;
	for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	for(int i=1;i<=n;i++){
//		printf("%d\n",q[i]);
		if(q[i]>n-m+1)continue;
		dfs(i,0,q[i]);
		(res+=1ll*d[i]*f[i][m]%mod)%=mod;
	}
	printf("%d\n",res);
	return 0;
}

然后是正解。

我们要求 STKth of S

考虑枚举该 Kth 的值为 i,则要求 i=1iST[Kth of S=i]

考虑让每个 i 拆分成在所有的 ji 的位置上各被计算一次,则要求 i=1ST[Kth of Si]。(这同时也是期望DP的经典套路)

考虑令 cnti 表示 i 的数的总数。则要求 i=1ST[cntim]。(注意这里的 m 即为 k

考虑对于每个连通块,在树上最高处计算它的贡献。设 fi,j,k 表示以 i 为根的子树内,当前统计的是 cntj,且 cntj=k 的方案数。转移是裸的背包卷积。

考虑如何求答案。因为我们是在最高处计算贡献,所以就要求 i=1nj=1Wk=mnfi,j,k

因为我们是卷积,所以考虑FFT转移。又因为它一直在卷,所以我们干脆考虑压根不把它复原成系数式,就纯粹用点集表示。

更准确地说,因为 fi,j 的生成函数是 k=1nfi,j,kxk,一个 n 次多项式,所以我们直接枚举 x[1,n+1],然后分别求出这时的生成函数的值,最后拉格朗日插值一下就插回系数式了。

则,在合并父亲 x 和儿子 yf 数组时,因为是点集式,所以直接对应位置相乘就行了。

但是就算有了这么伟大的思路,我们算算复杂度,还是 O(n3) 的。有没有更棒的复杂度?

我们发现,最终要对所有 xf 数组的和,倒不如在正常处理的过程中就顺便维护了。于是我们设 gi,j=ksubtreeifk,j,则最终要求的就是 i=mng1,i。当然,依据分配律,我们还是可以直接一股脑求出 i=1ng1,i,待插出系数式后再取 m 的项。

我们思考当合并 fx,ify,i 时会发生什么:

fx,ifx,i×(fy,i+1)(采用 fy,i 或不用)

gx,igx,i+gy,i

在全体结束后,再有 gx,igx,i+fx,i

同时,为了便于合并,我们采用线段树合并来维护DP数组。(这种操作被称作整体DP

我们考虑初始化,发现假如我们最外层枚举的点值是 X,则所有 idxfx,i 在结束时都要乘上一个 X

明显这个初始状态大体是区间的,非常适合线段树合并。

但是,就算这样,暴力合并的复杂度仍然 O(n3),必须考虑在区间上把它做掉。

于是,一个天才般的想法诞生了:

观察到每次的变换都是 (f,g)(af+b,cf+d+g) 的形式。

而这个变换,可以用四元组 (a,b,c,d) 唯一刻画。

同时,展开式子,就会发现这个变换具有结合律(虽然很遗憾的是,大部分情形下它不具有交换律)。

假如我们初始令 b=f,d=g 的话,就会发现,做一次上述操作,它就自动帮你更新了 fg

于是,我们把它看作区间的 tag,然后线段树合并就非常简单了。

同时,要注意的是,其单位元是 (1,0,0,0)

我们来总结一下操作:当合并的时候,我们希望 fxfx×(fy+1),而 fy+1 可以通过在遍历完 y 的子树后打上全体 +1tag 解决,当然这里不需要额外增加其它的 tag,我们发现 (1,1,0,0) 刚好胜任了这个操作。于是现在 fxfx×fy(fy,0,0,0)tag 即可(需要注意的是,fy 是线段树上 y 处的 b)。gxgx+gy(1,0,0,gy) 即可,而 gy 则是 d。两个乘起来,就是使用 (b,0,0,d)

最后合并 fg 的时候,则要使用 (1,0,1,0),意义通过展开即可得到就是将 f 加到 g。而乘上 X 的操作,使用 (X,0,0,0) 即可。

需要注意的是,这里并不能标记永久化,主要是因为从四元组中抽出 bd 的操作并非线性变换,不能打到 tag 上去,在线段树合并的时候要先一路下传到某一方已经没有叶子了再合并。

同时,使用 unsigned int 可以刚好把 64123 卡进一次乘法内不爆。

代码(一些比较疑惑的地方已经加了注释):

#include<bits/stdc++.h>
using namespace std;
#define int unsigned int
const int mod=64123;
int n,m,W,d[2010],X,cnt,bin[5010000],tp,rt[2010],a[2010];
struct dat{//(f,g)->(af+b,cf+d+g)
	int a,b,c,d;
	dat(){a=1,b=c=d=0;}
	dat(int A,int B,int C,int D){a=A,b=B,c=C,d=D;}
	friend dat operator*(const dat&u,const dat&v){return dat((u.a*v.a)%mod,(u.b*v.a+v.b)%mod,(u.a*v.c+u.c)%mod,(u.b*v.c+u.d+v.d)%mod);}
	void operator*=(const dat&v){(*this)=(*this)*v;}
	void print()const{printf("(%u %u %u %u)\n",a,b,c,d);}
};
#define mid ((l+r)>>1)
int newnode(){return tp?bin[tp--]:++cnt;}
struct SegTree{
	int lson,rson;
	dat tag;
}seg[5010000];
void erase(int &x){if(x)seg[x].tag=dat(),erase(seg[x].lson),erase(seg[x].rson),bin[++tp]=x,x=0;}//erase all the subtree of x.
void pushdown(int x){
	if(!seg[x].lson)seg[x].lson=newnode();
	if(!seg[x].rson)seg[x].rson=newnode();
	seg[seg[x].lson].tag*=seg[x].tag,seg[seg[x].rson].tag*=seg[x].tag,seg[x].tag=dat();
}
void modify(int &x,int l,int r,int L,int R,dat val){
	if(l>R||r<L)return;
	if(!x)x=newnode();
	if(L<=l&&r<=R){seg[x].tag*=val;return;}
	pushdown(x),modify(seg[x].lson,l,mid,L,R,val),modify(seg[x].rson,mid+1,r,L,R,val);
}
void merge(int &x,int &y){
	if(!seg[x].lson&&!seg[x].rson)swap(x,y);
	if(!seg[y].lson&&!seg[y].rson){seg[x].tag*=dat(seg[y].tag.b,0,0,seg[y].tag.d);return;}
	pushdown(x),pushdown(y),merge(seg[x].lson,seg[y].lson),merge(seg[x].rson,seg[y].rson);
}
int query(int x,int l,int r){
	if(l==r)return seg[x].tag.d;
	pushdown(x);
	return (query(seg[x].lson,l,mid)+query(seg[x].rson,mid+1,r))%mod;
}
void iterate(int x,int l,int r){
	if(!x)return;
	printf("%u:[%u,%u]\n",x,l,r);seg[x].tag.print();
	iterate(seg[x].lson,l,mid),iterate(seg[x].rson,mid+1,r);
}
vector<int>v[2010];
void dfs(int x,int fa){
	modify(rt[x],1,W,1,W,dat(0,1,0,0));//set all into (0,1,0,0),which means only f=1.
	for(auto y:v[x])if(y!=fa)dfs(y,x),merge(rt[x],rt[y]),erase(rt[y]);
	modify(rt[x],1,W,1,d[x],dat(X,0,0,0));//those <=d[x] are multiplied by an X
	modify(rt[x],1,W,1,W,dat(1,1,1,0));
	//product of (1,0,1,0) and (1,1,0,0), first means add f to g(to calculate the real g), second means add 1 to f (stands for x itself not chosen at x's father)
}
int all[2010],tmp[2010],res;
int ksm(int x,int y=mod-2){int z=1;for(;y;y>>=1,x=x*x%mod)if(y&1)z=z*x%mod;return z;}
void Lagrange(){
	all[0]=1;
	for(int i=1;i<=n+1;i++)for(int j=i-1;j<=i;j--)(all[j+1]+=all[j])%=mod,(all[j]*=mod-i)%=mod;//note that j is unsigned!!!
//	for(int i=0;i<=n+1;i++)printf("%u ",all[i]);puts("");
	for(int i=1;i<=n+1;i++){
		int inv=ksm(mod-i),sum=0;
		for(int j=0;j<=n;j++)tmp[j]=all[j];
		for(int j=0;j<=n;j++)(tmp[j]*=inv)%=mod,(tmp[j+1]+=mod-tmp[j])%=mod;
//		if(i>=1410){for(int j=0;j<=n;j++)printf("%u ",tmp[j]);puts("");}
		for(int j=m;j<=n;j++)sum+=tmp[j];sum%=mod;
		for(int j=1;j<=n+1;j++)if(j!=i)(sum*=ksm((i-j+mod)%mod))%=mod;
		res+=sum*a[i]%mod;
	}
	res%=mod;
}
signed main(){
	scanf("%u%u%u",&n,&m,&W);
	for(int i=1;i<=n;i++)scanf("%u",&d[i]);
	for(int i=1,x,y;i<n;i++)scanf("%u%u",&x,&y),v[x].push_back(y),v[y].push_back(x);
	for(X=1;X<=n+1;X++)dfs(1,0),a[X]=query(rt[1],1,W),erase(rt[1]);
//	for(int i=1;i<=n+1;i++)printf("%u ",a[i]);puts("");
	Lagrange();printf("%u\n",res);
	return 0;
}

III.动态 DP

常见的静态 DP 带上了修改,此时可以尝试动态 DP。

I.【模板】"动态 DP"&动态树分治

裸的树上最大独立集怎么做?设 fx,0 表示在 x 子树中, x 不选的最大答案;fx,1 表示在 x 子树中,x 选的最大答案。则有

fx,0=ysonxmax(fy,0,fy,1)

fx,1=ax+ysonxfy,0

现在要支持修改,怎么办?

发现,每次修改,DP值受到影响的位置是从修改位置到根的一条路径。听上去像是树剖应用的场景?

于是我们考虑树剖,并设 gx,0/1 表示忽略 x 的重儿子时的答案。则,fx,0=max(fy,0,fy,1)+gx,0,fx,1=fy,0+gx,1,其中 yx 的重儿子。

现在,我们考虑将其转成矩阵的形式。则有

[fy,0fy,1][gx,0gx,1gx,0]=[fx,0fx,1]

其中,矩阵乘法的定义是 uv=wwi,j=maxk{ui,k+vk,j}。手推可得其满足结合律。

于是,我们考虑从一个叶子的 f 数组开始(令人惊喜的是,叶子节点的 fg 是相等的(因为其不存在儿子),这使得转移很方便),一路往上乘矩阵,即可得到该叶子所在重链上任意节点的 f 数组。并且,因为任意重链的结尾节点都是叶子,所以通过上述方法可以求出任意节点的 f 数组。

现在考虑带修。发现,一次修改只会影响到根的路径上所有重链顶的父亲的 g 数组,而这样的父亲最多只有 O(logn) 个,故直接暴力跳链修改父亲即可。

时间复杂度 O(nlog2n)

代码:

#include<bits/stdc++.h>
using namespace std;
const int inf=0xd0d0d0d0;
int n,m,a[100100],f[100100][2],g[100100][2];//0:self unchosen 1:self chosen
int fa[100100],son[100100],sz[100100],dfn[100100],rev[100100],top[100100],bot[100100],tot;
vector<int>v[100100];
void dfs1(int x){
	sz[x]=1,f[x][0]=0,f[x][1]=a[x];
	for(auto y:v[x])if(y!=fa[x]){
		fa[y]=x,dfs1(y),sz[x]+=sz[y];
		if(sz[son[x]]<sz[y])son[x]=y;
		f[x][0]+=max(f[y][0],f[y][1]),f[x][1]+=f[y][0];
	}
}
void dfs2(int x){
	dfn[x]=++tot,rev[tot]=x;if(!top[x])top[x]=x;if(son[x])top[son[x]]=top[x],dfs2(son[x]);else bot[top[x]]=x;
	for(auto y:v[x])if(y!=fa[x]&&y!=son[x])dfs2(y),g[x][0]+=max(f[y][0],f[y][1]),g[x][1]+=f[y][0];
}
#define lson x<<1
#define rson x<<1|1
#define mid ((l+r)>>1)
struct Matrix{
	int t[2][2];
	Matrix(){memset(t,inf,sizeof(t));}
	Matrix(int x){t[1][0]=t[0][1]=inf,t[0][0]=t[1][1]=0;}
	int*operator[](const int&x){return t[x];}
	friend Matrix operator*(Matrix u,Matrix v){Matrix w;for(int i=0;i<2;i++)for(int j=0;j<2;j++)for(int k=0;k<2;k++)w[i][j]=max(w[i][j],u[i][k]+v[k][j]);return w;}
	void print()const{for(int i=0;i<2;i++,puts(""))for(int j=0;j<2;j++)printf("%d ",t[i][j]);}
	int zero(){return max(max(t[0][0],t[1][0]),max(t[0][1],t[1][1]));}
	int one(){return max(t[0][0],t[1][0]);}
}seg[400100];
Matrix Generate(int x){Matrix M;M[0][0]=g[x][0],M[1][0]=g[x][0],M[0][1]=g[x][1],M[1][1]=inf;return M;}
void build(int x,int l,int r){if(l==r)seg[x]=Generate(rev[l]);else build(lson,l,mid),build(rson,mid+1,r),seg[x]=seg[rson]*seg[lson];}
Matrix query(int x,int l,int r,int L,int R){if(l>R||r<L)return Matrix(0);if(L<=l&&r<=R)return seg[x];return query(rson,mid+1,r,L,R)*query(lson,l,mid,L,R);}
void reset(int x,int l,int r,int P){if(l>P||r<P)return;if(l==r)seg[x]=Generate(rev[P]);else reset(lson,l,mid,P),reset(rson,mid+1,r,P),seg[x]=seg[rson]*seg[lson];}
int modify(int x,int y){
	Matrix M;
	for(int i=x;top[i]!=1;)M=query(1,1,n,dfn[top[i]],dfn[bot[top[i]]]),i=fa[top[i]],g[i][0]-=M.zero(),g[i][1]-=M.one();
	g[x][1]+=y-a[x],a[x]=y,reset(1,1,n,dfn[x]);
	for(int i=x;top[i]!=1;)M=query(1,1,n,dfn[top[i]],dfn[bot[top[i]]]),i=fa[top[i]],g[i][0]+=M.zero(),g[i][1]+=M.one(),reset(1,1,n,dfn[i]);
	M=query(1,1,n,1,dfn[bot[1]]);
	return M.zero();
}
int main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]),g[i][1]=a[i];
	for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	dfs1(1),dfs2(1),build(1,1,n);
	for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),printf("%d\n",modify(x,y));
	return 0;
}

II.[SDOI2017]切树游戏

fx,0,i 表示 x 子树中,所有包含 x 且异或和为 i 的连通块数量,fx,1,i 表示 x 子树中异或和为 i 的连通块数量。显然,有公式

fx,1,i=fx,0,i+ysubtreexfy,1,i

现在考虑 fx,0,i 的转移。假如我们要合并 x 与某个儿子 y 的DP数组,则显然有公式

fx,0,i=jxork=ifx,0,j×(fy,0,k+[k=0])

其中 f 是老的 f 数组。

发现这是异或卷积形式,可以FWT优化。

但是我们发现,因为DP过程中全程要么在卷积,要么在做加法,对FWT后的数组作也是一样的。因此我们重新修改定义,将 f 数组全数修改为FWT后的数组。又因为 z0 这个函数在FWT后会得到 i=0mzi,因此转移式变更为

fx,1,i=fx,0,i+ysubtreexfy,1,i

fx,0,i=fx,0,j×(fy,0,k+1)

直接硬DP,单次复杂度 O(nm)

现在考虑轻重链剖分。设 g 表示仅考虑轻儿子时的答案。若只考虑 x 的一个重儿子 y,则有

fx,0=gx,0×(fy,0+1)=gx,0fy,0+gx,0

fx,1=fx,0+gx,1+fy,1=gx,0fy,0+gx,0+gx,1+fy,1

当然,这里的所有东西都是函数,连上面的 1 都不例外,指的是元素全为 1 的函数。

同时,要留心一点常规 g 的转移:

gx,0=FWT(zax)ylightsonx(fy,0+1)

gx,1=ylightsonxfy,1

需要注意的是,叶节点的 g 数组应直接是其 f 数组,故对于叶子节点,有 gx,0=gy,0=FWT(zax)

我们可以列出转移矩阵来:

[fy,0fy,11][gx,0gx,00010gx,0gx,0+gx,11]=[fx,0fx,11]

3×3 矩阵就有着 33 的常数,不好玩。但是,当我们计算两个上述矩阵的积后

[a1b10010c1d11][a2b20010c2d21]=[a1a2b1+a1b20010a2c1+c2b2c1+d1+d21]

发现矩阵的积不会影响其余位置的取值。

因此我们考虑只维护 a,b,c,d 四个位置的值,即可压缩矩阵为 2×2

于是现在的转移矩阵就压缩为

[a1b1c1d1][a2b2c2d2]=[a1a2b1+a1b2a2c1+c2b2c1+d1+d2]

同时,其存在单位元

[1000]

我们同时还要支持从链顶父亲的 g 数组中删掉一个 f 数组的操作。这样就需要支持除法。但是你不能保证FWT不会出现零。但我们可以维护每个位置的非零数之积是什么,再维护每个位置出现了多少零,这样分开处理,即可支持除法。

代码先咕着

upd:咕了一年了,咕完了。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=30100;
const int M=128;
const int mod=10007;
int ksm(int x,int y=mod-2){int z=1;for(;y;y>>=1,x=x*x%mod)if(y&1)z=z*x%mod;return z;}
int RED(int x){return x>=mod?x-mod:x;}
void ADJ(int&x){if(x>=mod)x-=mod;}
int read(){
	int x=0;
	char c=getchar();
	while(c>'9'||c<'0')c=getchar();
	while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x;
}
void print(int x){if(x>=10)print(x/10);putchar('0'+x%10);}
int n=read(),m=read(),q,invm=ksm(m),a[N],pc[M],INV[mod];
int son[N],dep[N],fa[N],dfn[N],rev[N],top[N],sz[N],bot[N];
vector<int>v[30100];
void dfs1(int x){
	dep[x]=dep[fa[x]]+1,sz[x]=1;
	for(auto y:v[x])if(y!=fa[x]){
		fa[y]=x,dfs1(y),sz[x]+=sz[y];
		if(sz[y]>sz[son[x]])son[x]=y;
	}
}
void dfs2(int x){
	static int tot;
	if(!top[x])top[x]=x;bot[top[x]]=x,dfn[x]=++tot,rev[tot]=x;
	if(son[x])top[son[x]]=top[x],dfs2(son[x]);
	for(auto y:v[x])if(y!=fa[x]&&y!=son[x])dfs2(y);
}
struct Array{
int a[M];
Array(){memset(a,0,sizeof(a));}
Array(int x){memset(a,0,sizeof(a));for(int i=0;i<m;i++)a[i]=(pc[i&x]&1?mod-1:1);}
int&operator[](const int&x){return a[x];}
friend Array operator+(const Array&u,const Array&v){
	Array w;for(int i=0;i<m;i++)w.a[i]=RED(u.a[i]+v.a[i]);return w;
}
friend Array operator-(const Array&u,const Array&v){
	Array w;for(int i=0;i<m;i++)w.a[i]=RED(u.a[i]+mod-v.a[i]);return w;
}
friend Array operator*(const Array&u,const Array&v){
	Array w;for(int i=0;i<m;i++)w.a[i]=u.a[i]*v.a[i]%mod;return w;
}
void FWT(int tp){
	for(int md=1;md<m;md<<=1)for(int pos=0;pos<m;pos+=md<<1){
		int*x=a+pos,*y=a+pos+md;
		for(int i=0;i<md;i++){
			int X=*x,Y=*y;
			*x++=RED(X+Y),*y++=RED(X+mod-Y);
		}
	}
	if(tp==-1)for(int i=0;i<m;i++)a[i]=a[i]*invm%mod;
}
int operator()(const int&x)const{
	int ret=0;
	for(int i=0;i<m;i++)ret+=(pc[i&x]&1?-a[i]:a[i]);
	return (ret%mod+mod)*invm%mod;
}
void print()const{putchar('[');for(int i=0;i<m;i++)printf("%d%c",a[i],i==m-1?']':',');}
}lis[N];
struct Litson{
Array val,tim;
Litson(){val=Array(0),tim=Array();}
void operator*=(const Array&v){for(int i=0;i<m;i++)if(!v.a[i])tim[i]++;else val[i]=val[i]*v.a[i]%mod;}
void operator/=(const Array&v){for(int i=0;i<m;i++)if(!v.a[i])tim[i]--;else val[i]=val[i]*INV[v.a[i]]%mod;}
Array operator()()const{Array ret=val;for(int i=0;i<m;i++)if(tim.a[i])ret.a[i]=0;return ret;}
}lid[N];
struct dat{
Array kd,bd,ks,bs;
dat(){kd=bd=ks=bs=Array();}
dat(Array KD,Array BD,Array KS,Array BS){kd=KD,bd=BD,ks=KS,bs=BS;}
friend dat operator*(const dat&u,const dat&v){
	dat w;
	w.kd=u.kd*v.kd,w.bd=u.kd*v.bd+u.bd;
	w.ks=u.ks*v.kd+v.ks,w.bs=u.ks*v.bd+u.bs+v.bs;
	return w;
}
void print()const{kd.print(),bd.print(),ks.print(),bs.print();puts("");}
};
int rt[N];
#define lson seg[x].ch[0]
#define rson seg[x].ch[1]
struct SegTree{int ch[2],l,r;dat tr;}seg[N<<1];
void build(int&x,int l,int r){
	static int cnt;
	x=++cnt,seg[x].l=l,seg[x].r=r;
	if(l==r)return;
	int L=l+1,R=r,B=(rev[r]==bot[top[rev[r]]]?0:sz[rev[r+1]]);
	while(L<R){
		int mid=(L+R+1)>>1;
		if(sz[rev[l]]-sz[rev[mid]]<sz[rev[mid]]-B)L=mid;else R=mid-1;
	}
	build(lson,l,R-1),build(rson,L,r);
}
void modify(int x,int P,dat val){
	if(seg[x].l==seg[x].r)seg[x].tr=val;
	else P<=seg[lson].r?modify(lson,P,val):modify(rson,P,val),seg[x].tr=seg[lson].tr*seg[rson].tr;
}
void reset(int x){
	Array dp=Array(a[x])*lid[x]();
	Array dps=lis[x]+dp;
	// printf("reset:%d\n",x);
	// lid[x]().print(),Array(a[x]).print(),puts("");
	// dp.print(),dps.print(),puts("");
	modify(rt[top[x]],dfn[x],dat(dp,dp,dp,dps));
}
void contri(int x,int tp){
	x=top[x];if(x==1)return;
	if(tp==1)lid[fa[x]]*=seg[rt[x]].tr.bd+Array(0),lis[fa[x]]=lis[fa[x]]+seg[rt[x]].tr.bs;
	else lid[fa[x]]/=seg[rt[x]].tr.bd+Array(0),lis[fa[x]]=lis[fa[x]]-seg[rt[x]].tr.bs;
}
void revest(int x){
	for(int i=x;i;i=fa[top[i]])contri(i,-1);
	scanf("%d",&a[x]);
	for(int i=x;i;i=fa[top[i]])reset(i),contri(i,1);
}
char s[10];
int main(){
	for(int i=0;i<m;i++)pc[i]=pc[i>>1]+(i&1);
	INV[1]=1;for(int i=2;i<mod;i++)INV[i]=INV[mod%i]*(mod-mod/i)%mod;
	for(int i=1;i<=n;i++)a[i]=read();
	for(int i=1,x,y;i<n;i++)x=read(),y=read(),v[x].push_back(y),v[y].push_back(x);
	dfs1(1),dfs2(1);
	for(int i=1;i<=n;i++)if(top[i]==i)build(rt[i],dfn[i],dfn[bot[i]]);
	for(int i=n;i;i--){int x=rev[i];reset(x);if(x==top[x])contri(x,1);}
	q=read();
	for(int i=1,x;i<=q;i++){
		scanf("%s",s),x=read();
		// seg[rt[1]].tr.print();
		if(s[0]=='C')revest(x);
		else print(seg[rt[1]].tr.bs(x)),putchar('\n');
	}
	return 0;
}

III.[LOJ#3539][JOI Open 2018]猫或狗

从割的角度试试发现没有思路,然后就自然想到对于每个极大的空连通块 DP,然后就自然想到动态 DP。动态 DP 是简单的。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=100100;
typedef long long ll;
const int inf=0x3f3f3f3f;
#ifndef Troverld
#include "catdog.h"
#endif
struct Matrix{
	ll a[3][3];
	Matrix(){for(int i=0;i<3;i++)for(int j=0;j<3;j++)a[i][j]=inf;}
	void clear(){memset(a,0,sizeof(a));}
	ll*operator[](const int&x){return a[x];}
	friend Matrix operator*(Matrix u,Matrix v){
		Matrix w;
		for(int i=0;i<3;i++)for(int j=0;j<3;j++)for(int k=0;k<3;k++)
			w[i][j]=min(w[i][j],u[i][k]+v[k][j]);
		return w;
	}
	void plu(ll*b)const{
		for(int i=0;i<3;i++){
			ll mn=inf;
			for(int j=0;j<3;j++)mn=min(mn,a[j][i]);
			b[i]+=mn;
		}
	}
	void mnu(ll*b)const{
		for(int i=0;i<3;i++){
			ll mn=inf;
			for(int j=0;j<3;j++)mn=min(mn,a[j][i]);
			b[i]-=mn;
		}
	}
	ll mval()const{
		ll mn=inf;for(int i=0;i<3;i++)for(int j=0;j<3;j++)mn=min(mn,a[i][j]);return mn;
	}
	void print(){for(int i=0;i<3;i++)printf("[%lld,%lld,%lld]",a[i][0],a[i][1],a[i][2]);puts("");}
}seg[N<<2];
ll lit[N][3];
Matrix restri(ll*a,int tp){
	Matrix r;for(int i=0;i<3;i++)for(int j=0;j<3;j++)r[i][j]=(i!=j);
	for(int i=0;i<3;i++){
		ll bon=(tp==2||tp==i?a[i]:inf);
		for(int j=0;j<3;j++)r[i][j]+=bon;
	}
	return r;
}
int n,son[N],fa[N],dep[N],sz[N],dfn[N],rev[N],top[N],bot[N],tot;
vector<int>v[N];
void dfs1(int x){
	sz[x]=1,dep[x]=dep[fa[x]]+1;
	for(auto y:v[x])if(y!=fa[x]){fa[y]=x,dfs1(y),sz[x]+=sz[y];if(sz[y]>sz[son[x]])son[x]=y;}
}
void dfs2(int x){
	dfn[x]=++tot,rev[tot]=x;
	if(!top[x])top[x]=x;
	bot[top[x]]=x;
	if(son[x])top[son[x]]=top[x],dfs2(son[x]);
	for(auto y:v[x])if(y!=fa[x]&&y!=son[x])dfs2(y);
}
#define lson x<<1
#define rson x<<1|1
#define mid ((l+r)>>1)
Matrix stt(){Matrix m;for(int i=0;i<3;i++)for(int j=0;j<3;j++)m[i][j]=(i!=j);return m;}
void build(int x,int l,int r){
	if(l==r)return seg[x]=stt(),void();
	build(lson,l,mid),build(rson,mid+1,r),seg[x]=seg[rson]*seg[lson];
}
void modify(int x,int l,int r,int P,Matrix m){
	if(l==r)return seg[x]=m,void();
	P<=mid?modify(lson,l,mid,P,m):modify(rson,mid+1,r,P,m),seg[x]=seg[rson]*seg[lson];
}
Matrix query(int x,int l,int r,int L,int R){
	if(L<=l&&r<=R)return seg[x];
	if(mid<L)return query(rson,mid+1,r,L,R);
	if(R<=mid)return query(lson,l,mid,L,R);
	return query(rson,mid+1,r,L,R)*query(lson,l,mid,L,R);
}
int sta[N];
void initialize(int _n,vector<int>a,vector<int>b){
	n=_n;
	for(int i=0;i<(int)a.size();i++)v[a[i]].push_back(b[i]),v[b[i]].push_back(a[i]);
	dfs1(1),dfs2(1),build(1,1,n);
//	for(int i=1;i<=n;i++)printf("[%d,%d]",top[i],dfn[i]);puts("");
	for(int x=1;x<=n;x++)sta[x]=2;
}
void reset(int x){
	while(x){
		query(1,1,n,dfn[top[x]],dfn[bot[top[x]]]).mnu(lit[fa[top[x]]]);
		modify(1,1,n,dfn[x],restri(lit[x],sta[x]));
		query(1,1,n,dfn[top[x]],dfn[bot[top[x]]]).plu(lit[fa[top[x]]]);
		x=fa[top[x]];
	}
}
int cat(int x){
	sta[x]=0,reset(x);return query(1,1,n,dfn[1],dfn[bot[1]]).mval();
}
int dog(int x){
	sta[x]=1,reset(x);return query(1,1,n,dfn[1],dfn[bot[1]]).mval();
}
int neighbor(int x){
	sta[x]=2,reset(x);return query(1,1,n,dfn[1],dfn[bot[1]]).mval();
}
#ifdef Troverld
int q;
int main(){
	freopen("data2.in","r",stdin);
	freopen("data.out","w",stdout);
	vector<int>a,b;
	scanf("%d",&n);
	for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),a.push_back(x),b.push_back(y);
	initialize(n,a,b);
	scanf("%d",&q);
	for(int i=1,x,y;i<=q;i++){
		scanf("%d%d",&x,&y);
		if(x==1)printf("%d\n",cat(y));
		if(x==2)printf("%d\n",dog(y));
		if(x==3)printf("%d\n",neighbor(y));
	}
	return 0;
}
#endif

IV.CF1208H Red Blue Tree

一个观察是,对于叶子固定的某局面,随着 K​​ 的增大,非叶子节点会由蓝翻红。因而可以设 fi​ 表示 i​ 节点翻红的时刻:蓝叶子为 ​,红叶子为 ​,并用 DDP 维护该式子。

现在考虑如何转移。fi 可以将所有儿子的 f 值排序后二分得到。故考虑维护一个仅考虑所有轻儿子时的 f 值,然后考虑进重儿子后会发现 f 值的 Δ[1,1]​。于是设 gi,1/0/1 表示子节点的 Δ 确定时,父节点的 Δ。转移就用两个 g 类倍增地拼接即可。使用线段树维护,复杂度对数平方。

代码中脑抽写了个 Δ[2,2] 的做法,但事实上用 [1,1] 就行了。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=100100;
int n,K,q;
int son[N],fa[N],dfn[N],top[N],bot[N],sz[N],dep[N],lit[N],tot;
vector<int>v[N];
void dfs1(int x){
	sz[x]=1,dep[x]=dep[fa[x]]+1;
	for(auto y:v[x])if(y!=fa[x]){
		fa[y]=x,dfs1(y),sz[x]+=sz[y];
		if(sz[y]>sz[son[x]])son[x]=y;
	}
}
namespace DSG{
int rt[N],cnt,bin[N*40],tp;
#define lson seg[x].ch[0]
#define rson seg[x].ch[1]
#define mid (l+r-(l+r<0))/2
struct SegTree{int ch[2],num;}seg[N*40];
int newnode(){return tp?bin[tp--]:++cnt;}
void delnode(int&x){bin[++tp]=x,seg[x].num=0,x=0;}
void modify(int&x,int l,int r,int P,int dlt){
//	printf("MODIFY:%d[%d,%d]%d:%d\n",x,l,r,P,dlt);
	if(!x)x=newnode();seg[x].num+=dlt;
	if(l!=r)P<=mid?modify(lson,l,mid,P,dlt):modify(rson,mid+1,r,P,dlt);
	if(!seg[x].num)delnode(x);
}
int retrieve(int x,int l,int r,int sum){
	if(l==r)return l;
	if(seg[rson].num-seg[lson].num+sum>=mid)
		return retrieve(rson,mid+1,r,sum-seg[lson].num);
	else return retrieve(lson,l,mid,sum+seg[rson].num);
}
int retrieve(int x,int l,int r,int P,int sum){
//	printf("RETRI:%d[%d,%d](%d):%d\n",x,l,r,P,sum);
	if(l==r)return l;
	if(seg[rson].num-seg[lson].num+sum+(P<=mid?-1:1)>=mid)
		return retrieve(rson,mid+1,r,P,sum-seg[lson].num);
	else return retrieve(lson,l,mid,P,sum+seg[rson].num);
}
#undef lson
#undef rson
#undef mid
void modify(int x,int P,int dlt){modify(rt[x],-n-4,n+4,P,dlt);}
int retrieve(int x){return retrieve(rt[x],-n-4,n+4,0);}
int retrieve(int x,int P){return retrieve(rt[x],-n-4,n+4,P,0);}
}
using DSG::modify;
using DSG::retrieve;
namespace TSG{
#define lson x<<1
#define rson x<<1|1
#define mid ((l+r)>>1)
struct Trans{
int a[5];
Trans(){for(int i=0;i<5;i++)a[i]=i-2;}
int&operator[](const int&x){return a[x+2];}
friend Trans operator*(Trans u,Trans v){Trans w;for(int i=-2;i<=2;i++)w[i]=v[u[i]];return w;}
void print()const{putchar('[');for(int i=0;i<5;i++)printf("%d%c",a[i],i==4?']':',');}
}seg[400100];
Trans query(int x,int l,int r,int L,int R){
//	printf("QUERY:%d[%d,%d][%d,%d]\n",x,l,r,L,R);
	if(l>R||r<L)return Trans();
	if(L<=l&&r<=R)return seg[x];
	return query(rson,mid+1,r,L,R)*query(lson,l,mid,L,R);
}
void endow(int x,int l,int r,int P,Trans V){
//	printf("ENDOW:%d[%d,%d]%d",x,l,r,P);V.print(),puts("");
	if(l==r)seg[x]=V;
	else P<=mid?endow(lson,l,mid,P,V):endow(rson,mid+1,r,P,V),seg[x]=seg[rson]*seg[lson];
}
#undef lson
#undef rson
#undef mid
int DP(int x){return query(1,1,n,dfn[x],dfn[bot[top[x]]])[0]+lit[x];}
void reendow(int x){
//	printf("REENDOW:%d\n",x);
	Trans V;
	if(son[x])for(int i=-2,f=lit[son[x]];i<=2;i++)V[i]=retrieve(x,f+i)-lit[x];
	else for(int i=-2;i<=2;i++)V[i]=i;
//	V.print(),puts("");
	endow(1,1,n,dfn[x],V);
}
}
using TSG::DP;
using TSG::reendow;
using TSG::Trans;
void dfs2(int x){
//	printf("%d\n",x);
	dfn[x]=++tot;if(!top[x])top[x]=x;bot[top[x]]=x;
	if(son[x])top[son[x]]=top[x],dfs2(son[x]);
	for(auto y:v[x])if(y!=fa[x]&&y!=son[x])dfs2(y),modify(x,DP(y),1);
	if(son[x])lit[x]=retrieve(x);
	reendow(x);
}
void reset(int x,int y){
	static int f[N];
	for(int i=x;fa[top[i]];i=fa[top[i]])f[i]=DP(top[i]);
	lit[x]=(y?n+1:-n-1);
	while(fa[top[x]])
		reendow(fa[x]),
		modify(fa[top[x]],f[x],-1),modify(fa[top[x]],DP(top[x]),1),
		lit[fa[top[x]]]=retrieve(fa[top[x]]),
		x=fa[top[x]];
	if(fa[x])reendow(fa[x]);
}
int main(){
	scanf("%d%d",&n,&K);
	for(int i=1,x,y;i<n;i++)scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	for(int i=1,x;i<=n;i++)scanf("%d",&x),lit[i]=(x==1?n+1:(x==0?-n-1:0));
	dfs1(1),dfs2(1),scanf("%d",&q);
//	for(int i=1;i<=n;i++)printf("%d ",top[i]);puts("");
//	for(int i=1;i<=n;i++)printf("%d ",lit[i]);puts("");
//	for(int i=1;i<=n;i++)printf("%d ",DP(i));puts("");
	for(int i=1,tp,x,y;i<=q;i++){
		scanf("%d%d",&tp,&x);
		if(tp==1)printf("%d\n",K<DP(x));
		if(tp==2)scanf("%d",&y),reset(x,y);
		if(tp==3)K=x;
	}
//	for(int i=1;i<=n;i++)printf("%d ",DP(i));puts("");
	return 0;
} 

V.CF1286D LCC

Observation 1.碰撞若发生,其仅可能发生在两个相邻的粒子间。

这是显然的。

进而,所有可能的碰撞仅有 O(n) 种:其在相邻的两个粒子各自取一个方向时被确定。

考虑将所有碰撞按照发生时间排序,并钦定某个碰撞发生,考虑此时的概率。则,所有比它小的碰撞都不能发生,也即其有一些限制,限制 dirxadirx+1b 二者至少有一个成立。把转移写成矩阵的形式就发现它是经典可爱可爱 DDP 模型,故直接用线段树维护矩乘即可。

复杂度 nlogn

代码:

#include<bits/stdc++.h>
using namespace std;
const int mod=998244353;
int ksm(int x,int y=mod-2){int z=1;for(;y;y>>=1,x=1ll*x*x%mod)if(y&1)z=1ll*z*x%mod;return z;}
const int inv100=ksm(100);
int n,pos[100100],vel[100100],pro[100100];
struct frac{
	int x,y,p,a,b;
	frac(int X,int Y,int P,int A,int B){x=X,y=Y,p=P,a=A,b=B;} 
	friend bool operator<(const frac&u,const frac&v){return 1ll*u.x*v.y<1ll*u.y*v.x;}
	int operator()()const{return 1ll*x*ksm(y)%mod;}
};
vector<frac>v;
struct Matrix{
	int a[2][2];
	Matrix(){a[0][0]=a[1][1]=1,a[0][1]=a[1][0]=0;}
	int*operator[](const int&x){return a[x];}
	friend Matrix operator*(Matrix u,Matrix v){
		Matrix w;
		for(int i=0;i<2;i++)for(int j=0;j<2;j++)
			w[i][j]=(1ll*u[i][0]*v[0][j]+1ll*u[i][1]*v[1][j])%mod;
		return w;
	}
};
struct SegTree{
#define lson x<<1
#define rson x<<1|1
#define mid ((l+r)>>1)
Matrix seg[400100];	
void buildl(int x,int l,int r){
	if(l==r)seg[x][0][0]=seg[x][1][0]=pro[l],seg[x][0][1]=seg[x][1][1]=(1+mod-pro[l])%mod;
	else buildl(lson,l,mid),buildl(rson,mid+1,r),seg[x]=seg[lson]*seg[rson];
}
void buildr(int x,int l,int r){
	if(l==r)seg[x][0][0]=seg[x][0][1]=pro[r],seg[x][1][0]=seg[x][1][1]=(1+mod-pro[r])%mod;
	else buildr(lson,l,mid),buildr(rson,mid+1,r),seg[x]=seg[lson]*seg[rson];
}
Matrix query(int x,int l,int r,int L,int R){
	if(l>R||r<L)return Matrix();
	if(L<=l&&r<=R)return seg[x];
	return query(lson,l,mid,L,R)*query(rson,mid+1,r,L,R); 
}
void modify(int x,int l,int r,int P,int a,int b){
	if(l==r){seg[x][a][b]=0;return;}
	P<=mid?modify(lson,l,mid,P,a,b):modify(rson,mid+1,r,P,a,b),seg[x]=seg[lson]*seg[rson]; 
}
}s1,s2;
int res;
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d%d%d",&pos[i],&vel[i],&pro[i]),pro[i]=1ll*pro[i]*inv100%mod;
	s1.buildl(1,1,n),s2.buildr(1,1,n);
	for(int i=1;i<n;i++){
		v.emplace_back(pos[i+1]-pos[i],vel[i]+vel[i+1],i,0,1);
		if(vel[i]>vel[i+1])v.emplace_back(pos[i+1]-pos[i],vel[i]-vel[i+1],i,0,0);
		if(vel[i]<vel[i+1])v.emplace_back(pos[i+1]-pos[i],vel[i+1]-vel[i],i,1,1);
	}
	sort(v.begin(),v.end());
	for(auto i:v){
//		printf("(%d/%d):%d[%d,%d]\n",i.x,i.y,i.p,i.a,i.b);
		int pr=1;
		pr=1ll*pr*s1.query(1,1,n,1,i.p)[0][i.a]%mod;
		pr=1ll*pr*s2.query(1,1,n,i.p+1,n)[i.b][1]%mod;
//		printf("%d\n",pr);
		pr=1ll*pr*i()%mod;
		(res+=pr)%=mod;
		s1.modify(1,1,n,i.p+1,i.a,i.b);
		s2.modify(1,1,n,i.p,i.a,i.b);
	}
	printf("%d\n",res);
	return 0;
}

VI.[ZJOI2022] 深搜

题解

VII.CF1740H MEX Tree Manipulation

一个 0 点,子树中需要 1 个点;1 点需要 0 儿子,子树中至少两个点;2 点需要 0,1 各一个,子树中至少 4 个点;故假如一个点的答案是 x,其子树大小至少为 2x

那么我们就可以知道值域是对数级别的。

修改是牵一发动全身的。因此我们考虑 DDP,用矩阵维护重儿子取每个值时,转移的目标以及效果,矩阵大小是 log×log……

这几个 log 啊?过不去,怎么想都不可能过去好吧。

注意到重儿子事实上只有两个本质不同的取值:取到 x,结果变成 y;取到其它东西,结果变成 z,是易处理的。

于是矩阵就只需 2×2 了。

#include<bits/stdc++.h>
using namespace std;
const int N=300100;
int n,fa[N],son[N],rev[N],dfn[N],dep[N],sz[N],top[N],bot[N],tot;
vector<int>v[N];
void dfs_init(int x){
	sz[x]=1,dep[x]=dep[fa[x]]+1;
	for(auto y:v[x]){
		dfs_init(y),sz[x]+=sz[y];
		if(sz[y]>sz[son[x]])son[x]=y;
	}
}
void dfs_part(int x){
	dfn[x]=++tot,rev[tot]=x;
	if(!top[x])top[x]=x;bot[top[x]]=x;
	if(son[x])top[son[x]]=top[x],dfs_part(son[x]);
	for(auto y:v[x])if(y!=son[x])dfs_part(y);
}
struct Trans{
	int key;
	int oup[2],sum[2];//neq/eq.
	Trans(){}
	Trans(int x,int y,int z){//if x then y, otherwise z.
		key=x;
		oup[0]=sum[0]=z;
		oup[1]=sum[1]=y;
	}
	friend Trans operator*(const Trans&u,const Trans&v){
		Trans w;
		w.key=u.key;
		for(int i=0;i<2;i++)
			w.oup[i]=v.oup[u.oup[i]==v.key],w.sum[i]=u.sum[i]+v.sum[u.oup[i]==v.key];
		return w;
	}
	void print()const{printf("<%d>[%d,%d][%d,%d]",key,oup[0],sum[0],oup[1],sum[1]);}
};
#define lson seg[x].ch[0]
#define rson seg[x].ch[1]
struct SegTree{
	int ch[2],l,r;
	Trans tr;
}seg[N<<2];
int rt[N],cnt;
void build(int&x,int l,int r){
	x=++cnt,seg[x].l=l,seg[x].r=r;
	if(l==r)return;
	int L=l+1,R=r,B=(r==dfn[bot[top[rev[r]]]]?0:sz[rev[r+1]]);
	while(L<R){
		int mid=(L+R+1)>>1;
		if(sz[rev[l]]-sz[rev[mid]]<sz[rev[mid]]-B)L=mid;else R=mid-1;
	}
	// printf("[%d,%d]->[%d,%d][%d,%d]\n",l,r,l,R-1,L,r);
	build(lson,l,R-1),build(rson,L,r);
}
void setval(int x,int P,Trans tr){
	// printf("SETVAL:%d,%d(%d)|",x,P,rev[P]),tr.print();puts("");
	if(seg[x].l==seg[x].r)seg[x].tr=tr;
	else P<=seg[lson].r?setval(lson,P,tr):setval(rson,P,tr),seg[x].tr=seg[rson].tr*seg[lson].tr;
}
Trans query(int x,int P){
	if(seg[x].r<=P)return seg[x].tr;
	if(P<=seg[lson].r)return query(lson,P);
	return query(rson,P)*query(lson,P);
}
int num[N][20];
int res;
Trans genetrans(int x){
	int i,j;
	for(i=0;num[x][i];i++);
	for(j=i+1;num[x][j];j++);
	return Trans(i,j,i);
}
void turnon(int x){
	for(int i=fa[x];i;i=fa[top[i]]){
		Trans tr=query(rt[top[i]],dfn[bot[top[i]]]);
		res-=tr.sum[0],num[fa[top[i]]][tr.oup[0]]--;
	}
	setval(rt[top[x]],dfn[x],Trans(0,1,0)),bot[top[x]]=x;
	for(int i=x;i;i=fa[top[i]]){
		Trans tr=query(rt[top[i]],dfn[bot[top[i]]]);
		res+=tr.sum[0],num[fa[top[i]]][tr.oup[0]]++;
		if(!fa[top[i]])break;
		setval(rt[top[fa[top[i]]]],dfn[fa[top[i]]],genetrans(fa[top[i]]));
	}
}
int main(){
	scanf("%d",&n),n++;
	for(int i=2;i<=n;i++)scanf("%d",&fa[i]),v[fa[i]].push_back(i);
	dfs_init(1),dfs_part(1);
	// for(int i=1;i<=n;i++)printf("%d ",top[i]);puts("");
	for(int i=1;i<=n;i++)if(top[i]==i)build(rt[i],dfn[i],dfn[bot[i]]),bot[i]=i;
	setval(rt[1],dfn[1],Trans(0,1,0));
	for(int i=2;i<=n;i++)turnon(i),printf("%d\n",res);
	return 0;
}
posted @   Troverld  阅读(200)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?
点击右上角即可分享
微信分享提示