HDU4624 Endless Spin Min-Max容斥+DP

题目分析

朋友,你听说过Min-Max容斥吗?

所谓Min-Max容斥就是这样一个式子: E ( m a x ( S ) ) = ∑ T ⊂ S ( − 1 ) ∣ S ∣ + 1 E ( m i n ( T ) ) E(max(S))=\sum_{T \subset S} (-1)^{|S|+1}E(min(T)) E(max(S))=TS(1)S+1E(min(T))。所谓的max,就是集合内最后一个被选中的元素被选中的期望代价,min就是第一个被选中的元素被选中的期望代价。

那么这题用一个Min-Max容斥,问题就转化为求每一个集合里的点,至少选中一个的期望。那么只要算出每一次决策一个都选不中的概率,也就是选择一次,选择的区间不包含这个集合里任何点的概率就行了。

复杂度 O ( 2 n ) O(2^n) O(2n),你只需要把评测机换成神威太湖之光,就能AC此题。

设一个DP状态, f ( i , j , k , 0 / 1 ) f(i,j,k,0/1) f(i,j,k,0/1),表示考虑前 i i i个球是否在集合中,有 j j j个区间可以保证不选到在集合中的球,最后一个选择的球到 i i i位置之间有 k k k个空位,集合中选择的球的数量是偶数/奇数的状态数,转移通过考虑第 i + 1 i+1 i+1个球选不选有两种:

f ( i + 1 , j , 0 , 1 / 0 ) + = f ( i , j , k , 0 / 1 ) f(i+1,j,0,1/0)+=f(i,j,k,0/1) f(i+1,j,0,1/0)+=f(i,j,k,0/1)
f ( i + 1 , j + k + 1 , k + 1 , 0 / 1 ) + = f ( i , j , k , 0 / 1 ) f(i+1,j+k+1,k+1,0/1)+=f(i,j,k,0/1) f(i+1,j+k+1,k+1,0/1)+=f(i,j,k,0/1)

这个DP是 O ( n 4 ) O(n^4) O(n4)的。

假设共有 i i i个球,某个集合 T T T j j j个区间可以保证不选到在集合内的球,那么 E ( m i n ( T ) ) = ( i + 1 ) i 2 ( i + 1 ) i 2 − j E(min(T))=\frac{\frac{(i+1)i}{2}}{\frac{(i+1)i}{2}-j} E(min(T))=2(i+1)ij2(i+1)i,通过考虑集合内球数量的奇偶性,就可以做Min-Max容斥啦。

但是这题…尼玛保留15位小数。脑子有坑的litble打了一个高精度小数,并且在打完之后才意识到,应该打一个分数之间的运算,到最后再化成小数,算了,反正过了,就这样吧。

代码

#include<bits/stdc++.h>
using namespace std;
#define RI register int
typedef long long LL;
const int N=52,K=30;
struct QAQ{LL zs,xs[K+3];}ans[N];
LL f[N][N*N][N][2];
int T,n;
void print(QAQ a) {
	if(a.xs[16]>=5) ++a.xs[15];
	for(RI i=15;i>=2;--i) if(a.xs[i]==10) a.xs[i]=0,++a.xs[i-1];
	if(a.xs[1]==10) a.xs[1]=0,++a.zs;
	printf("%lld.",a.zs);
	for(RI i=1;i<=15;++i) printf("%lld",a.xs[i]);
	puts("");
}
QAQ getQAQ(LL fz,LL fm) {
	QAQ re; re.zs=fz/fm,fz%=fm,fz*=10;
	for(RI i=1;i<=K;++i) re.xs[i]=fz/fm,fz%=fm,fz*=10;
	return re;
}
QAQ operator + (const QAQ a,const QAQ b) {
	LL x=0;QAQ re;
	for(RI i=K;i>=1;--i)
		re.xs[i]=a.xs[i]+b.xs[i]+x,x=re.xs[i]/10,re.xs[i]%=10;
	re.zs=a.zs+b.zs+x;return re;
}
QAQ operator - (QAQ a,const QAQ b) {
	for(RI i=K;i>=2;--i) {
		if(a.xs[i]<b.xs[i]) a.xs[i]+=10,--a.xs[i-1];
		a.xs[i]-=b.xs[i];
	}
	if(a.xs[1]<b.xs[1]) a.xs[1]+=10,--a.zs;
	a.xs[1]-=b.xs[1],a.zs-=b.zs;
	return a;
}
QAQ mul(QAQ a,LL b) {
	LL x=0;
	for(RI i=K;i>=1;--i)
		a.xs[i]=a.xs[i]*b+x,x=a.xs[i]/10,a.xs[i]%=10;
	a.zs=a.zs*b+x;return a;
}
void prework() {
	f[0][0][0][0]=1;
	for(RI i=0;i<50;++i)
	  for(RI j=0;j<=i*(i+1)/2;++j)
		for(RI k=0;k<=i;++k)
		  for(RI t=0;t<=1;++t) {
		  	f[i+1][j][0][t^1]+=f[i][j][k][t];
		  	f[i+1][j+k+1][k+1][t]+=f[i][j][k][t];
		  }
	for(RI i=1;i<=50;++i)
	  for(RI j=0;j<i*(i+1)/2;++j) {
	  	QAQ tmp=getQAQ(i*(i+1)/2,i*(i+1)/2-j);
	    for(RI t=0;t<=1;++t) {
	    	LL js=0;
	    	for(RI k=0;k<=i;++k) js+=f[i][j][k][t];
	    	if(t) ans[i]=ans[i]+mul(tmp,js);
	    	else ans[i]=ans[i]-mul(tmp,js);
	    }
	  }
}
int main()
{
	scanf("%d",&T);
	prework();
	while(T--) scanf("%d",&n),print(ans[n]);
    return 0;
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值