IOI2021集训队作业 293BL Weather Report

长度为\(n\)的序列,序列的每个位置有\(4\)种取值。\(i\)出现在一位的概率为\(p_i\)

你需要对\(4^n\)个序列编码,使得期望的序列长度最小。

编码满足任意一个序列的编码不是另一个序列的编码的前缀。

\(n\le 20\)


\(4^n\)个序列的出现概率求出来,然后合并果子。

优化这个过程:我们只关注组成而不是顺序,所以初始状态数为\(\binom{n+4-1}{n}\)种,不多。合并果子的时候将相同的记在一起,然后一起合并。

时间\(O(n\lg^2n)\)。多个\(\lg\)是因为每次相同的状态个数减半。


using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 25
#define ll long long
#define M 100005
int n;
ll C[N][N];
double p[4];
struct Status{
	double w;
	ll num;
} q[M];
int nq;
bool cmpq(Status son,Status fa){return son.w>fa.w;}
void dfs(int k,double w,ll num,int s){
	if (k==3){
		for (;s;--s,w*=p[k]);
		q[nq++]={w,num};
		return;
	}
	for (int i=0;i<=s;++i,w*=p[k])
		dfs(k+1,w,num*C[s][i],s-i);
}
double ans;
int main(){
	freopen("in.txt","r",stdin);
	scanf("%d",&n);
	for (int i=0;i<=n;++i){
		C[i][0]=1;
		for (int j=1;j<=i;++j)
			C[i][j]=C[i-1][j-1]+C[i-1][j];
	}
	for (int i=0;i<4;++i)
		scanf("%lf",&p[i]);
	dfs(0,1,1,n);
	make_heap(q,q+nq,cmpq);
	while (nq>1 || nq==1 && q[0].num>1){
		double w=q[0].w;
		ll num=q[0].num;
		pop_heap(q,q+nq--,cmpq);
		if (!(num&1)){
			ans+=w*num;
			q[nq++]={w*2,num/2};
			push_heap(q,q+nq,cmpq);
		}
		else{
			ans+=w*(num-1);
			if (num>1){
				q[nq++]={w*2,num/2};
				push_heap(q,q+nq,cmpq);
			}
			double w1=q[0].w;
			ll num1=q[0].num;
			pop_heap(q,q+nq--,cmpq);
			ans+=w+w1;
			q[nq++]={w+w1,1};
			push_heap(q,q+nq,cmpq);
			if (num1>1){
				q[nq++]={w1,num1-1};
				push_heap(q,q+nq,cmpq);
			}
		}
	}
	printf("%.6lf\n",ans);
	return 0;
}
posted @ 2020-10-23 22:06  jz_597  阅读(110)  评论(0编辑  收藏  举报