专栏文章

题解:P5215 [SHOI2014] 神秘金字塔

P5215题解参与者 1已保存评论 0

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
0 条
当前快照
1 份
快照标识符
@min4344l
此快照首次捕获于
2025/12/01 20:14
3 个月前
此快照最后确认于
2025/12/01 20:14
3 个月前
查看原文

思路

首先发现 lil_i 一定为偶数,且左右上下对称,所以可以直接分成四半大小为 n4\frac{n}4 的部分,每层的最大宽度减半。
设第 ii 列有 wiw_i 个数,根据题目限制,需要满足 wiwi+1w_i\ge w_{i+1},且 w1=l,wl1w_1=l,w_l\ge 1。这个东西的个数并不多,具体的,当 l=10l=10 时,状态数等于 (189)=48620\binom{18}{9}=48620
那么就可以 DP 了,设 fi,j,sf_{i,j,s} 表示第 ii 层,用了 jj 个方块,这一层的阶梯为 ss 的方案数。
考虑从 ii 转移到 i+1i+1,设 tt 为最大一个阶梯使得长度为 li+1l_{i+1} 且被 ss 包含。先把 fi,j,sf_{i,j,s} 转移到 fi+1,j,tf_{i+1,j,t},然后再对每一个 jj 做高维后缀和即可。最后处理每个阶梯对 jj 的影响。
时间复杂度 O(hnlV)O(hnlV),其中 VV 表示不同的阶梯数量,最大为 4862048620。算出来大约为 10910^9,不过常数被实现得特别优秀,可以通过。

细节

首先为了记录状态,我们需要对每一个 ll 按字典序从小到大枚举阶梯,将每个阶梯映射到一个编号,这一步使用 map 即可。
预处理 mni,smn_{i,s} 表示最大一个阶梯使得长度为 ii 且被 ss 包含。这个是简单的,mns,imn_{s,i} 的第 xx 个位置等于 min(sx,i)\min(s_x,i),套用上面的编号即可。
对于高维后缀和,我们需要预处理一个 deli,sdel_{i,s} 表示 ss 将第 ii 位减一后到哪个区间。做高维后缀和的时候,直接从大到小枚举 ii,从大到小枚举 ss 的编号,让 fx,y,dels,if_{x,y,del_{s,i}} 加上 fx,y,sf_{x,y,s} 即可。
常数小的原因是长度固定时,阶梯的编号是一个区间,内存访问较为连续,所以可以通过。

代码

CPP
#include<bits/stdc++.h>
using namespace std;
const int N = 7e4+5,mod = 1e9+7;
int cnt;
map<vector<int>,int> mp;
vector<int> now;
int lim,lt[N],rt[N],del[30][N],mn[30][N],sum[N]; 
void dfs(int x)
{
	if(x==lim)
	{
		mp[now] = ++cnt;
		mn[lim][cnt] = cnt;
		for(auto i:now) sum[cnt]+=i;
		if(!lt[lim]) lt[lim] = cnt;
		rt[lim] = cnt;
		for(int i = 1;i<lim;i++)
		{
			vector<int> tmp;
			for(int j = 0;j<i;j++)
				tmp.push_back(min(now[j],i));
			mn[i][cnt] = mp[tmp];
		}
		for(int i = 1;i<lim;i++)
		{
			if(now[i]>(i==lim-1?1:now[i+1]))
			{
				now[i]--;
				del[i][cnt] = mp[now];
				now[i]++;
			}
		}
		return;
	}
	for(int i = 1;i<=now[x-1];i++)
	{
		now[x] = i;
		dfs(x+1);
	}
}
vector<int> vec[N];
int n,h,a[N],f[2][255][N];
signed main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	for(lim = 1;lim<=10;lim++)
	{
		now.resize(lim);
		now[0] = lim;
		dfs(1);
	}
	cin>>n>>h;
	if(n%4) return cout<<0,0;
	n/=4;
	for(int i = 1,x;i<=h;i++)
	{
		cin>>a[i];
		if(a[i]%2) return cout<<0,0;
		a[i]/=2;
		if(i==1)
		{
			for(int j = lt[a[i]];j<=rt[a[i]];j++)
				if(sum[j]<=n) f[1][sum[j]][j] = 1;
		}
		else
		{
			int now = (i&1);
			for(int j = 0;j<=n;j++)
				for(int k = lt[a[i]];k<=rt[a[i]];k++)
					f[now][j][k] = 0;
			for(int j = 0;j<=n;j++)
				for(int k = lt[a[i-1]];k<=rt[a[i-1]];k++)
					(f[now][j][mn[a[i]][k]]+=f[now^1][j][k])%=mod;
			for(int j = n;j;j--)
			{
				for(int x = a[i]-1;x;x--)
					for(int k = rt[a[i]];k>=lt[a[i]];k--)
						(f[now][j][del[x][k]]+=f[now][j][k])%=mod;
				for(int k = lt[a[i]];k<=rt[a[i]];k++)
				{
					if(j+sum[k]<=n) (f[now][j+sum[k]][k]+=f[now][j][k])%=mod;
					f[now][j][k] = 0;
				}
			}
		}
	}
	int ans = 0;
	for(int i = lt[a[h]];i<=rt[a[h]];i++)
		(ans+=f[h&1][n][i])%=mod;
	cout<<ans;
	return 0;
}

评论

0 条评论,欢迎与作者交流。

正在加载评论...