[DP] 跳水运动员

简要题意:
给出一棵树,要求割掉 x 条边,将树分成若干个连通块。定义一个连通块合法当且仅当对于联通快内点 u,vx[u,v] 也在这连通块内。

对于 x=0,1,2,...k1,输出割掉 x 条边的方案数。

个人错误做法

转化问题,对于每一个 m[0,k),求将序列分成 m+1 段,且每一个段都构成一个联通块的方案数。

考虑构成连通块的条件:
对于区间 [l,r],对于任一点 luvr uv 的路径上所有点的值都在 [l,r] 之间。

这启发我们考虑构造重构树,则有结论:合法区间 [l,r] 所有节点的小根树的 lcal,大根树的 lcar

我们考虑 dp,f[i][j] 表示把序列 1...i 分成 j 段的方案数。

如果我们可以优化转移到O(1),复杂度就是 O(nk)。 我们可以一层一层转移。

转化条件:小根树上,区间 [l,r] 都为 l 的儿子,大根树上,区间 [l,r] 都是 r 的儿子。

我们对于每一个 l 求出一个最大的 rpl 满足 [l,rpl] 都是 l 的儿子,对于每一个 r 求出一个最小的 lpr 满足 [lpr,r] 都是 r 的儿子。

先写一棵主席树,然后二分端点,查询区间的子树和是否为区间个数。然后就可以把 dp 转化成偏序问题。

f[k][i]=ji & rpji & lpijf[k1][j]

利用树状数组优化这个二维偏序,利用滚动数组优化空间,复杂度为 O(nklogn)。 和 O(n2k) 一个分数。

正解

O(nk+nlogn) 6,好像跟 cust10 做法一样。

同样考虑序列上从前往后 dp。
观察到一个联通块的另外一个条件,我们记录父亲信息,有且仅有一个点的父亲不在 [L,R] 内。

所以我们分类讨论,考虑决策点 j。先利用 set 记录对于 i,前面的父亲大于 i 的最近点 x 和次近点 y

  • j[y,x),区间 (j,i] 没有连向前面的边。
  • j[x,i),区间 (j,i] 有一条连向前面的边。

考虑定义两个 set, s0,s1 分别记录区间 (j,i] 有没有连向前面的边的决策点的集合。

如果 fai<i,那么对于 j[fai,i) 会多一条连向前面的边。把这一部分的点从 s1 中删去,把 s0 中的点加到 s1

直接做复杂度 O(nklogn)

观察到每次删除和加入都是一段后缀,把 set 换成单调栈,从 s0 拼到 s1 这一部分的修改仍然不会改变有序性。观察到决策点的集合不会改变,只有每个决策点的贡献的改变。因此我们利用单调栈内二分预处理出每个点在 s0,s1 的栈内有贡献的区间。每轮 dp 时,再重复维护单调栈并维护前缀和,利用前缀和和预处理的贡献区间。优化到 O(nk)

总复杂度 O(nk+nlogn)。比错解好写 \jk

code
#include<bits/stdc++.h>
typedef long long LL;
using namespace std;
const int N=2e5+5,Md=998244353;
vector<int> vt[N];
int n,K,fa[N];
void dfs(int u,int Fa){
	fa[u]=Fa;
	for(int v:vt[u])
		if(v!=Fa) dfs(v,u);
}
LL f[2][N],ans[N];
int st0[N],tp0,st1[N],tp1;
int l1[N],l2[N],lp1[N],lp2[N],lp3[N];
set<int> s1;
LL ss0[N],ss1[N];
int main(){
	scanf("%d%d",&n,&K);
	for(int i=1,u,v;i<n;i++){
		scanf("%d%d",&u,&v);
		vt[u].push_back(v); vt[v].push_back(u);
	}
	dfs(1,0);
	for(int i=1;i<=n;i++){
		if(fa[i]>i) s1.insert(i);
		for(int v:vt[i])
			if(v!=fa[i] && v<i) s1.erase(v);
		if(s1.size()>=1) l1[i]=*--s1.end();
		if(s1.size()>=2) {
			auto p=--s1.end(); --p;
			l2[i]=*p;
		}
	}
	tp0=tp1=0;
	for(int i=1;i<=n;i++){
		st0[++tp0]=i-1;
		if(fa[i]<=i){
			while(tp1 && st1[tp1]>=fa[i]) tp1--;
			int x=tp0;
			while(x && st0[x]>=fa[i]) x--;
			for(int j=x+1;j<=tp0;j++) st1[++tp1]=st0[j];
			tp0=x;
		}	
		lp1[i]=lower_bound(st0+1,st0+tp0+1,l2[i])-st0-1;
		lp2[i]=lower_bound(st0+1,st0+tp0+1,l1[i])-st0-1;
		lp3[i]=lower_bound(st1+1,st1+tp1+1,l1[i])-st1-1;
	}
	int nw=0; f[0][0]=1;
	for(int k=1;k<=K;k++){
		nw^=1; for(int i=0;i<=n;i++) f[nw][i]=0;
		tp0=tp1=0;
		for(int i=1;i<=n;i++){
			st0[++tp0]=i-1; ss0[tp0]=(ss0[tp0-1]+f[nw^1][i-1])%Md;
			if(fa[i]<=i){
				while(tp1 && st1[tp1]>=fa[i]) tp1--;
				int x=tp0;
				while(x && st0[x]>=fa[i]) x--;
				for(int j=x+1;j<=tp0;j++) st1[++tp1]=st0[j],ss1[tp1]=(ss1[tp1-1]+f[nw^1][st0[j]])%Md;
				tp0=x;
			}
			f[nw][i]=(ss0[lp2[i]]-ss0[lp1[i]]+ss1[tp1]-ss1[lp3[i]]+2*Md)%Md;
		}
		ans[k-1]=f[nw][n];
	}
	for(int i=0;i<K;i++) printf("%lld\n",ans[i]);
	return 0;
}
posted @   Shui_dream  阅读(28)  评论(0编辑  收藏  举报
(评论功能已被禁用)
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示