数据结构优化 DP 学习笔记
数据结构优化 DP 的题。
I.数据结构维护 DP 态
在前一个 DP 态与后一个 DP 态变化不大时,可以使用数据结构维护。
事实上是整体 DP 在链上的特殊形式。
I.CF115E Linear Kingdom Races
思路1.
设表示:
当前DP到第位,且最右边的一个没有修的路是第条路,的最大收益。
则有
这是在号路不修的情况。
对于其它的情况,有,其中表示修路的代价,且有。
然后考虑举办的比赛。
对于一场比赛,所有的都能获得的收益。比赛可以直接在右端点处开vector
储存。
这样时空复杂度都是的。
代码:
#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.
先把空间复杂度解决掉。
发现时刻的数组与时刻的数组区别只有这些:
-
的变动。
-
的。
-
比赛的收益。
那么我们完全可以自始至终只用一个数组。
代码:
#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),区间求(1),区间加/减(2,3)。
而这些都是线段树的常规操作。
于是大力往上一套完事。复杂度
代码:
#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
设表示长度为的LIS结尾的最小值。为了方便,设表示前一个物品的(即滚动数组);
则对于一个的物品:
-
对于的位置,有。
-
对于的位置,有。
-
对于的位置,转移不了,因此直接有。
考虑用平衡树实现。
对于转移1,因为是递增的所以这个转移只对第一个的位置生效。效果相当于直接插入一个数字。
对于转移2,因为我们已经插入过一个了,因此实际上已经全体右移一位了,所以直接打一个即可。
对于转移3,直接删去第一个的即可。
操作采取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
首先,考虑暴力 DP——设 表示当前DP到第 个人,且第一条楼梯上到的最晚的人在时刻 到达,第二条楼梯在时刻 。
然后,观察到 中至少有一个值为前缀 的时刻值。故状态可以被压缩到二维—— 表示第一条楼梯上的人是前缀 ,第二条楼梯在时刻 。
设前缀 为 。然后有
考虑 ,其中肯定应该是 越小,值越大,即 应是单调递减的。假如在某个地方单调性被破坏,那肯定是在后方的东西更劣,可以直接舍去。
于是我们考虑全程只使用一个 数组,随着 增加改变里面的东西。设 表示老的 数组,然后,假设我们已经保证了 的单调性,则转移式可以直接变为:
然后, 可以在最后统一减去,优化得
于是我们现在要解决两个问题,一是区间加定值 ,这个简单用个什么线段树平衡树之类轻松解决;关键是第二个问题,后缀全部增加 ,同时还要维护单调性。
我们对于每个位置 ,设小于它的最大元素是 ,维护一个值为 。当后缀带权加时,明显这个值会减少 。而当这个值最终减少到 时,就说明单调性被破坏,可以删掉该元素了。
在前缀加 时,上述值并没有被改变;在修改 的值时,只有 附近的东西被改变了,暴力修改即可;在后缀带权加时,除了起始的地方,其他地方的值是全部减一的,于是仍然暴力修改起始处的值即可。
可以使用线段树,但这样就没法很方便地维护单调性;无论是分块还是平衡树维护都可以起到简单维护单调性的功效,直接上即可。
时间复杂度 或 ,取决于使用哪种数据结构。这里采取平衡树。
代码:
/*
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
设 表示位置 填 的方案数。则 。于是我们便考虑线段树优化,只需要实现四种操作:整体求和,整体翻转,整体加,后缀赋零。可以直接用带乘法和加法 tag
的线段树轻松解决。
时间复杂度 。
代码:
#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吧。在每个节点上维护,表示在以该节点为根的子树上,阶段到阶段的最大收益。
直接在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:
对于,可以从转移过来。
现在,我们设分别表示位置所有变化中的最大值以及最小值,
则对于,可以从转移过来。
直接暴力转移,复杂度,期望得分。
代码:
#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;
}
然后我们发现,如果把看成一对坐标的话,它又转变成矩形内部了。
然后无脑树套树维护一下即可。
我吹爆树状数组套权值线段树!!!好写到爆!!!
#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
首先先讲一种暴力但能过的方法。
很容易就会往每个值各被计算几次的方向去想。于是我们枚举每个节点,计算有多少种可能下该节点是目标节点。
为了避免相同的值的影响,我们在值相同的点间也决出一种顺序,即,若两个值相同的点在作比较,依照上文定下的那种顺序决定。
于是我们考虑从该枚举的点 出发,遍历整棵子树同时DP。设 表示 子树中有 个点的危险程度 。于是就直接背包转移就行了。
看上去复杂度是 ,但是加上下述两个优化就可以过了:
-
第二维最大只枚举到 (这里的 即题面中的 ,因为 这个字母我们接下来还要用)
-
第二维最大只枚举到子树大小 。
然后就过了,跑的还比正解都要快。
代码:
#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;
}
然后是正解。
我们要求 。
考虑枚举该 的值为 ,则要求 。
考虑让每个 拆分成在所有的 的位置上各被计算一次,则要求 。(这同时也是期望DP的经典套路)
考虑令 表示 的数的总数。则要求 。(注意这里的 即为 )
考虑对于每个连通块,在树上最高处计算它的贡献。设 表示以 为根的子树内,当前统计的是 ,且 的方案数。转移是裸的背包卷积。
考虑如何求答案。因为我们是在最高处计算贡献,所以就要求 。
因为我们是卷积,所以考虑FFT转移。又因为它一直在卷,所以我们干脆考虑压根不把它复原成系数式,就纯粹用点集表示。
更准确地说,因为 的生成函数是 ,一个 次多项式,所以我们直接枚举 ,然后分别求出这时的生成函数的值,最后拉格朗日插值一下就插回系数式了。
则,在合并父亲 和儿子 的 数组时,因为是点集式,所以直接对应位置相乘就行了。
但是就算有了这么伟大的思路,我们算算复杂度,还是 的。有没有更棒的复杂度?
我们发现,最终要对所有 求 数组的和,倒不如在正常处理的过程中就顺便维护了。于是我们设 ,则最终要求的就是 。当然,依据分配律,我们还是可以直接一股脑求出 ,待插出系数式后再取 的项。
我们思考当合并 与 时会发生什么:
(采用 或不用)
在全体结束后,再有 。
同时,为了便于合并,我们采用线段树合并来维护DP数组。(这种操作被称作整体DP)
我们考虑初始化,发现假如我们最外层枚举的点值是 ,则所有 , 在结束时都要乘上一个 。
明显这个初始状态大体是区间的,非常适合线段树合并。
但是,就算这样,暴力合并的复杂度仍然 ,必须考虑在区间上把它做掉。
于是,一个天才般的想法诞生了:
观察到每次的变换都是 的形式。
而这个变换,可以用四元组 唯一刻画。
同时,展开式子,就会发现这个变换具有结合律(虽然很遗憾的是,大部分情形下它不具有交换律)。
假如我们初始令 的话,就会发现,做一次上述操作,它就自动帮你更新了 与 !
于是,我们把它看作区间的 tag
,然后线段树合并就非常简单了。
同时,要注意的是,其单位元是 。
我们来总结一下操作:当合并的时候,我们希望 ,而 可以通过在遍历完 的子树后打上全体 的 tag
解决,当然这里不需要额外增加其它的 tag
,我们发现 刚好胜任了这个操作。于是现在 , 的 tag
即可(需要注意的是, 是线段树上 处的 )。, 即可,而 则是 。两个乘起来,就是使用 。
最后合并 与 的时候,则要使用 ,意义通过展开即可得到就是将 加到 。而乘上 的操作,使用 即可。
需要注意的是,这里并不能标记永久化,主要是因为从四元组中抽出 和 的操作并非线性变换,不能打到 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"&动态树分治
裸的树上最大独立集怎么做?设 表示在 子树中, 不选的最大答案; 表示在 子树中, 选的最大答案。则有
现在要支持修改,怎么办?
发现,每次修改,DP值受到影响的位置是从修改位置到根的一条路径。听上去像是树剖应用的场景?
于是我们考虑树剖,并设 表示忽略 的重儿子时的答案。则,,其中 是 的重儿子。
现在,我们考虑将其转成矩阵的形式。则有
其中,矩阵乘法的定义是 。手推可得其满足结合律。
于是,我们考虑从一个叶子的 数组开始(令人惊喜的是,叶子节点的 与 是相等的(因为其不存在儿子),这使得转移很方便),一路往上乘矩阵,即可得到该叶子所在重链上任意节点的 数组。并且,因为任意重链的结尾节点都是叶子,所以通过上述方法可以求出任意节点的 数组。
现在考虑带修。发现,一次修改只会影响到根的路径上所有重链顶的父亲的 数组,而这样的父亲最多只有 个,故直接暴力跳链修改父亲即可。
时间复杂度 。
代码:
#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]切树游戏
设 表示 子树中,所有包含 且异或和为 的连通块数量, 表示 子树中异或和为 的连通块数量。显然,有公式
现在考虑 的转移。假如我们要合并 与某个儿子 的DP数组,则显然有公式
其中 是老的 数组。
发现这是异或卷积形式,可以FWT优化。
但是我们发现,因为DP过程中全程要么在卷积,要么在做加法,对FWT后的数组作也是一样的。因此我们重新修改定义,将 数组全数修改为FWT后的数组。又因为 这个函数在FWT后会得到 ,因此转移式变更为
直接硬DP,单次复杂度 。
现在考虑轻重链剖分。设 表示仅考虑轻儿子时的答案。若只考虑 的一个重儿子 ,则有
当然,这里的所有东西都是函数,连上面的 都不例外,指的是元素全为 的函数。
同时,要留心一点常规 的转移:
需要注意的是,叶节点的 数组应直接是其 数组,故对于叶子节点,有 。
我们可以列出转移矩阵来:
矩阵就有着 的常数,不好玩。但是,当我们计算两个上述矩阵的积后
发现矩阵的积不会影响其余位置的取值。
因此我们考虑只维护 四个位置的值,即可压缩矩阵为 。
于是现在的转移矩阵就压缩为
同时,其存在单位元
我们同时还要支持从链顶父亲的 数组中删掉一个 数组的操作。这样就需要支持除法。但是你不能保证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
一个观察是,对于叶子固定的某局面,随着 的增大,非叶子节点会由蓝翻红。因而可以设 表示 节点翻红的时刻:蓝叶子为 ,红叶子为 ,并用 DDP 维护该式子。
现在考虑如何转移。 可以将所有儿子的 值排序后二分得到。故考虑维护一个仅考虑所有轻儿子时的 值,然后考虑进重儿子后会发现 值的 。于是设 表示子节点的 确定时,父节点的 。转移就用两个 类倍增地拼接即可。使用线段树维护,复杂度对数平方。
代码中脑抽写了个 的做法,但事实上用 就行了。
代码:
#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.碰撞若发生,其仅可能发生在两个相邻的粒子间。
这是显然的。
进而,所有可能的碰撞仅有 种:其在相邻的两个粒子各自取一个方向时被确定。
考虑将所有碰撞按照发生时间排序,并钦定某个碰撞发生,考虑此时的概率。则,所有比它小的碰撞都不能发生,也即其有一些限制,限制 或 二者至少有一个成立。把转移写成矩阵的形式就发现它是经典可爱可爱 DDP 模型,故直接用线段树维护矩乘即可。
复杂度 。
代码:
#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
一个 点,子树中需要 个点; 点需要 儿子,子树中至少两个点; 点需要 各一个,子树中至少 个点;故假如一个点的答案是 ,其子树大小至少为 。
那么我们就可以知道值域是对数级别的。
修改是牵一发动全身的。因此我们考虑 DDP,用矩阵维护重儿子取每个值时,转移的目标以及效果,矩阵大小是 ……
这几个 啊?过不去,怎么想都不可能过去好吧。
注意到重儿子事实上只有两个本质不同的取值:取到 ,结果变成 ;取到其它东西,结果变成 ,是易处理的。
于是矩阵就只需 了。
#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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?