Loading

dp 练习 2024.4.9/4.10

Luogu8389 [ZJOI2022] 树

考虑容斥,每个点有三个状态:不钦定、钦定都是叶子、钦定都是非叶子。对于每一种钦定状态都计算答案,然后乘上对应容斥系数即可。

叶子的钦定是可做的,考虑非叶子的钦定,我们可以用“容斥套容斥”的思想理解:对于钦定都是非叶子的点 \(i\),考虑用总方案数减去钦定其中之一是叶子的方案数,再加上两者都是叶子的方案数。

把非叶子的钦定放回原容斥里描述:减去总方案数,加上钦定第一棵树中是叶子的方案数、加上钦定第二棵树中是叶子的方案数、减去两棵树中都是叶子的方案数。

和不钦定、钦定叶子的方案加起来,相当于:加上钦定第一棵树中是叶子的方案数、加上钦定第二棵树中是叶子的方案数、减去 \(2\times\) 两棵树中都是叶子的方案数。

\(f[i,j,k]\) 表示考虑了前 \(i\) 个点,其中第一棵树中有 \(j\) 个没有被钦定是叶子的点,第二棵树中还剩 \(k\) 个没有被钦定是叶子的点。

转移容易。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define pir pair<ll,ll>
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const ll maxn=510, M=1e9;
ll n,mod,f[2][maxn][maxn],ans;
int main(){
	scanf("%lld%lld",&n,&mod);
	for(ll i=1;i<n;i++) f[1][1][i]=i;
	puts("1");
	for(ll i=2;i<n;i++){
		memset(f[i&1],0,sizeof f[i&1]);
		for(ll j=1;j<=n;j++)
			for(ll k=1;k<=n;k++){
				if(!f[i&1^1][j][k]) continue;
				f[i&1][j][k-1]=(f[i&1][j][k-1]+f[i&1^1][j][k]*j*(k-1))%mod;
				f[i&1][j+1][k]=(f[i&1][j+1][k]+f[i&1^1][j][k]*j*k)%mod;
				f[i&1][j][k]=(f[i&1][j][k]-2*f[i&1^1][j][k]*j*k)%mod;
			}
		ans=0;
		for(ll j=1;j<=i;j++)
			ans=(ans+f[i&1][j][1]*j)%mod;
		printf("%lld\n",(ans+mod)%mod);
	}
	return 0;
}

AGC036F Square Constraints

相当于有两个圆,每个位置的取值必须在两个圆中间,求方案数。

本质上还是 \(p_i\in[l_i,r_i]\) 的计数。考虑如果没有下面那个圆怎么做,把 \(r_i\) 从小到大排序,答案就是 \(\prod\limits_i (r_i-i+1)\)

如果有下面的圆,因为钦定后本质上还是在一段前缀上取值,我们考虑容斥

钦定取值在下面圆内的位置集合 \(S\),不难发现 \(S\in \{0,2,...,n-1\}\),考虑使用 dp 来综合所有的 \(S\)

但是这样有个问题,我们难以计算方案数。尝试先排序:对于 \(i=n...2n-1\),基准为 \(r_i\);对于 \(i=0...n-1\),基准为 \(l_i-1\)。按基准来排序。

对于未被钦定的位置 \(i\),我们还要知道 \(r_i\) 前面有多少个比他小的数,发现我们只要知道了钦定的位置个数就能算出。因为对于每个钦定的位置 \(i(i<n)\)\(l_i-1\) 总是不大于所有未钦定的位置 \(j(j<n)\)\(r_j\)

所以我们考虑 dp 开始前先枚举钦定的位置个数,便于计算。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define pir pair<ll,ll>
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const ll maxn=510;
ll n,id[maxn],mod,f[maxn][maxn],ans,l[maxn],r[maxn];
bool cmp(ll x,ll y){ ll p=(r[x]<r[y]);
	x=(x<n? l[x]-1:r[x]), y=(y<n? l[y]-1:r[y]);
	return x^y? x<y:p;
}
int main(){
	scanf("%lld%lld",&n,&mod);
	for(ll i=0;i<(n<<1);i++){
		if(i<n) l[i]=ceil(sqrt(n*n-i*i));
		r[i]=sqrt(4*n*n-i*i); id[i+1]=i;
		r[i]=min(r[i],(n<<1)-1);
	} sort(id+1,id+1+(n<<1),cmp);
	for(ll d=0;d<=n;d++){ //printf("d = %lld\n",d);
		memset(f,0,sizeof f);
		f[0][0]=1; ll cnt=0;
		for(ll i=1;i<=(n<<1);i++){
			for(ll j=0;j<=d&&j<=i-1-cnt;j++){
				if(id[i]>=n){
					if(cnt+j<=r[id[i]]) f[i][j]=(f[i][j]+f[i-1][j]*(r[id[i]]+1-cnt-j))%mod;
				}
				else{
					if(n+d+(i-1-cnt-j)<=r[id[i]]) f[i][j]=(f[i][j]+f[i-1][j]*(r[id[i]]+1-n-d-(i-1-cnt-j)))%mod;
					if(j<d&&cnt+j<l[id[i]]) f[i][j+1]=(f[i][j+1]+f[i-1][j]*(l[id[i]]-cnt-j))%mod;
				}
			} cnt+=(id[i]>=n);
		}
		ans=(ans+(d&1? mod-1:1)*f[n<<1][d])%mod;
	} printf("%lld",ans);
	return 0;
}

AGC040E Prefix Suffix Addition

考虑原问题的本质:将 \(x_i\) 分裂为 \(a_i+b_i\),钦定 \(a_0=b_{n+1}=0\),最小化 \(\sum\limits_{i=1}^n[a_{i-1}>a_i]+\sum\limits_{i=1}^n[b_i<b_{i+1}]\)

挖掘问题的本质很重要,这样能转化模型。

我们暴力,设 \(f[i,j]\) 表示确定了 \(a_{1...i},b_{1...i}\),并且 \(a_i=j\) 的答案。

转移很显然:

\[f[i,j]=\min_{0\le k\le x_{i-1}} \{[k>j]+[x_{i-1}-k<x_i-j]+f[i-1,k]\} \]

  • 一个 trick:注意每次转移最多加 \(2\),并且都是全局贡献,那么 \(f[i,\_]\) 最多 \(3\) 段,直接维护。

\(val=\min\limits_{0\le k\le x_{i-1}} f[i-1,k]\),且位置为 \(p\),不难发现 \(f[i,\max(p,x_i-(x_{i-1}-p))...x_i]\) 都会变成 \(val\)

接着处理 \(val+1\) 的段,是 \([\min(p,x_i-(x_{i-1}-p)),\max(p,x_i-(x_{i-1}-p)))\)

剩下那段是前缀,等于 \(val+2\)

顺便可以发现,我们的 \(p\) 是贪心取最小的位置,我们直接维护 \(p_1,p_2,p_3\) 表示三段的起点即可。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define pir pair<ll,ll>
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const ll maxn=2e5+10, mod=998244353, iv=mod-mod/2;
ll n,a[maxn],p1,p2,p3,val;
int main(){
	scanf("%lld",&n);
	p1=p2=p3=val=0;
	for(ll i=1;i<=n+1;i++){
		if(i<=n) scanf("%lld",a+i);
		ll q3=min(a[i]+1,max(p3,a[i]-(a[i-1]-p3))),
		q2=max(0ll,min(min(p3,a[i]-(a[i-1]-p3)),max(p2,a[i]-(a[i-1]-p2)))), q1=0, v=val;
		if(q3>a[i]) q3=q2, q2=q1, q1=0, ++v;
		p1=q1, p2=q2, p3=q3, val=v;
	} printf("%lld",val);
	return 0;
}

AGC022F Checkers

详见 AGC022F 做题记录

CF1930G Prefix Max Set Counting

直接 DP 最终序列,然后思考充要条件

\(f[u]\) 表示前缀 max 序列接到 \(u\) 的方案数。

\(p_u\) 表示 \(1\to u\) 路径上的最大点,当 \(p_u>u\)\(u\) 显然不可能在序列中。

否则思考上一个点 \(v\),首先可以是 \(v=p_u\),直接从 \(p_u\) 走到 \(u\)

如果不是,\(v\) 必定不是 \(u\) 的祖先。那么我们会发现一个条件,\(v\in [p_u+1,u-1]\),这个容易脑补证明。

思考还有什么条件,设 \(c\)\(\text{lca}(u,v)\),那么 \(v\)\(c\) 的包含 \(v\) 的儿子的子树内编号最大点。

这个比较显然,我们一定是先 dfs 完那棵子树再出来的。

然后我们会发现这些转移也是充分的,在这种条件下,我们不可能重复走进同一棵子树。

所以可以直接 DP。首先,\(v=p_u\) 的转移是容易的。

然后思考 \(v\) 不是祖先的情况,我们考虑求出 \(f[v]\) 时,不断祖先链,跳到 \(x\) 时需满足 \(v\)\(x\) 子树内最大点,然后在 \(fa_x\) 上加上 \(f[v]\) 的贡献。

这样一来,我们只需要求 \(u\) 所有祖先上面的贡献之和,树状数组即可。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define pir pair<ll,ll>
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const ll maxn=1e6+10, mod=998244353;
ll t,n,fa[maxn],mx[maxn],pre[maxn],tree[maxn],dfn[maxn],out[maxn],ti,f[maxn];
vector<ll>to[maxn],vec[maxn];
void dfs(ll u,ll ff){
	dfn[u]=++ti;
	fa[u]=ff; pre[u]=max(pre[ff],u);
	mx[u]=u;
	for(ll v:to[u])
		if(v!=ff) dfs(v,u), mx[u]=max(mx[u],mx[v]);
	out[u]=ti;
}
void add(ll x,ll v){
	while(x<=n){
		tree[x]=(tree[x]+v)%mod;
		x+=x&-x;
	}
}
ll ask(ll x){
	ll v=0;
	while(x){
		v=(v+tree[x])%mod;
		x&=x-1;
	} return v;
}
int main(){
	scanf("%lld",&t);
	while(t--){
		scanf("%lld",&n);
		for(ll i=1;i<n;i++){
			ll u,v; scanf("%lld%lld",&u,&v);
			to[u].pb(v), to[v].pb(u);
		}
		dfs(1,0);
		f[1]=1;
		for(ll i=2;i<=n;i++)
			if(pre[fa[i]]<i) vec[pre[fa[i]]].pb(i);
		for(ll u=2;u<=n;u++){
			if(pre[fa[u]]>u) continue;
			f[u]+=f[pre[fa[u]]], f[u]%=mod;
			f[u]=(f[u]+ask(dfn[u]))%mod;
			for(ll x:vec[u]) f[x]=mod-ask(dfn[x]);
			ll x=u;
			while(x>1&&mx[x]==u){
				add(dfn[fa[x]],f[u]), add(out[fa[x]]+1,mod-f[u]);
				x=fa[x];
			}
		}
		printf("%lld\n",f[n]);
		for(ll i=1;i<=n;i++){
			to[i].clear(), vec[i].clear();
			tree[i]=f[i]=0;
		} ti=0;
	}
	return 0;
}

AGC020F Arcs on a Circle

直接做很困难。

注意到所有弧长都是整数,他们的起始位置的小数部分期望下分别为 \(\dfrac 1{n+1},\dfrac 2{n+1},...,\dfrac n{n+1}\),由于 \(n\) 很小,暴力枚举全排列。

我们考虑断环为链,这里有个比较技巧的东西,以最长弧的起点断开,这样前面的弧一定包含不到这条弧,那么最后一个位置延伸过去的弧就不用管。

这样弧就变成了线段。由于每个线段的起点的小数部分已经确定,我们把 \(c\) 个点拆成 \(n\times c\) 个点,每个点都只能由一条线段为开头。

直接 dp,设 \(f[i,j,S]\) 表示考虑了起点 \(\le k\) 的所有线段,覆盖到了点 \(j\),使用了的线段集合为 \(S\),的概率。

转移是容易的。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define pir pair<ll,ll>
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const ll maxn=52, mod=1e9+7;
ll N,C,L[maxn],p[maxn];
double f[310][310][1<<6],ans;
int main(){
	scanf("%lld%lld",&N,&C);
	for(ll i=1;i<=N;i++) scanf("%lld",L+i);
	sort(L+1,L+1+N);
	for(ll i=2;i<=N;i++) p[i]=i-1;
	do{
		memset(f,0,sizeof f);
		f[1][L[N]*N+1][0]=1;
		for(ll i=2;i<=N*C;i++){
			for(ll j=i;j<=N*C+1;j++){
				for(ll S=0;S<(1<<N-1);S++){
					if(i<j) f[i][j][S]+=f[i-1][j][S];
					ll x=p[(i-1)%N+1];
					if(x&&!(S&(1<<x-1))){
						f[i][min(N*C+1,max(j,i+L[x]*N))][S|(1<<x-1)]+=f[i-1][j][S]/C;
					}
				}
			}
		}
		ans+=f[N*C][N*C+1][(1<<N-1)-1];
	}while(next_permutation(p+2,p+N+1));
	for(ll i=1;i<N;i++) ans/=i;
	printf("%.15lf",ans);
	return 0;
}

CF1466H Finding satisfactory solutions

详见 CF1466H 做题记录


Luogu4426 [HNOI/AHOI2018] 毒瘤

终于是一道比较 trival 的题了。

考虑广义串并联图的做法,会发现在此处仍然使用:

  • 删一度点

  • 缩二度点

每删掉一个点后,会带走一条边,所以最终是形如 \(x\) 个点,\(x+k\) 条边的图,其中 \(k=m-n\)

最终每个点度数都 \(\ge 3\),所以 \(3x\ge 2(x+k)\),解得 \(x\le 2k\),即 \(x\le 20\)

所以可以直接暴力枚举每个点的状态。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define pir pair<ll,ll>
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const ll maxn=2e5+10, mod=998244353;
struct Data {ll dp[2][2];};
set<ll>to[maxn];
unordered_map<ll,Data>w[maxn];
ll n,m,q[maxn],l,r,ans,g[maxn][2],vis[maxn],id[maxn],rk[maxn],len;
void ins(ll u,ll v,Data t){
	if(to[u].count(v)){
		Data p=w[u][v];
		p.dp[0][0]=p.dp[0][0]*t.dp[0][0]%mod;
		p.dp[0][1]=p.dp[0][1]*t.dp[0][1]%mod;
		p.dp[1][0]=p.dp[1][0]*t.dp[1][0]%mod;
		p.dp[1][1]=p.dp[1][1]*t.dp[1][1]%mod;
		w[u][v]=p;
		swap(p.dp[0][1],p.dp[1][0]), w[v][u]=p;
	} else{
		to[u].insert(v), to[v].insert(u);
		w[u][v]=t;
		swap(t.dp[0][1],t.dp[1][0]), w[v][u]=t;
	}
}
int main(){
	scanf("%lld%lld",&n,&m);
	for(ll i=1;i<=m;i++){
		ll u,v; scanf("%lld%lld",&u,&v);
		to[u].insert(v), to[v].insert(u);
		w[u][v]=w[v][u]=(Data){{{1,1},{1,0}}};
	}
	for(ll i=1;i<=n;i++) g[i][0]=g[i][1]=1;
	for(ll i=1;i<=n;i++)
		if(to[i].size()<=2) q[++r]=i, vis[i]=1;
	l=1; vis[q[n]]=0, r=min(r,n-1);
	while(l<=r){
		ll u=q[l++];
		if(to[u].size()==1){
			ll v=*to[u].begin(); to[u].clear(), to[v].erase(u);
			Data tmp=w[u][v];
			g[v][0]=(g[u][0]*tmp.dp[0][0]+g[u][1]*tmp.dp[1][0])%mod*g[v][0]%mod;
			g[v][1]=(g[u][0]*tmp.dp[0][1]+g[u][1]*tmp.dp[1][1])%mod*g[v][1]%mod; 
			if(r+1<n&&to[v].size()<=2&&!vis[v]) q[++r]=v, vis[v]=1;
		} else{
			ll v1=*to[u].begin(), v2=*to[u].rbegin();
			to[u].clear(), to[v1].erase(u), to[v2].erase(u);
			Data t1=w[v1][u], t2=w[u][v2];
			Data t;
			t.dp[0][0]=(t1.dp[0][0]*t2.dp[0][0]%mod*g[u][0]+t1.dp[0][1]*t2.dp[1][0]%mod*g[u][1])%mod;
			t.dp[0][1]=(t1.dp[0][0]*t2.dp[0][1]%mod*g[u][0]+t1.dp[0][1]*t2.dp[1][1]%mod*g[u][1])%mod;
			t.dp[1][0]=(t1.dp[1][0]*t2.dp[0][0]%mod*g[u][0]+t1.dp[1][1]*t2.dp[1][0]%mod*g[u][1])%mod;
			t.dp[1][1]=(t1.dp[1][0]*t2.dp[0][1]%mod*g[u][0]+t1.dp[1][1]*t2.dp[1][1]%mod*g[u][1])%mod;
			ins(v1,v2,t);
		}
	}
	for(ll i=1;i<=n;i++)
		if(!vis[i]) id[++len]=i, rk[i]=len;
	for(ll S=0;S<(1<<len);S++){ ll res=1;
		for(ll i=1;i<=len;i++){
			ll u=id[i]; res=res*g[u][(S>>i-1)&1]%mod; 
			for(set<ll>::iterator it=to[u].begin();it!=to[u].end();it++){
				ll v=*it;
				if(v>u) res=res*w[u][v].dp[(S>>i-1)&1][(S>>rk[v]-1)&1]%mod;
			}
		} ans=(ans+res)%mod;
	} printf("%lld",ans);
	return 0;
}

AGC012F Prefix Median

AGC,考虑充要条件,直接用充要条件计数。

先假设 \(a_{1...2n-1}\) 两两不同。

不妨先对 \(a\) 从小到大排序,首先可以观察到,前 \(2i-1\) 个数的中位数只能在 \(a_{i...n-i+1}\) 中选,就好比 \(2n-1\) 个数的中位数只能是 \(a_n\)

我们不妨倒着进行整个过程,对于 \(2n-1\to 2(n-1)-1\),中位数至多移动一位。什么意思,换句话说,前 \(2(n-1)-1\) 个数的中位数为 \(a_{n-1}/a_n/a_{n+1}\)

如果前 \(2(n-1)-1\) 个数中位数为 \(a_{n+1}\),那么对于前 \(2(n-2)-1\) 个数,中位数可以是 \(a_{n+2}/a_{n+1}/a_n/a_{n-1}\),注意 \(a_{n-1}\) 也可以选,因为我们在 \(2n-1\to 2(n-1)-1\) 的过程中,\(a_n\) 可能被删除。

这下结论很好猜了:对于 \(\forall i\),不存在 \(j<i\) 使得 \(b_j\) 夹在 \(b_i,b_{i+1}\) 两数之间。

把充要条件结合图像:一开始对于 \(2n-1\),只有一个黑点,并且一个小人在黑点上;每次往前枚举一位,两边各新增一个黑点,然后小人可以跳到任意一个黑点,并把两个黑点中间的黑点全部删掉,求跳跃方案数。

这个 dp 是容易的,设 \(f[i,j,k]\) 表示处理了 \(i...n\) 轮,并且有 \(j\) 个黑点,小人在第 \(k\) 个黑点上。

考虑 \(a_{1...2n-1}\) 有相同的情况,不难发现,数值相同的黑点其实没有意义,我们只需要保留其中一个即可。

因此,当新拓展的黑点数值和原来有的黑点一样时,拓展是不必要的,直接 dp 也是容易的。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define pir pair<ll,ll>
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const ll maxn=110, mod=1e9+7;
ll n,a[maxn],f[maxn][maxn][maxn],b[maxn],ans;
void add(ll &x,const ll &y) {x=(x+y>=mod? x+y-mod:x+y);}
int main(){
	scanf("%lld",&n);
	for(ll i=1;i<(n<<1);i++){
		scanf("%lld",a+i);
	} sort(a+1,a+(n<<1));
	b[a[n]]=1; ll cnt=1;
	f[n][1][1]=1;
	for(ll i=n-1;i;i--){
		ll le=(!b[a[n-(n-i)]]), ri=(!b[a[n+(n-i)]]);
		b[a[n-(n-i)]]=b[a[n+(n-i)]]=1;
		for(ll j=1;j<=cnt;j++)
			for(ll k=1;k<=j;k++){
				if(le) add(f[i][j+ri+le-(k-1)][1],f[i+1][j][k]);
				if(ri) add(f[i][k+le+ri][k+le+ri],f[i+1][j][k]);
				add(f[i][j+le+ri][k+le],f[i+1][j][k]);
				for(ll x=1;x<=j;x++)
					if(x<k) add(f[i][x+(j-k+1)+le+ri][x+le],f[i+1][j][k]);
					else if(x>k) add(f[i][k+(j-x+1)+le+ri][k+1+le],f[i+1][j][k]);
			}
		cnt+=le+ri;
	}
	for(ll i=1;i<=cnt;i++)
		for(ll j=1;j<=i;j++) add(ans,f[1][i][j]);
	printf("%lld",ans);
	return 0;
}

AGC041F Histogram Rooks

思考容斥。

钦定 \(k\) 个格子不被覆盖,容斥系数为 \((-1)^k\),每个格子所在行和列的并中所有格子都不能放车。

不妨直接考虑钦定了哪些行和列不能放车,设两个集合分别为 \(S_r,S_c\),令 \(d\) 为不被这两个集合包含的格子个数。那么贡献是

\[2^d\sum_k (-1)^k\times 「\text{选 }k\text{ 个格子,恰好覆盖 } S_r,S_c\text{ 的方案数}」 \]

后者不好做,考虑二次容斥,钦定一些行和列不被覆盖。

下面称 “二次钦定集合” 为不被二次容斥钦定不被选择的格子覆盖的行和列,“一次钦定集合” 为钦定的不能放车的行和列。

设二次钦定集合为 \(S_2\),一次钦定集合为 \(S_1\),没有被钦定的格子个数为 \(d\),二次钦定集合中行和列同时包含的格子个数为 \(t\)

那么贡献为:

\[(-1)^{|S_1|}2^d\sum_{k=0}^t (-1)^k \binom tk \]

注意后面的求和式,等价于 \([t=0]\)容斥系数抵消,因此我们只需要求 \(S_2\) 中行和列两两不交的情况的贡献。

因此我们只需要关心如何求 \(d\),以及是否存在二次钦定。

\(f[i,j,0/1]\) 表示对于笛卡尔树上的点 \(i\),其子树管辖的范围中,一共钦定了 \(j\) 列,并且不存在/存在二次钦定的列。

转移先合并左右两边,然后枚举 \(i\) 管辖的行和列中的钦定情况即可。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define pir pair<ll,ll>
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
using namespace std;
const ll maxn=410, mod=998244353;
ll n,a[maxn],lc[maxn],rc[maxn],stk[maxn],top,f[maxn][maxn][2],rt,g[maxn][2],pw[maxn*maxn],ans;
ll fac[maxn],ifac[maxn];
ll C(ll n,ll m) {return n<m? 0:fac[n]*ifac[m]%mod*ifac[n-m]%mod;}
void dfs(ll u,ll l,ll r,ll ff){
	if(lc[u]) dfs(lc[u],l,u-1,u);
	if(rc[u]) dfs(rc[u],u+1,r,u);
	memset(g,0,sizeof g);
	for(ll i=0;i<=u-l;i++)
		for(ll j=0;j<=r-u;j++)
			g[i+j][0]=(g[i+j][0]+f[lc[u]][i][0]*f[rc[u]][j][0])%mod,
			g[i+j][1]=(g[i+j][1]+f[lc[u]][i][1]*f[rc[u]][j][1]
			+f[lc[u]][i][1]*f[rc[u]][j][0]+f[lc[u]][i][0]*f[rc[u]][j][1])%mod;
	for(ll i=r-l;~i;i--){
		g[i+1][0]=(g[i+1][0]+mod-g[i][0])%mod, g[i+1][1]=(g[i+1][1]+g[i][0])%mod;
	} ll d=a[u]-a[ff];
	for(ll i=0;i<=r-l+1;i++){
		for(ll j=0;j<=d;j++){
			f[u][i][1]=(f[u][i][1]+g[i][1]*(j&1? mod-1:1)%mod
			*pw[(r-l+1-i)*(d-j)]%mod*C(d,j))%mod;
			for(ll k=0;j+k<=d;k++){
				f[u][i][0]=(f[u][i][0]+g[i][0]*(j&1? mod-1:1)%mod
				*pw[(r-l+1-i)*(d-j-k)]%mod*C(d,j)%mod*C(d-j,k))%mod;
			}
		}
	}
}
ll power(ll a,ll b=mod-2){
	ll s=1;
	while(b){
		if(b&1) s=s*a%mod;
		a=a*a%mod; b>>=1;
	} return s;
}
int main(){
	f[0][0][0]=1;
	scanf("%lld",&n);
	pw[0]=fac[0]=1;
	for(ll i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
	ifac[n]=power(fac[n]);
	for(ll i=n;i;i--) ifac[i-1]=ifac[i]*i%mod;
	for(ll i=1;i<=n*n;i++) pw[i]=pw[i-1]*2%mod;
	for(ll i=1;i<=n;i++){
		scanf("%lld",a+i);
		while(top&&a[stk[top]]>a[i]) lc[i]=stk[top--];
		if(top) rc[stk[top]]=i;
		stk[++top]=i;
	}
	rt=stk[1];
	dfs(rt,1,n,0);
	for(ll i=0;i<=n;i++)
		ans=(ans+f[rt][i][0]+f[rt][i][1])%mod;
	printf("%lld",ans);
	return 0;
}
posted @ 2024-04-09 16:39  Sktn0089  阅读(71)  评论(2编辑  收藏  举报