1.数据结构
数据结构1
开题顺序: \(BXGEWDJVNCFQKPLAHR\)
\(A\) luogu P5494 【模板】线段树分裂
\(B\) luogu P1637 三元上升子序列
\(C\) luogu P6492 [COCI2010-2011#6] STEP
-
将
L
视作 \(0\) ,R
视作 \(1\) 。 -
则等价于查询最长 \(01\) 串长度,线段树维护前后缀信息即可,写法跟维护最大子段和差不多。
点击查看代码
int a[200010]; struct SMT { struct SegmentTree { int l,r,pre,suf,ans; }tree[800010]; int lson(int x) { return x*2; } int rson(int x) { return x*2+1; } void pushup(int rt) { tree[rt].ans=max(tree[lson(rt)].ans,tree[rson(rt)].ans); tree[rt].pre=tree[lson(rt)].pre; tree[rt].suf=tree[rson(rt)].suf; if(a[tree[lson(rt)].r]!=a[tree[rson(rt)].l]) { tree[rt].ans=max(tree[rt].ans,tree[lson(rt)].suf+tree[rson(rt)].pre); tree[rt].pre+=(tree[lson(rt)].pre==tree[lson(rt)].r-tree[lson(rt)].l+1)*tree[rson(rt)].pre; tree[rt].suf+=(tree[rson(rt)].suf==tree[rson(rt)].r-tree[rson(rt)].l+1)*tree[lson(rt)].suf; } } void build(int rt,int l,int r) { tree[rt].l=l; tree[rt].r=r; if(l==r) { tree[rt].pre=tree[rt].suf=tree[rt].ans=1; return; } int mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); pushup(rt); } void update(int rt,int pos) { if(tree[rt].l==tree[rt].r) { a[tree[rt].l]^=1; return; } int mid=(tree[rt].l+tree[rt].r)/2; if(pos<=mid) { update(lson(rt),pos); } else { update(rson(rt),pos); } pushup(rt); } SegmentTree query(int rt,int x,int y) { if(x<=tree[rt].l&&tree[rt].r<=y) { return tree[rt]; } int mid=(tree[rt].l+tree[rt].r)/2; SegmentTree p,q,ans; if(y<=mid) { return query(lson(rt),x,y); } else { if(x>mid) { return query(rson(rt),x,y); } else { p=query(lson(rt),x,y); q=query(rson(rt),x,y); ans.ans=max(p.ans,q.ans); ans.pre=p.pre; ans.suf=q.suf; if(a[p.r]!=a[q.l]) { ans.ans=max(ans.ans,p.suf+q.pre); ans.pre+=(p.pre==p.r-p.l+1)*q.pre; ans.suf+=(q.suf==q.r-q.l+1)*p.suf; } return ans; } } } }T; int main() { int n,m,x,i; cin>>n>>m; T.build(1,1,n); for(i=1;i<=m;i++) { cin>>x; T.update(1,x); cout<<T.query(1,1,n).ans<<endl; } return 0; }
\(D\) luogu P6136 【模板】普通平衡树(数据加强版)
\(E\) luogu P3038 [USACO11DEC] Grass Planting G
\(F\) luogu P7735 [NOI2021] 轻重边
-
维护路径信息,考虑树剖。
-
边权直接维护不太好维护,考虑转成点权。而直接将边权信息赋给深度较深的儿子节点在本题中难以适应。
-
考虑给每一个点赋一个权值 \(val\) 使得对于任意一条边 \(u \to v\) 若 \(val_{u}=val_{v}\) 则 \(u \to v\) 是重边,否则是轻边。
-
此时操作 \(1\) 等价于将 \(u \to v\) 上的点的权值都赋成一个统一的值;操作 \(2\) 等价于查询 \(u \to v\) 上相邻两点权值相同的无序点对,做法同 luogu P2486 [SDOI2011] 染色 差不多,线段树维护即可。
点击查看代码
struct node { int nxt,to; }e[200010]; int head[200010],c[200010],cc[200010],fa[200010],dep[200010],siz[200010],son[200010],dfn[200010],top[200010],cnt=0,tot=0; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } struct SMT { struct SegmentTree { int l,r,lazy,lc,rc,ans; }tree[800010]; int lson(int x) { return x*2; } int rson(int x) { return x*2+1; } void pushup(int rt) { tree[rt].ans=tree[lson(rt)].ans+tree[rson(rt)].ans+(tree[lson(rt)].rc==tree[rson(rt)].lc); tree[rt].lc=tree[lson(rt)].lc; tree[rt].rc=tree[rson(rt)].rc; } void build(int rt,int l,int r) { tree[rt].l=l; tree[rt].r=r; tree[rt].lazy=0; if(l==r) { tree[rt].lc=tree[rt].rc=cc[l]; tree[rt].ans=0; return; } int mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); pushup(rt); } void pushdown(int rt) { if(tree[rt].lazy!=0) { tree[lson(rt)].lc=tree[lson(rt)].rc=tree[rt].lazy; tree[rson(rt)].lc=tree[rson(rt)].rc=tree[rt].lazy; tree[lson(rt)].ans=tree[lson(rt)].r-tree[lson(rt)].l+1-1; tree[rson(rt)].ans=tree[rson(rt)].r-tree[rson(rt)].l+1-1; tree[lson(rt)].lazy=tree[rson(rt)].lazy=tree[rt].lazy; tree[rt].lazy=0; } } void update(int rt,int x,int y,int val) { if(x<=tree[rt].l&&tree[rt].r<=y) { tree[rt].lc=tree[rt].rc=tree[rt].lazy=val; tree[rt].ans=tree[rt].r-tree[rt].l+1-1; return; } pushdown(rt); int mid=(tree[rt].l+tree[rt].r)/2; if(x<=mid) { update(lson(rt),x,y,val); } if(y>mid) { update(rson(rt),x,y,val); } pushup(rt); } SegmentTree query(int rt,int x,int y) { if(x<=tree[rt].l&&tree[rt].r<=y) { return tree[rt]; } pushdown(rt); int mid=(tree[rt].l+tree[rt].r)/2; SegmentTree p,q,ans; if(y<=mid) { return query(lson(rt),x,y); } else { if(x>mid) { return query(rson(rt),x,y); } else { p=query(lson(rt),x,y); q=query(rson(rt),x,y); ans.ans=p.ans+q.ans+(p.rc==q.lc); ans.lc=p.lc; ans.rc=q.rc; return ans; } } } }T; void dfs1(int x,int father) { siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs1(e[i].to,x); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } } void dfs2(int x,int id) { top[x]=id; tot++; dfn[x]=tot; cc[tot]=c[x]; if(son[x]!=0) { dfs2(son[x],id); for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]&&e[i].to!=son[x]) { dfs2(e[i].to,e[i].to); } } } } void update1(int u,int v,int val) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { T.update(1,dfn[top[u]],dfn[u],val); u=fa[top[u]]; } else { T.update(1,dfn[top[v]],dfn[v],val); v=fa[top[v]]; } } if(dep[u]<dep[v]) { T.update(1,dfn[u],dfn[v],val); } else { T.update(1,dfn[v],dfn[u],val); } } int query1(int u,int v) { int ans=0; while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { ans+=T.query(1,dfn[top[u]],dfn[u]).ans+(T.query(1,dfn[top[u]],dfn[top[u]]).lc==T.query(1,dfn[fa[top[u]]],dfn[fa[top[u]]]).lc); u=fa[top[u]]; } else { ans+=T.query(1,dfn[top[v]],dfn[v]).ans+(T.query(1,dfn[top[v]],dfn[top[v]]).lc==T.query(1,dfn[fa[top[v]]],dfn[fa[top[v]]]).lc); v=fa[top[v]]; } } if(dep[u]<dep[v]) { ans+=T.query(1,dfn[u],dfn[v]).ans; } else { ans+=T.query(1,dfn[v],dfn[u]).ans; } return ans; } int main() { int t,n,m,pd,u,v,i,j; scanf("%d",&t); for(j=1;j<=t;j++) { cnt=tot=0; memset(e,0,sizeof(e)); memset(head,0,sizeof(head)); memset(son,0,sizeof(son)); scanf("%d%d",&n,&m); for(i=1;i<=n;i++) { c[i]=i; } for(i=1;i<=n-1;i++) { scanf("%d%d",&u,&v); add(u,v); add(v,u); } dfs1(1,0); dfs2(1,1); T.build(1,1,n); for(i=1;i<=m;i++) { scanf("%d%d%d",&pd,&u,&v); if(pd==1) { update1(u,v,n+i); } else { printf("%d\n",query1(u,v)); } } } return 0; }
\(G\) luogu P3605 [USACO17JAN] Promotion Counting P
\(H\) luogu P7394 「TOCO Round 1」History
-
与 \(x\) 深度相等且节点距离为 \(y\) 的节点一定是 \(x\) 的 \(\frac{y}{2}\) 级祖先的 \(\frac{y}{2}\) 的子孙。
- 同时也解释了当 \(y\) 为奇数时答案为 \(0\) 。
-
考虑对于每个深度开一棵动态开点线段树,查询在 \(x\) 的 \(\frac{y}{2}\) 级祖先的子树且不在 \(x\) 的 \(\frac{y}{2}-1\) 级祖先的子树中的开着灯的节点数量, \(DFS\) 序维护即可。
- 因为直接查询 \(x\) 的 \(\frac{y}{2}\) 级祖先的子树中深度和 \(x\) 的深度相等的情况下,会将 \(x\) 的 \(\frac{y}{2}-1\) 级祖先的 \(\frac{y}{2}-1\) 级子孙也统计到,需要减去这部分贡献。
-
对于回溯操作,操作树或主席树维护即可。其中后者的空间复杂度多带一个 \(2\) 倍常数。
点击查看代码
struct node { int nxt,to; }e[200010]; struct ask { int pd,x,y,id; }q[200010]; int head[200010],a[200010],dep[200010],dfn[200010],out[200010],fa[200010][20],ans[200010],cnt=0,tot=0,N; vector<int>E[200010]; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs1(int x,int father) { tot++; dfn[x]=tot; fa[x][0]=father; dep[x]=dep[father]+1; for(int i=1;(1<<i)<=dep[x];i++) { fa[x][i]=fa[fa[x][i-1]][i-1]; } for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs1(e[i].to,x); } } out[x]=tot; } int kth_fa(int x,int k) { int rt=x; for(int i=N;i>=0;i--) { if(dep[x]-dep[fa[rt][i]]<=k) { rt=fa[rt][i]; } } return rt; } struct SMT { int root[200010],rt_sum; struct SegmentTree { int ls,rs,sum; }tree[200010<<5]; #define lson(rt) (tree[rt].ls) #define rson(rt) (tree[rt].rs) int build_rt() { rt_sum++; lson(rt_sum)=rson(rt_sum)=tree[rt_sum].sum=0; return rt_sum; } void pushup(int rt) { tree[rt].sum=tree[lson(rt)].sum+tree[rson(rt)].sum; } void update(int &rt,int l,int r,int pos,int val) { rt=(rt==0)?build_rt():rt; if(l==r) { tree[rt].sum=val; return; } int mid=(l+r)/2; if(pos<=mid) { update(lson(rt),l,mid,pos,val); } else { update(rson(rt),mid+1,r,pos,val); } pushup(rt); } int query(int rt,int l,int r,int x,int y) { if(rt==0) { return 0; } if(x<=l&&r<=y) { return tree[rt].sum; } int mid=(l+r)/2,ans=0; if(x<=mid) { ans+=query(lson(rt),l,mid,x,y); } if(y>mid) { ans+=query(rson(rt),mid+1,r,x,y); } return ans; } }T; void dfs2(int x,int n) { if(q[x].pd==1) { a[q[x].x]^=1; T.update(T.root[dep[q[x].x]],1,n,dfn[q[x].x],a[q[x].x]); } if(q[x].pd==2) { if(q[x].y%2==0) { if(q[x].y==0) { ans[q[x].id]=a[q[x].x]; } else { int rt1=kth_fa(q[x].x,q[x].y/2),rt2=kth_fa(q[x].x,q[x].y/2-1); ans[q[x].id]=T.query(T.root[dep[q[x].x]],1,n,dfn[rt1],out[rt1])-T.query(T.root[dep[q[x].x]],1,n,dfn[rt2],out[rt2]); } } } for(int i=0;i<E[x].size();i++) { dfs2(E[x][i],n); } if(q[x].pd==1) { a[q[x].x]^=1; T.update(T.root[dep[q[x].x]],1,n,dfn[q[x].x],a[q[x].x]); } } int main() { int n,m,u,v,i; cin>>n; N=log2(n)+1; for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } cin>>m; for(i=1;i<=m;i++) { cin>>q[i].pd>>q[i].x; q[i].id=i; if(q[i].pd==2) { cin>>q[i].y; } if(q[i].pd<=2) { E[i-1].push_back(i); } else { E[q[i].x].push_back(i); } } dfs1(1,0); dfs2(0,n); for(i=1;i<=m;i++) { if(q[i].pd==2) { cout<<ans[i]<<endl; } } return 0; }
\(I\) luogu P7671 [GDOI2016] 疯狂动物城
\(J\) CF915E Physical Education Lessons
\(K\) CF19D Points
-
离散化后线段树套
set
即可。 -
叶子节点只保留 \(y\) 坐标的最大值然后进行线段树上二分。
点击查看代码
int x[200010],y[200010],xx[200010],yy[200010]; char pd[200010][8]; set<int>s[200010]; struct SMT { struct SegmentTree { int l,r,maxx; }tree[800010]; int lson(int x) { return x*2; } int rson(int x) { return x*2+1; } void pushup(int rt) { tree[rt].maxx=max(tree[lson(rt)].maxx,tree[rson(rt)].maxx); } void build(int rt,int l,int r) { tree[rt].l=l; tree[rt].r=r; if(l==r) { s[l].insert(-0x7f7f7f7f); tree[rt].maxx=-0x7f7f7f7f; return; } int mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); pushup(rt); } void update(int rt,int pos,int val) { if(tree[rt].l==tree[rt].r) { tree[rt].maxx=val; return; } int mid=(tree[rt].l+tree[rt].r)/2; if(pos<=mid) { update(lson(rt),pos,val); } else { update(rson(rt),pos,val); } pushup(rt); } int query(int rt,int x,int y,int val) { if(tree[rt].maxx<val) { return -1; } if(tree[rt].l==tree[rt].r) { return tree[rt].l; } int mid=(tree[rt].l+tree[rt].r)/2,p,q; if(y<=mid) { return query(lson(rt),x,y,val); } else { if(x>mid) { return query(rson(rt),x,y,val); } else { p=query(lson(rt),x,y,val); if(p!=-1) { return p; } q=query(rson(rt),x,y,val); if(q!=-1) { return q; } return -1; } } } }T; int main() { int n,ans,i; scanf("%d",&n); for(i=1;i<=n;i++) { scanf("%s%d%d",(pd[i]+1),&x[i],&y[i]); xx[i]=x[i]; yy[i]=y[i]; } sort(xx+1,xx+1+n); sort(yy+1,yy+1+n); xx[0]=unique(xx+1,xx+1+n)-(xx+1); yy[0]=unique(yy+1,yy+1+n)-(yy+1); T.build(1,1,xx[0]); for(i=1;i<=n;i++) { x[i]=lower_bound(xx+1,xx+1+xx[0],x[i])-xx; y[i]=lower_bound(yy+1,yy+1+yy[0],y[i])-yy; if(pd[i][1]=='a') { ans=*--s[x[i]].end(); s[x[i]].insert(y[i]); if(ans!=*--s[x[i]].end()) { T.update(1,x[i],*--s[x[i]].end()); } } if(pd[i][1]=='r') { ans=*--s[x[i]].end(); s[x[i]].erase(s[x[i]].find(y[i])); if(ans!=*--s[x[i]].end()) { T.update(1,x[i],*--s[x[i]].end()); } } if(pd[i][1]=='f') { if(x[i]+1<=xx[0]) { ans=T.query(1,x[i]+1,xx[0],y[i]+1); if(ans==-1) { printf("-1\n"); } else { printf("%d %d\n",xx[ans],yy[*s[ans].upper_bound(y[i])]); } } else { printf("-1\n"); } } } return 0; }
\(L\) CF193D Two Segments
-
翻译过来就是询问 \(1 \sim n\) 中有多少个值域连续段 \([l,r]\) 满足 \(\{ a \}\) 中值在 \([l,r]\) 之间的数所形成的区间个数 \(\le 2\) 。
-
令 \(\forall i \in [1,n],pos_{a_{i}}=i\) 。
-
尝试枚举值域连续段的右端点 \(r\) ,每次统计 \([1,r]\) 中有多少个合法的左端点 \(l \in [1,r]\) 使得 \([l,r]\) 能被分成 \(1\) 段或 \(2\) 段。而这个过程可以统计 \([1,r]\) 至少、次少分成的区间个数(最后判断一下是否 \(\le 2\) )及各自方案数进行统计。
-
具体地,设 \(f_{l,r}\) 表示值在 \([l,r]\) 中的数组成区间的个数, \(S_{l,r}\) 表示值在 \([l,r]\) 中的数的下标组成的集合。
-
考虑右端点由 \(r-1\) 变成 \(r\) 对 \(f\) 的影响。首先不难有 \(\forall l \in [1,r],pos_{r} \in S_{l,r}\) ,故假设 \(r\) 会单独成一段,即 \(f_{l,r}+=1(l \in [1,r])\) ;接着若 \(a_{pos_{r}-1}<r\) 有 \(\forall l \in [1,a_{pos_{r}-1}],pos_{r} \in S_{l,r} \land pos_{r}-1 \in S_{l,r}\) ,故 \(pos_{r}\) 可以和 \(pos_{r}-1\) 拼起来,使区间个数少一,即 \(f_{l,r}-=1(l \in [1,a_{pos_{r}-1}])\) ;最后若 \(a_{pos_{r}+1}<r\) 有 \(\forall l \in [1,a_{pos_{r}+1}],pos_{r} \in S_{l,r} \land pos_{r}+1 \in S_{l,r}\) ,故 \(pos_{r}\) 可以和 \(pos_{r}+1\) 拼起来,使区间个数少一,即 \(f_{l,r}-=1(l \in [1,a_{pos_{r}+1}])\) 。
-
线段树维护区间修改、区间查询即可。
点击查看代码
ll a[300010],pos[300010]; struct SMT { pair<ll,ll>tmp[6]; struct SegmentTree { ll l,r,lazy; pair<ll,ll>zx,cx; }tree[1200010]; ll lson(ll x) { return x*2; } ll rson(ll x) { return x*2+1; } void pushup(ll rt) { tmp[1]=tree[lson(rt)].zx; tmp[2]=tree[lson(rt)].cx; tmp[3]=tree[rson(rt)].zx; tmp[4]=tree[rson(rt)].cx; tmp[5]=make_pair(0x3f3f3f3f,0); sort(tmp+1,tmp+5); tree[rt].zx=tree[rt].cx=make_pair(0,0); ll pos=1; do { tree[rt].zx.first=tmp[pos].first; tree[rt].zx.second+=tmp[pos].second; pos++; }while(pos<=4&&tmp[pos].first==tree[rt].zx.first); do { tree[rt].cx.first=tmp[pos].first; tree[rt].cx.second+=tmp[pos].second; pos++; }while(pos<=4&&tmp[pos].first==tree[rt].cx.first); } void build(ll rt,ll l,ll r) { tree[rt].l=l; tree[rt].r=r; if(l==r) { tree[rt].zx=make_pair(0,1); tree[rt].cx=make_pair(0x3f3f3f3f,0); return; } ll mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); pushup(rt); } void pushdown(ll rt) { if(tree[rt].lazy!=0) { tree[lson(rt)].zx.first+=tree[rt].lazy; tree[lson(rt)].cx.first+=tree[rt].lazy; tree[rson(rt)].zx.first+=tree[rt].lazy; tree[rson(rt)].cx.first+=tree[rt].lazy; tree[lson(rt)].lazy+=tree[rt].lazy; tree[rson(rt)].lazy+=tree[rt].lazy; tree[rt].lazy=0; } } void update(ll rt,ll x,ll y,ll val) { if(x<=tree[rt].l&&tree[rt].r<=y) { tree[rt].zx.first+=val; tree[rt].cx.first+=val; tree[rt].lazy+=val; return; } pushdown(rt); ll mid=(tree[rt].l+tree[rt].r)/2; if(x<=mid) { update(lson(rt),x,y,val); } if(y>mid) { update(rson(rt),x,y,val); } pushup(rt); } ll query(ll rt,ll x,ll y) { if(x<=tree[rt].l&&tree[rt].r<=y) { return tree[rt].zx.second*(tree[rt].zx.first<=2)+tree[rt].cx.second*(tree[rt].cx.first<=2); } pushdown(rt); ll mid=(tree[rt].l+tree[rt].r)/2,ans=0; if(x<=mid)//对子树分治时在此题中不需要再进行 pushup 因为最小值、次小值可以直接合并 { ans+=query(lson(rt),x,y); } if(y>mid) { ans+=query(rson(rt),x,y); } return ans; } }T; int main() { ll n,ans=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; pos[a[i]]=i; } T.build(1,1,n); for(i=1;i<=n;i++) { T.update(1,1,i,1); if(pos[i]-1>=1&&a[pos[i]-1]<i) { T.update(1,1,a[pos[i]-1],-1); } if(pos[i]+1<=n&&a[pos[i]+1]<i) { T.update(1,1,a[pos[i]+1],-1); } if(i-1>=1) { ans+=T.query(1,1,i-1); } } cout<<ans<<endl; return 0; }
\(M\) CF1983F array-value
\(N\) [ABC371F] Takahashi in Narrow Road
\(O\) CF1304F2 Animal Observation (hard version)
\(P\) CF946G Almost Increasing Array
-
若将 \(\{ a \}\) 变成严格递增序列,至少需要更改 \(n\) 减去 \(\{ a_{i}-i \}\) 的最长不下降子序列长度个数。
- 证明
- 对于 \(a_{i},a_{j}(i<j)\) 若都在最终的严格递增序列里,则有 \(a_{i}-a_{j} \le i-j\) ,即 \(a_{i}-i \le a_{j}-j\) 。而这 \(\{ a_{i}-i \}\) 的最长不下降子序列长度个数是不需要更改的。
- 证明
-
考虑计算最多能保留的数的个数。
-
令 \(\forall i \in [1,n],b_{i}=a_{i}-i\) 。
-
设 \(f_{i,0/1}\) 表示以 \(i\) 结尾的前缀中没有/有删除过的数时(删除的这个数仅能 \(\in [1,i-1]\) ,能够在最终保留但不参与运算)最多能保留的数的个数,状态转移方程为 \(\begin{cases} f_{i,0}=\max\limits_{j=1}^{i-1} \{ [b_{j} \le b_{i}] \times (f_{j,0}+1) \} \\ f_{i,1}=\max(\max\limits_{j=1}^{i-1} \{ [b_{j} \le b_{i}] \times (f_{j,1}+1) \},\max\limits_{j=1}^{i-2} \{ [b_{j} \le b_{i}+1] \times (f_{j,0}+1) \}) \end{cases}\) ,边界为 \(\begin{cases} f_{1,0}=1 \\ f_{1,1}=0 \end{cases}\) 。
-
权值树状数组优化 \(DP\) 即可。
-
最终,有 \(\max(n-1-\max\limits_{i=1}^{n} \{ f_{i,0},f_{i,1} \},0)\) 即为所求。
- 使用删除一定比不使用删除不劣。
点击查看代码
ll a[200010],b[200010],c[400010],f[200010][2]; struct BIT { ll c[400010]; ll lowbit(ll x) { return (x&(-x)); } void add(ll n,ll x,ll val) { for(ll i=x;i<=n;i+=lowbit(i)) { c[i]=max(c[i],val); } } ll getsum(ll x) { ll ans=0; for(ll i=x;i>=1;i-=lowbit(i)) { ans=max(ans,c[i]); } return ans; } }T[3]; int main() { ll n,ans=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; b[i]=a[i]-i; c[2*i-1]=b[i]; c[2*i]=b[i]+1; } sort(c+1,c+2*n+1); c[0]=unique(c+1,c+2*n+1)-(c+1); for(i=1;i<=n;i++) { f[i][0]=T[0].getsum(lower_bound(c+1,c+1+c[0],b[i])-c)+1; f[i][1]=T[1].getsum(lower_bound(c+1,c+1+c[0],b[i])-c); f[i][1]+=(f[i][1]!=0); if(i-2>=1) { f[i][1]=max(f[i][1],T[2].getsum(lower_bound(c+1,c+1+c[0],b[i]+1)-c)+1); } T[0].add(c[0],lower_bound(c+1,c+1+c[0],b[i])-c,f[i][0]); T[1].add(c[0],lower_bound(c+1,c+1+c[0],b[i])-c,f[i][1]); if(i-1>=1) { T[2].add(c[0],lower_bound(c+1,c+1+c[0],b[i-1])-c,f[i-1][0]); } } for(i=1;i<=n;i++) { ans=max(ans,max(f[i][0],f[i][1])); } cout<<max(n-1-ans,0ll)<<endl; return 0; }
\(Q\) CF718C Sasha and Array
-
容易有 \(\begin{bmatrix} Fib_{n} & Fib_{n+1} \end{bmatrix}=\begin{bmatrix} Fib_{n-1} & Fib_{n} \end{bmatrix} \times \begin{bmatrix} 0 & 1 \\ 1 & 1 \end{bmatrix}\) 。
-
建树时暴力算斐波那契数,修改时维护 \(\begin{bmatrix} 0 & 1 \\ 1 & 1 \end{bmatrix}\) 的乘方标记,区间和维护矩阵加法即可。
- 本质上应用了矩阵的结合律 \(A \times B+A \times C=A \times (B+C)\) 。
-
为方便重载运算符,将原 \(F\) 矩阵写作 \(\begin{bmatrix} Fib_{n} & Fib_{n+1} \\ 0 & 0 \end{bmatrix}\) 。
点击查看代码
const ll p=1000000007; struct Matrix { ll ma[3][3]; Matrix() { memset(ma,0,sizeof(ma)); } Matrix operator + (const Matrix &another) const { Matrix ans; for(ll i=1;i<=2;i++) { for(ll j=1;j<=2;j++) { ans.ma[i][j]=(ma[i][j]+another.ma[i][j])%p; } } return ans; } Matrix operator * (const Matrix &another) const { Matrix ans; for(ll i=1;i<=2;i++) { for(ll j=1;j<=2;j++) { for(ll h=1;h<=2;h++) { ans.ma[i][j]=(ans.ma[i][j]+ma[i][h]*another.ma[h][j]%p)%p; } } } return ans; } bool operator == (const Matrix &another) const { for(ll i=1;i<=2;i++) { for(ll j=1;j<=2;j++) { if(ma[i][j]!=another.ma[i][j]) { return false; } } } return true; } }base,I; ll a[100010]; Matrix qpow(Matrix a,ll b,ll p) { Matrix ans; for(ll i=1;i<=2;i++) { ans.ma[i][i]=1; } while(b) { if(b&1) { ans=ans*a; } b>>=1; a=a*a; } return ans; } struct SMT { struct SegmentTree { ll l,r; Matrix sum,lazy; }tree[400010]; ll lson(ll x) { return x*2; } ll rson(ll x) { return x*2+1; } void pushup(ll rt) { tree[rt].sum=tree[lson(rt)].sum+tree[rson(rt)].sum; } void build(ll rt,ll l,ll r) { tree[rt].l=l; tree[rt].r=r; tree[rt].lazy=I; if(l==r) { tree[rt].sum.ma[1][1]=tree[rt].sum.ma[2][1]=tree[rt].sum.ma[2][2]=0; tree[rt].sum.ma[1][2]=1; tree[rt].sum=tree[rt].sum*qpow(base,a[l],p); return; } ll mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); pushup(rt); } void pushdown(ll rt) { if(!(tree[rt].lazy==I)) { tree[lson(rt)].sum=tree[lson(rt)].sum*tree[rt].lazy; tree[rson(rt)].sum=tree[rson(rt)].sum*tree[rt].lazy; tree[lson(rt)].lazy=tree[lson(rt)].lazy*tree[rt].lazy; tree[rson(rt)].lazy=tree[rson(rt)].lazy*tree[rt].lazy; tree[rt].lazy=I; } } void update(ll rt,ll x,ll y,Matrix val) { if(x<=tree[rt].l&&tree[rt].r<=y) { tree[rt].sum=tree[rt].sum*val; tree[rt].lazy=tree[rt].lazy*val; return; } pushdown(rt); ll mid=(tree[rt].l+tree[rt].r)/2; if(x<=mid) { update(lson(rt),x,y,val); } if(y>mid) { update(rson(rt),x,y,val); } pushup(rt); } ll query(ll rt,ll x,ll y) { if(x<=tree[rt].l&&tree[rt].r<=y) { return tree[rt].sum.ma[1][1]; } pushdown(rt); int mid=(tree[rt].l+tree[rt].r)/2,ans=0; if(x<=mid) { ans=(ans+query(lson(rt),x,y))%p; } if(y>mid) { ans=(ans+query(rson(rt),x,y))%p; } return ans; } }T; int main() { ll n,m,pd,l,r,x,i; cin>>n>>m; for(i=1;i<=n;i++) { cin>>a[i]; } base.ma[1][1]=0; base.ma[1][2]=base.ma[2][1]=base.ma[2][2]=1; I.ma[1][1]=I.ma[2][2]=1; I.ma[1][2]=I.ma[2][1]=0; T.build(1,1,n); for(i=1;i<=m;i++) { cin>>pd>>l>>r; if(pd==1) { cin>>x; T.update(1,l,r,qpow(base,x,p)); } else { cout<<T.query(1,l,r)<<endl; } } return 0; }
\(R\) CF1956F Nene and the Passing Game
- 等价于求建完图后的连通块个数。
- \((i,j) \in E\) 当且仅当 \(|i-j| \in [l_{i}+l_{j},r_{i}+r_{j}]\) ,即 \(\begin{cases} i+l_{i} \le j-l_{j} \land i+r_{i} \ge j-r_{j} & i<j \\ j+l_{j} \le i-l_{i} \land j+r_{j} \ge i-r_{i} & i>j \end{cases}\) ,即 \(\begin{cases} [i+l_{i},i+r_{i}] \bigcap [j-l_{j},j-r_{j}] \ne \varnothing & i<j \\ [j+l_{j},j+r_{j}] \bigcap [i-l_{i},i-r_{i}] \ne \varnothing & i>j \end{cases}\) 。
- 记 \(\forall i \in [1,n],L_{i}=[i-l_{i},i-r_{i}],R_{i}=[i+l_{i},i+r_{i}]\) ,上述条件转化为 \(\begin{cases} R_{i} \bigcap L_{j} \ne \varnothing & i<j \\ R_{j} \bigcap L_{i} \ne \varnothing & i>j \end{cases}\) ,又因为一定有 \(\begin{cases} R_{i} \bigcap L_{j}= \varnothing & i>j \\ R_{j} \bigcap L_{i} = \varnothing & i<j \end{cases}\) ,即 \((R_{i} \bigcap L_{j}) \bigcup (L_{i} \bigcap R_{j}) \ne \varnothing\) 。
\(S\) [ABC351G] Hash on Tree
\(T\) [ARC073F] Many Moves
\(U\) luogu P4719 【模板】"动态 DP"&动态树分治
\(V\) luogu P6348 [PA2011] Journeys
\(W\) luogu P2605 [ZJOI2010] 基站选址
\(X\) luogu P3521 [POI2011] ROT-Tree Rotations
数据结构2
开题顺序: \(BAHJCE\)
\(A\) luogu B3656 【模板】双端队列 1
-
用
list
代替deque
。 -
关于
deque
和list
的空间问题,详见 关于deque和list 。点击查看代码
list<int>q[1000001]; int main() { int n,i,a,x; string pd; cin>>n; for(i=1;i<=n;i++) { cin>>pd; if(pd=="push_back") { cin>>a>>x; q[a].push_back(x); } if(pd=="pop_back") { cin>>a; if(q[a].empty()==0) { q[a].pop_back(); } } if(pd=="push_front") { cin>>a>>x; q[a].push_front(x); } if(pd=="pop_front") { cin>>a; if(q[a].empty()==0) { q[a].pop_front(); } } if(pd=="size") { cin>>a; cout<<q[a].size()<<endl; } if(pd=="front") { cin>>a; if(q[a].empty()==0) { cout<<q[a].front()<<endl; } } if(pd=="back") { cin>>a; if(q[a].empty()==0) { cout<<q[a].back()<<endl; } } } return 0; }
\(B\) luogu P1892 [BOI2003] 团伙
-
考虑扩展域并查集,把一个点 \(x\) 拆成两个节点 \(x_{friend}\) 和 \(x_{enemy}\) 。
-
若 \(x,y\) 是朋友,则合并 \(x_{friend},y_{friend}\) ;否则合并 \(x_{friend},y_{enemy}\) 和 \(x_{enemy},y_{friend}\) 。
点击查看代码
int vis[2010]; struct DSU { int fa[2010]; void init(int n) { for(int i=1;i<=n;i++) { fa[i]=i; } } int find(int x) { return fa[x]==x?x:fa[x]=find(fa[x]); } void merge(int x,int y) { x=find(x); y=find(y); if(x!=y) { fa[x]=y; } } }D; int main() { int n,m,x,y,ans=0,i; char pd; cin>>n>>m; D.init(2*n); for(i=1;i<=m;i++) { cin>>pd>>x>>y; if(pd=='F') { D.merge(x,y); } else { D.merge(x,y+n); D.merge(x+n,y); } } for(i=1;i<=n;i++) { ans+=(vis[D.find(i)]==0); vis[D.find(i)]=1; } cout<<ans<<endl; return 0; }
\(C\) luogu P2502 [HAOI2006] 旅行
-
同 luogu P4234 最小差值生成树 的暴力做法,不妨钦定某一条边作为最小边在最终的生成树上。
-
接着按边权大小枚举剩下的边直至 \(s,t\) 连通。
点击查看代码
struct node { int from,to,w; }e[5010]; bool cmp(node a,node b) { return a.w<b.w; } int gcd(int a,int b) { return b?gcd(b,a%b):a; } struct DSU { int fa[510]; void init(int n) { for(int i=1;i<=n;i++) { fa[i]=i; } } int find(int x) { return (fa[x]==x)?x:fa[x]=find(fa[x]); } }D; int main() { int n,m,s,t,x,y,d,i,j; pair<int,int>ans=make_pair(1,0); cin>>n>>m; for(i=1;i<=m;i++) { cin>>e[i].from>>e[i].to>>e[i].w; } cin>>s>>t; sort(e+1,e+1+m,cmp); for(i=1;i<=m;i++) { D.init(n); for(j=i;j<=m;j++) { x=D.find(e[j].from); y=D.find(e[j].to); if(x!=y) { D.fa[x]=y; } if(D.find(s)==D.find(t)) { if(ans.first*e[i].w>ans.second*e[j].w) { ans.first=e[j].w; ans.second=e[i].w; } break; } } } if(ans.second==0) { cout<<"IMPOSSIBLE"<<endl; } else { if(ans.first%ans.second==0) { cout<<ans.first/ans.second<<endl; } else { d=gcd(ans.first,ans.second); cout<<ans.first/d<<"/"<<ans.second/d<<endl; } } return 0; }
\(D\) luogu P3402 可持久化并查集
\(E\) luogu P4768 [NOI2018] 归程
-
对 \(\{ a \}\) 建出 \(Kruskal\) 重构树(最大生成树),倍增找到合法点后子树内部的点都是开车可以到达的,取到 \(1\) 的距离的最小值即可。
点击查看代码
struct node { ll nxt,to,w; }e[800010]; ll head[200010],dis[400010],vis[200010],u[400010],v[400010],a[400010],p[400010],c[400010],fa[400010][25],f[400010],cnt=0; vector<ll>E[400010]; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } bool cmp(ll x,ll y) { return a[x]>a[y]; } void dijkstra(ll s) { memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); priority_queue<pair<ll,ll> >q; dis[s]=0; q.push(make_pair(-dis[s],s)); while(q.empty()==0) { ll x=q.top().second; q.pop(); if(vis[x]==0) { vis[x]=1; for(ll i=head[x];i!=0;i=e[i].nxt) { if(dis[e[i].to]>dis[x]+e[i].w) { dis[e[i].to]=dis[x]+e[i].w; q.push(make_pair(-dis[e[i].to],e[i].to)); } } } } } struct DSU { ll fa[400010]; void init(ll n) { for(ll i=1;i<=n;i++) { fa[i]=i; } } ll find(ll x) { return fa[x]==x?x:fa[x]=find(fa[x]); } }D; void kruskal(ll n,ll m) { D.init(2*n); sort(p+1,p+1+m,cmp); for(ll i=1,tot=n;i<=m&&tot<=2*n-1;i++) { ll x=D.find(u[p[i]]),y=D.find(v[p[i]]); if(x!=y) { tot++; c[tot]=a[p[i]]; D.fa[x]=D.fa[y]=tot; E[tot].push_back(x); E[tot].push_back(y); } } } void dfs(ll x,ll father) { f[x]=dis[x]; fa[x][0]=father; for(ll i=1;i<=20;i++) { fa[x][i]=fa[fa[x][i-1]][i-1]; } for(ll i=0;i<E[x].size();i++) { if(E[x][i]!=father) { dfs(E[x][i],x); f[x]=min(f[x],f[E[x][i]]); } } } ll ask(ll x,ll val) { ll rt=x; for(ll i=20;i>=0;i--) { if(fa[rt][i]!=0&&c[fa[rt][i]]>val) { rt=fa[rt][i]; } } return rt; } int main() { ll t,n,m,w,q,k,s,x,y,ans,i,j; scanf("%lld",&t); for(j=1;j<=t;j++) { cnt=ans=0; memset(e,0,sizeof(head)); memset(head,0,sizeof(head)); memset(c,0,sizeof(c)); scanf("%lld%lld",&n,&m); for(i=1;i<=2*n-1;i++) { E[i].clear(); } for(i=1;i<=m;i++) { scanf("%lld%lld%lld%lld",&u[i],&v[i],&w,&a[i]); add(u[i],v[i],w); add(v[i],u[i],w); p[i]=i; } dijkstra(1); kruskal(n,m); dfs(2*n-1,0); scanf("%lld%lld%lld",&q,&k,&s); for(i=1;i<=q;i++) { scanf("%lld%lld",&x,&y); x=(x+k*ans-1)%n+1; y=(y+k*ans)%(s+1); ans=f[ask(x,y)]; printf("%lld\n",ans); } } return 0; }
\(F\) luogu P5854 【模板】笛卡尔树
\(G\) luogu P2081 [NOI2012] 迷失游乐园
\(H\) luogu P4381 [IOI2008] Island
- 题解 。
\(I\) CF1713E Cross Swapping
\(J\) HDU6403 Card Game
\(K\) luogu P6453 [COCI2008-2009#4] PERIODNI
\(L\) luogu P3765 总统选举
\(M\) CF1920F2 Smooth Sailing (Hard Version)
\(N\) CF1687C Sanae and Giant Robot
\(O\) [AGC003E] Sequential operations on Sequence
\(P\) luogu P3452 [POI2007] BIU-Offices
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18447569,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。