• ●CodeForces 698C LRU


    题链:

    http://codeforces.com/problemset/problem/698/C
    题解.1:

    概率dp,状压dp
    棒棒哒题解:https://www.cnblogs.com/liu-runda/p/6256730.html

    由于操作次数很多,所以可以看成无穷次,
    且题中明确给出K<=N,所以一般来说最后的缓存区中有k个物品的概率非常接近1。
    或者这么来理解:
    如果这么多次的操作我们都只取到了某固定的k-1个物品,那么也就意味这其它的物品一次都没取到过,
    设其它物品的概率和为x,那么它们一次都没取到的概率就为x^inf,非常接近0,即基本不可能只取到过某固定的k-1个物品。
    (当然也有特殊情况,如果某些物品(假设有h个)的出现概率为0,那么只有N-h个物品可能被得到,
    所以K=min(k,N-h),更改一下缓存区里能存在的物品的最大个数)

    显然,对于任意一个操作序列,操作结束后,缓冲区里存在哪k个物品,只与这个操作序列的末尾有关。
    更详细的是指:那k个物品,就是这个操作序列从后往前数,首先数到的k个不同的物品。(然后前面的物品无论怎样出现都没有任何关系了)
    那么只要求得一个k个物品的集合的出现概率P,那么这个集合里面所有元素的出现概率都要+P。
    然后我们需要计算出所有k个物品的集合的出现概率,而且k的大小是20,毫不犹豫的想到状压dp

    令dp[S]表示从后往前已经拥有的物品的集合为S的出现概率:
    设S集合里的物品出现的概率和为x,然后枚举每个不在S里的物品i。
    dp[S|idx(i)]=dp[S]*p[i]/(1-x).
    p[i]/(1-x)意思是i物品在未出现的物品种出现的概率。
    因为遇到已在S里的物品对S这个状态没有任何影响,所以只考虑不在S里的物品。

    然后每当S的大小为K时,就把dp[S]加到S里的每个物品i的出现概率ans[i]上去。


    代码.1:

    #include<bits/stdc++.h>
    #define MAXN 20
    using namespace std;
    const double eps=1e-9;
    double p[1<<MAXN],g[1<<MAXN],ans[MAXN+5],dp[1<<MAXN];
    int cnt[1<<MAXN];
    int N,K,ALL,k;
    int idx(int i){return 1<<(i-1);}
    int dcmp(double x){
    	if(fabs(x)<eps) return 0;
    	return x>0?1:-1;
    }
    int main(){
    	ios::sync_with_stdio(0);
    	cin>>N>>K; ALL=idx(N+1); k=N;
    	for(int i=1;i<=N;i++){
    		cin>>p[idx(i)];
    		if(dcmp(p[idx(i)])==0) k--;
    	}
    	K=min(k,K);
    	for(int S=1,i;i=S&(-S),S<ALL;S++)
    		g[S]=g[S^i]+p[i],cnt[S]=cnt[S^i]+1;
    	dp[0]=1;
    	for(int S=1;S<ALL;S++){
    		for(int i=1;i<=N;i++) if(idx(i)&S)
    			if(dcmp(1-g[idx(i)^S])!=0) dp[S]+=dp[S^idx(i)]*p[idx(i)]/(1-g[S^idx(i)]);
    		if(cnt[S]==K) for(int i=1;i<=N;i++) if(idx(i)&S) ans[i]+=dp[S];
    	}
    	for(int i=1;i<=N;i++)
    		cout<<fixed<<setprecision(7)<<ans[i]<<" ";
    	return 0;
    }
    

      

    题解.2:

    容斥,概率
    同样的,由于操作次数为10^100,所以可以看成是无穷次。
    假设对于一个操作序列,如果要使得最后的k个物品中存在i物品,
    那必然在最后一次出现了i物品之后出现的其他物品种类数不超过k-1种.(即可能出现了0种其它物品,1种其它物品,2种其他物品,...,k-1种其他物品)
    现在我们假设之后出现了k-1种其它物品,这k-1种物品的集合为S,且这k-1种物品的出现概率和为x,
    那么由于i物品最后一次出现的具体位置未知,
    所以有如下计算式来计算上述这种“出现了i以后再出现的其他物品种类不超过k-1种且集合为S”的情况的发生概率:
    P=p[i]*(x+x^2+x^3+...+x^inf)=p[i]*1.0/(1-x)
    由于这只是一种k-1种物品的集合的情况,所以还要枚举所有的k-1种物品的集合来进行上述操作,并把每个求得的概率累加到一起。
    显然,存在了许多重复和无用信息,
    举个例子,上面由若干个k-1种物品的集合所得到的概率和∑P中,
    把任意一种“出现了i以后再出现的其他物品种类不超过k-2种且集合为S'”的情况的都包含了若干次,
    具体来说是C(N-1-(k-2),1)次,(也就是S'的不含i物品的超集的个数)
    所以需要把这些多余的减去,这就是容斥的原因,
    注意到容斥系数只与集合大小有关,
    我们只需要预处理出集合大小为cnt时的容斥系数即可。

    (因为我们要求之后出现的其他物品种类为0种,1种,2种...k-1的概率和,所以在处理容斥系数之前,首先把每个的容斥系数都定为1)

    代码.2:

    #include<bits/stdc++.h>
    #define MAXN 20
    using namespace std;
    const double eps=1e-9;
    double a[MAXN],p[1<<MAXN],h[MAXN+5],dp[1<<MAXN],c[MAXN+5][MAXN+5];
    int cnt[1<<MAXN];
    int N,K,ALL,k;
    int idx(int i){return 1<<(i-1);}
    int dcmp(double x){
    	if(fabs(x)<eps) return 0;
    	return x>0?1:-1;
    }
    int main(){
    	ios::sync_with_stdio(0);
    	cin>>N>>K; ALL=1<<N;
    	for(int i=1;i<=N;i++) cin>>a[i],p[idx(i)]=a[i];
    	for(int S=1,i;i=S&(-S),S<ALL;S++)
    		p[S]=p[i]+p[S^i],cnt[S]=1+cnt[S^i];
    	for(int i=0;c[i][0]=1,i<=N;i++)
    		for(int j=1;j<=i;j++) 
    			c[i][j]=c[i-1][j-1]+c[i-1][j];
    	for(int i=K-1;h[i]=1,i>=0;i--)
    		for(int j=i+1;j<=K-1;j++)
    			h[i]-=h[j]*c[N-1-i][j-i];
    	cout<<fixed<<setprecision(7);
    	for(int i=1;i<=N;i++){
    		if(dcmp(a[i])==0||dcmp(1-a[i])==0||K==1)
    			cout<<p[idx(i)];
    		else{
    			double ans=0;
    			for(int S=0;S<ALL;S++){
    				if(idx(i)&S||cnt[S]>=K) continue;
    				ans+=h[cnt[S]]*1.0/(1-p[S]);
    			}
    			cout<<ans*p[idx(i)];
    		}
    		cout<<((i==N)?'
    ':' ');
    	}
    	return 0;
    }
    

      

  • 相关阅读:
    MySQL之架构与历史(二)
    MySQL之架构与历史(一)
    MySQL之体系结构与存储实例
    Redis实现之复制(二)
    Redis实现之复制(一)
    选项卡
    滑动效果
    选择器
    下拉列表
    1.__tostring()这个方法在类里可以直接输出对象。2.克隆对象的运用
  • 原文地址:https://www.cnblogs.com/zj75211/p/8543054.html
Copyright © 2020-2023  润新知