【2022国赛模拟】[SDWC Day4] 数列——树形DP

好久没有写题解了喔!这次又没有链接!

题目描述

在这里插入图片描述

题解

由于 n n n 比较小,我们不妨把我们能直接得到的大小关系用有向边建出来( > > > ≥ \ge 的边作区分),那么可以得到一个 DAG。

然后我们发现,这个 DAG 上有很多边是没用的。比如有 a > b , a > c , c ≥ b a>b,a>c,c\ge b a>b,a>c,cb,那么此时 a > b a>b a>b 这条边就是没用的。

我们把所有没用的边去掉后,这个 DAG 一定可以变为一颗内向/外向树,因为它必然是一棵合法笛卡尔树的一部分。此时树上每个节点到祖先的路径必然是原 DAG 上的最长路径,所以可以用 Floyd 求最长路然后辅助判断。

然后就是一个裸的树形DP了,总复杂度 O ( n 3 + n m ) O(n^3+nm) O(n3+nm)

代码

#include<bits/stdc++.h>//JZM yyds!!
#define ll long long
#define lll __int128
#define uns unsigned
#define fi first
#define se second
#define IF (it->fi)
#define IS (it->se)
#define END putchar('\n')
#define lowbit(x) ((x)&-(x))
#define inline jzmyyds
using namespace std;
const int MAXN=1e5+5;
const ll INF=1e17;
ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
int ptf[50],lpt;
void print(ll x,char c='\n'){
	if(x<0)putchar('-'),x=-x;
	ptf[lpt=1]=x%10;
	while(x>9)x/=10,ptf[++lpt]=x%10;
	while(lpt>0)putchar(ptf[lpt--]^48);
	if(c>0)putchar(c);
}
const ll MOD=998244353;
ll ksm(ll a,ll b,const ll&mo=MOD){
	ll res=1;
	for(;b;b>>=1,a=a*a%mo)if(b&1)res=res*a%mo;
	return res;
}
using pii=pair<int,int>;
int n,m,fa[105],ds[105][105];
vector<int>G[105],D[105];
ll dp[105][MAXN],ans=1;
void addD(int u,int v,bool w){D[u].emplace_back(v<<1|w);}
void ad(ll&a,const ll&b){a+=b;if(a>=MOD)a-=MOD;}
void dfs(int x){
	for(int i=1;i<=m;i++)dp[x][i]=1;
	for(int pv:G[x]){
		int v=pv>>1,e=pv&1;
		dfs(v);
		ll cg=0;
		for(int i=1;i<=m;i++)ad(cg,dp[v][i-e]),(dp[x][i]*=cg)%=MOD;
	}
}
int main()
{
	freopen("array.in","r",stdin);
	freopen("array.out","w",stdout);
	n=read(),m=read();
	for(int i=1;i<=n;i++){
		int b=read();
		if(b<0)continue;
		if(b)addD(b,i,1);
		for(int j=b+1;j<i;j++)addD(i,j,0);
	}memset(ds,-0x3f,sizeof(ds));
	for(int x=1;x<=n;x++){
		ds[x][x]=0;
		for(int pv:D[x])ds[x][pv>>1]=1;
	}
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)if(i^k)
			for(int j=1;j<=n;j++)if((i^j)&&(k^j))
				ds[i][j]=max(ds[i][j],ds[i][k]+ds[k][j]);
	for(int x=1;x<=n;x++)
		for(int pv:D[x]){
			int v=pv>>1;
			if(ds[x][v]==1)G[x].push_back(pv),fa[v]=x;
		}
	for(int x=1;x<=n;x++)if(!fa[x]){
		dfs(x);ll cg=0;
		for(int i=1;i<=m;i++)ad(cg,dp[x][i]);
		(ans*=cg)%=MOD;
	}
	print(ans);
	return 0;
}

优化

第一部分的删边,其实有更快的拓扑排序的 O ( n 3 ω ) O(\frac{n^3}{\omega}) O(ωn3) 做法,而第二部分的DP,由于DP值是个 n n n 次函数,所以可以用拉插做到 O ( n 2 ) O(n^2) O(n2)

所以 n n n 完全可以开到 1000 以上。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值