【Codeforces Round 275 (Div 2)E】【状压DP 概率DP求期望 线性相加思想 二进制系统函数】Game with Strings n个串目标串随机猜位置随机的猜出答案的期望步数
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre(){freopen("c://test//input.in","r",stdin);freopen("c://test//output.out","w",stdout);}
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1,class T2>inline void gmax(T1 &a,T2 b){if(b>a)a=b;}
template <class T1,class T2>inline void gmin(T1 &a,T2 b){if(b<a)a=b;}
const int N=50+2,M=20+2,S=1<<20,Z=1e9+7,ms63=1061109567;
int n,m;
LL b[N]; //b[i]表示:1<<i
char s[N][M]; //s[][]用来存储字符串
LL amb[S]; //amb[sta]表示:位置选择的二进制表示sta时,有哪些串,是分辨不出来的
double c[M][M];
double p[M]; //p[i]表示:到达一个猜了i个位置状态的概率
void solve()
{
for(int i=0;i<=50;++i)b[i]=1ll<<i;
for(int i=0;i<n;++i)
{
for(int j=i+1;j<n;++j)
{
int sta=0;
for(int pos=0;pos<m;++pos)if(s[j][pos]==s[i][pos])sta|=b[pos];
amb[sta]|=b[i];
amb[sta]|=b[j];
}
}
int top=1<<m;
for(int sta=top;sta>=0;--sta)
{
for(int i=0;i<m;++i)if(sta&b[i])amb[sta^b[i]]|=amb[sta];
}
c[0][0]=1;
for(int i=1;i<=m;++i)
{
c[i][0]=1;
for(int j=1;j<=m;++j)
{
c[i][j]=c[i-1][j-1]+c[i-1][j];
}
}
for(int i=0;i<m;++i)p[i]=1.0/c[m][i];
double ans=0;
for(int sta=0;sta<top;++sta)
{
ans+=__builtin_popcountll(amb[sta])*p[__builtin_popcountll(sta)];
}
printf("%.15f\n",ans/n);
}
int main()
{
while(~scanf("%d",&n))
{
for(int i=0;i<n;++i)scanf("%s",s[i]);m=strlen(s[0]);
solve();
}
return 0;
}
/*
【trick&&吐槽】
这种DP还真是难呀。
要不停尝试,横着来,横着不行竖着来,竖着不行再横着。
不断来回倒腾,才能最终AC这道题~啦啦啦。
【题意】
有n(1<=n<=50)个串,串由小写字符或大写字符构成。串的长度在[1,20]之间。
我们知道所有的串,而我的朋友等概率随机选取了其中一个串。
我们想去猜,朋友选取的串是哪个串。
于是我们每次会随机猜一个之前没有猜过的位置,然后朋友会告诉我,它选取的串的这个位置的字符是什么。
如果在某个时刻,备选的串变成了唯一。那么我们就猜出了答案。
现在问你,我们猜出目标串的步数的期望是多少。
【类型】
状压DP 期望DP
【分析】
这道题,首先有一个——"猜出的位置是影响问题的因素,位置之间不需要考虑先后顺序"的的性质。
于是,我们可以考虑用二进制表示哪些位置选取了,这里的时间复杂度是2^m,最大只有2^20,约为1e6.
然后,之后我们的复杂度,最大是n*2^m或者m*2^m。否则就会TLE。
首先,我们想,我们大概可以枚举目标串的。
如果知道了目标串,我们就可以知道,选取的位置为sta的时候,我们是否已经可以确定答案。具体做法是——
用f[i][sta]表示,目标串是i号串,位置状态是sta的情况下,合法串的集合。
之前先做一个预处理,用w[i][j]表示,如果第i个串是目标串,那么在位置j处与其有相同字符的串有哪些(用二进制LL表示)
这个预处理可以用O(n * m * n)的时间复杂度暴力得到。也可以通过 dp[位置][~字符~]=对应满足要求的字符串集合LL的方式,用O(n*m)的时间复杂度得到。
for(目标串)
{
for(当前位置状态)
{
找到任意一个可以拓展到它的状态,可以预处理"最后一个1的位置",也可以用系统函数中的__builtin_ctz()求出,记做pos
然后就可以有这样一个状态转移:
f[目标串][当前位置状态]=(f[目标串][上一个位置状态]&w[目标串][pos]);
设()内的值为val,它表明,对于此目标串,位置状态是sta的情况下,合法串的集合
然后,如果val==目标串的二进制表示,或者__builtin_popcountll(val)==1,代表我们可以确定目标串了。
我们把所有(目标串,位置状态)确定的目标串,存放在这个位置状态中,即|进fix[sta]。以进行接下来的DP转移。
}
}
然后我们可以开始DP了——
用p[sta]表示,达到位置状态sta的概率。
for(位置状态)
{
得到现在确定的串
for(下一个位置)
{
得到下一个位置状态与下一个状态可以确定的串
(现在确定的串的集合)^(接下来确定的串的集合)= 上一步并没有确定,这一步确定的串的集合
我们现在可以得到:1,走到这一步决策的概率;2,步数;3,这一步所确定的串的数量。
把位置概率*当步确定的串的数量*步数 统计到一个全局的ans中,最后的ans就是期望的步数。然后这道题就做完啦!
}
}
=================================================
我们再看看别人是如何做这道题的。
1,对于两个字符串(i,j),我们求出字符串j的哪些位置与字符串i是相同的,存在一个二进制中。
这个二进制再存下在这个二进制状态下可能会产生不确定关系的所有字符串的二进制表示。
2,从大到小枚举位置状态,再枚举这个状态中有的位置。我们就可以传递不确定关系。
可以知道某个位置状态下,可能是哪些字符串。
3,从小到大枚举所有状态,得到这个状态已经选取了哪些位置。
对于这个状态的所有后继状态,积累达到下一个状态的概率
对于这个状态还不确定的位置,我们便要增加一次猜数,积累概率*步长即可。
我们发现——
其实,对于每个状态,只要积累到达这个状态的概率*这个状态不确定字符串的个数。最后/n就是答案。
【时间复杂度&&优化】
O((n+m)*2^m)
*/
【Codeforces Round 275 (Div 2)E】【状压DP 概率DP求期望 二进制系统函数 旧写法】Game with Strings n个串目标串随机猜位置随机的猜出答案的期望步数
#include<stdio.h>
#include<iostream>
#include<string.h>
#include<string>
#include<ctype.h>
#include<math.h>
#include<set>
#include<map>
#include<vector>
#include<queue>
#include<bitset>
#include<algorithm>
#include<time.h>
using namespace std;
void fre(){freopen("c://test//input.in","r",stdin);freopen("c://test//output.out","w",stdout);}
#define MS(x,y) memset(x,y,sizeof(x))
#define MC(x,y) memcpy(x,y,sizeof(x))
#define MP(x,y) make_pair(x,y)
#define ls o<<1
#define rs o<<1|1
typedef long long LL;
typedef unsigned long long UL;
typedef unsigned int UI;
template <class T1,class T2>inline void gmax(T1 &a,T2 b){if(b>a)a=b;}
template <class T1,class T2>inline void gmin(T1 &a,T2 b){if(b<a)a=b;}
const int N=50+2,M=50+2,S=1<<20,Z=1e9+7,ms63=1061109567;
int n,m;
LL b[N]; //b[i]表示:1<<i
char s[N][M]; //s[][]用来存储字符串
LL w[N][M]; //w[i][j]表示:在位置j处的字符,与i号串相同的字符串的集合
int num1[S]; //num1[sta]表示:二进制状态表示为sta的数中有多少个1,相当于__builtin_popcountll(sta)
int lst1[S]; //lst1[sta]表示:二进制状态表示为sta的数中,最后一个1的位置,相当于__builtin_ctz(sta);
LL fix[S]; //fix[sta]表示:位置选择的二进制表示sta时,有哪些串,已经能够分辨出来了
LL f[S]; //f([i])[sta]表示:当目标串为i,位置选择的二进制表示为sta时,有哪些串,依然与目标串一致。
double p[S]; //p[sta]表示:达到位置选择的二进制表示为sta下的概率
void solve()
{
for(int i=0;i<=50;++i)b[i]=1ll<<i;
for(int pos=0;pos<m;++pos)
{
for(int i=0;i<n;++i)
{
w[i][pos]=0;
for(int j=0;j<n;++j)if(s[j][pos]==s[i][pos])w[i][pos]|=b[j];
}
}
int top=1<<m;
for(int sta=0;sta<top;++sta)
{
num1[sta]=0;
lst1[sta]=-1;
for(int x=sta,p=0;x;x>>=1,++p)
{
num1[sta]+=x&1;
if((x&1)&&lst1[sta]==-1)lst1[sta]=p;
}
}
MS(fix,0);
for(int i=0;i<n;++i)
{
f[0]=b[n]-1;
for(int sta=1;sta<top;++sta)
{
int pos=lst1[sta];
f[sta]=f[sta^b[pos]]&w[i][pos];
if(f[sta]==b[i])fix[sta]|=b[i];
}
}
MS(p,0);p[0]=1;
double ans=0;
for(int sta=0;sta<top-1;++sta)
{
double prb=p[sta]/(m-num1[sta]);//!小心除0!
for(int i=0;i<m;++i)if(!(sta&b[i]))
{
int nxt=sta|b[i];p[nxt]+=prb;
ans+=prb*__builtin_popcountll(fix[nxt]^fix[sta])*num1[nxt];
//目标期望=∑概率 * 确定数的个数 * 确定数的步长
}
}
if(n==1)ans=0;//!小心把边界错误特判掉!
printf("%.15f\n",ans/n);
}
int main()
{
while(~scanf("%d",&n))
{
for(int i=0;i<n;++i)scanf("%s",s[i]);m=strlen(s[0]);
solve();
}
return 0;
}
/*
【trick&&吐槽】
这种DP还真是难呀。
要不停尝试,横着来,横着不行竖着来,竖着不行再横着。
不断来回倒腾,才能最终AC这道题~啦啦啦。
不要忘记避免除法0
不要忘记边界特判
【题意】
有n(1<=n<=50)个串,串由小写字符或大写字符构成。串的长度在[1,20]之间。
我们知道所有的串,而我的朋友等概率随机选取了其中一个串。
我们想去猜,朋友选取的串是哪个串。
于是我们每次会随机猜一个之前没有猜过的位置,然后朋友会告诉我,它选取的串的这个位置的字符是什么。
如果在某个时刻,备选的串变成了唯一。那么我们就猜出了答案。
现在问你,我们猜出目标串的步数的期望是多少。
【类型】
状压DP 期望DP
【分析】
这道题,首先有一个——"猜出的位置是影响问题的因素,位置之间不需要考虑先后顺序"的的性质。
于是,我们可以考虑用二进制表示哪些位置选取了,这里的时间复杂度是2^m,最大只有2^20,约为1e6.
然后,之后我们的复杂度,最大是n*2^m或者m*2^m。否则就会TLE。
首先,我们想,我们大概可以枚举目标串的。
如果知道了目标串,我们就可以知道,选取的位置为sta的时候,我们是否已经可以确定答案。具体做法是——
用f[i][sta]表示,目标串是i号串,位置状态是sta的情况下,合法串的集合。
之前先做一个预处理,用w[i][j]表示,如果第i个串是目标串,那么在位置j处与其有相同字符的串有哪些(用二进制LL表示)
这个预处理可以用O(n * m * n)的时间复杂度暴力得到。也可以通过 dp[位置][~字符~]=对应满足要求的字符串集合LL的方式,用O(n*m)的时间复杂度得到。
for(目标串)
{
for(当前位置状态)
{
找到任意一个可以拓展到它的状态,可以预处理"最后一个1的位置",也可以用系统函数中的__builtin_ctz()求出,记做pos
然后就可以有这样一个状态转移:
f[目标串][当前位置状态]=(f[目标串][上一个位置状态]&w[目标串][pos]);
设()内的值为val,它表明,对于此目标串,位置状态是sta的情况下,合法串的集合
然后,如果val==目标串的二进制表示,或者__builtin_popcountll(val)==1,代表我们可以确定目标串了。
我们把所有(目标串,位置状态)确定的目标串,存放在这个位置状态中,即|进fix[sta]。以进行接下来的DP转移。
}
}
然后我们可以开始DP了——
用p[sta]表示,达到位置状态sta的概率。
for(位置状态)
{
得到现在确定的串
for(下一个位置)
{
得到下一个位置状态与下一个状态可以确定的串
(现在确定的串的集合)^(接下来确定的串的集合)= 上一步并没有确定,这一步确定的串的集合
我们现在可以得到:1,走到这一步决策的概率;2,步数;3,这一步所确定的串的数量。
把位置概率*当步确定的串的数量*步数 统计到一个全局的ans中,最后的ans就是期望的步数。然后这道题就做完啦!
}
}
=================================================
我们再看看别人是如何做这道题的。
1,对于两个字符串(i,j),我们求出字符串j的哪些位置与字符串i是相同的,存在一个二进制中。
这个二进制再存下在这个二进制状态下可能会产生不确定关系的所有字符串的二进制表示。
2,从大到小枚举位置状态,再枚举这个状态中有的位置。我们就可以传递不确定关系。
可以知道某个位置状态下,可能是哪些字符串。
3,从小到大枚举所有状态,得到这个状态已经选取了哪些位置。
对于这个状态的所有后继状态,积累达到下一个状态的概率
对于这个状态还不确定的位置,我们便要增加一次猜数,积累概率*步长即可。
我们发现——
其实,对于每个状态,只要积累到达这个状态的概率*这个状态不确定字符串的个数。最后/n就是答案。
【时间复杂度&&优化】
O((n+m)*2^m)
*/