把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷2612】[ZJOI2012] 波浪(DP+卡精度)

点此看题面

  • 定义一个序列的波动值为每相邻两项差的绝对值之和。
  • 求一个长度为\(n\)的序列波动值大于等于\(m\)的概率,保留\(d\)位小数。
  • \(n\le100,k\le30\)

动态规划

绝对值一看就非常麻烦,所以套路地想到从小到大枚举每一个数把它插入序列中,这样就能保证每次插入的数一定大于序列中已有的所有数,小于序列中没有的所有数。而大小关系一旦确定,绝对值也自然可以除去了。

考虑一个数\(i\)的贡献,分别考虑两侧,如果是边界则没有贡献,否则若\(i\)更大则产生\(i\)的贡献,若\(i\)更小则产生\(-i\)的贡献。

若把\(i\)插在序列中间,可以在插入时钦定它两侧的贡献。如果某侧是\(i\)说明这个空隙中不再插入数,如果某侧是\(-i\)说明这个空隙中必须插入数。因此我们可以记一下必须插入数的空隙个数\(k\),则把\(i\)插在序列中间的方案数其实也就是\(k\)

若把\(i\)插在序列边上,两侧贡献的讨论和上面是一样的,但一个需要额外考虑的问题是它是否为最终的端点(如果是则边界侧贡献为\(0\),如果不是则边界侧必然需要插入数贡献只能为\(-i\))。因此我们还需要记录已确定的最终端点个数\(x\),则把\(i\)插在序列边上的方案数应该是\(2-x\)

综上,设\(f_{i,j,k,x}\)表示当前要插入\(i\),贡献总和为\(j\)(为了防止出现负数,给它加上\(5000\)),必须插入数的空隙个数为\(k\),已确定端点个数为\(x\)时的方案数。

初始状态只需考虑\(1\)是否为最终端点。

卡精度

本意应该是想让我们写高精度,懒了点直接用__float128

然而全部开__float128大数据会T掉,因此需要特判\(d\le 8\)的点开long double

代码:\(O(n^4)\)

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 100
#define V 5000
using namespace std;
int n,m,d;long double f1[2][2*V+5][N+5][3];__float128 f2[2][2*V+5][N+5][3];
Tp I void T(Ty x)//保留d位小数
{
	if(!d) return (void)printf("%d\n",round((double)x));printf("%d.",(int)x),x-=(int)x;//输出整数部分(特判d=0)
	for(RI i=1;i^d;++i) printf("%d",(int)(x*=10)),x-=(int)x;printf("%d\n",(int)round((double)(x*=10)));//直接输出前d-1位,四舍五入输出第d位
}
Tp I void Solve(Ty f[2][2*V+5][N+5][3])//动态规划
{
	RI i,j,k,x;Ty v,nv;for(f[1][V-2][0][0]=1,f[1][V-1][0][1]=2,i=2;i<=n;++i)//初始化考虑1是否为最终端点
	{
		for(j=0;j<=2*V;++j) for(k=0;k^i;++k) for(x=0;x<=2;++x) f[i&1][j][k][x]=0;//清空
		#define DP(_j,_k,_x) (_j>=0&&_j<=2*V&&(f[i&1][_j][_k][_x]+=nv))//转移,防数组越界
		for(j=0;j<=2*V;++j) for(k=0;k^(i-1);++k) for(x=0;x<=2;++x) if(v=f[i&1^1][j][k][x]/i)
			(nv=k*v)&&(DP(j-2*i,k+1,x),DP(j,k,x),DP(j,k,x),DP(j+2*i,k-1,x)),//插在序列中间,钦定两侧贡献
			(nv=(2-x)*v)&&(DP(j,k,x),DP(j-2*i,k+1,x),DP(j+i,k,x+1),DP(j-i,k+1,x+1));//插在序列边上,讨论是否为最终端点,再钦定非边界侧贡献
	}
	Ty t=0;for(i=m;i<=V;++i) t+=f[n&1][V+i][0][2];T(t);//统计最终贡献和大于等于m的概率之和
}
int main()
{
	if(scanf("%d%d%d",&n,&m,&d),n==1) return T(m?0:1),0;if(m>V) return T(0),0;//特判n=1和m过大
	return d<=8?Solve(f1):Solve(f2),0;//数据分治
}
posted @ 2021-05-04 20:28  TheLostWeak  阅读(50)  评论(0编辑  收藏  举报