[LOJ#2540][PKUWC2018]随机算法(概率DP)

场上数据很水,比较暴力的做法都可以过90分以上,下面说几个做法。

1. 暴力枚举所有最大独立集,对每个独立集分别DP。复杂度玄学,但是由于最大独立集并不多,所以可以拿90.

2. dp[S][k]表示考虑到排列的第k位,当前独立集为S的方案数,枚举第k+1位,根据是否与S相连转移到dp[S][k+1]或dp[S | a[k+1]][k+1]。$O(n^22^n)$

3. dp[S]表示排列的状态为S时的正确率,mx[S]表示排列状态为S时能得到的最大独立集大小,考虑转移,枚举排列里最后一个在独立集中的点i∈S,从S中删去所有与i相连的点得到S‘,若mx[S]<mx[S‘]+1则更新mx[S],dp[S]清零,否则累加。注意到每个排列都是等概率出现的,所以最后直接除以|S|即可。 $O(n2^n)$

方法一:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define rep(i,l,r) for (int i=(l); i<=(r); i++)
 5 #define ll long long
 6 using namespace std;
 7
 8 const int N=1<<22,mod=998244353;
 9 ll n,m,x,y,s[25],p[25],f[N][25],cnt,mx,v[N],num[N],t[N],ans,o[N];
10
11 int main(){
12     freopen("walk.in","r",stdin);
13     freopen("walk.out","w",stdout);
14     scanf("%lld%lld",&n,&m);
15     p[1]=1; rep(i,2,n) p[i]=p[i-1]<<1;
16     rep(i,1,m) scanf("%lld%lld",&x,&y),s[x]|=p[y],s[y]|=p[x];
17     cnt=(1<<n)-1; f[0][0]=1;
18     rep(i,0,cnt){
19         ll tmp=0; v[i]=1;
20         rep(j,1,n) if ((i&p[j])&&(s[j]&i)) v[i]=0;
21         if (v[i]){
22             rep(j,1,n) if (i&p[j]) tmp++,t[i]|=s[j];
23             num[i]=tmp; mx=max(mx,tmp);
24             tmp=0;
25             rep(j,1,n) if (t[i]&p[j]) tmp++;
26             o[i]=tmp;
27         }
28     }
29     rep(i,0,cnt) if (v[i])
30         rep(j,0,o[i]){
31             if (j!=o[i]) f[i][j+1]=(f[i][j+1]+f[i][j]*(o[i]-j))%mod;
32             rep(k,1,n) if (!(i&p[k])&&!(p[k]&t[i])) f[i|p[k]][j]=(f[i|p[k]][j]+f[i][j])%mod;
33             if (num[i]==mx && j==o[i]) ans=(ans+f[i][j])%mod;
34         }
35       printf("%lld\n",ans);
36       return 0;
37 }

方法二:

 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cmath>
 4 #include<cstdlib>
 5 #include<cstring>
 6 #include<algorithm>
 7 using namespace std;
 8 int read()
 9 {
10     int x=0,f=1;char c=getchar();
11     while (c<‘0‘||c>‘9‘) {if (c==‘-‘) f=-1;c=getchar();}
12     while (c>=‘0‘&&c<=‘9‘) x=(x<<1)+(x<<3)+(c^48),c=getchar();
13     return x*f;
14 }
15 #define P 998244353
16 #define N 21
17 #define t (1<<n)
18 int n,m;
19 long long ans=0;
20 bool flag[1<<(N-1)];
21 int s[1<<(N-1)],w[N],v[1<<(N-1)],cnt[1<<(N-1)],tot[1<<(N-1)],f[21][1<<(N-1)],maximum=1;
22 int main()
23 {
24     freopen("walk.in","r",stdin);
25     freopen("walk.out","w",stdout);
26     n=read(),m=read();
27     for (int i=1;i<=n;i++) w[i]=1<<(i-1),s[w[i]]=w[i];
28     for (int i=1;i<=m;i++)
29     {
30         int x=read(),y=read();
31         s[w[x]]|=w[y],s[w[y]]|=w[x];
32     }
33     flag[0]=1;
34     for (int i=0;i<t;i++)
35     if (flag[i])
36         for (int j=1;j<=n;j++)
37         if (!(w[j]&s[i]))
38         {
39             flag[i|w[j]]=1,s[i|w[j]]=s[i]|s[w[j]],cnt[i|w[j]]=cnt[i]+1;
40             if (cnt[i]>=maximum) maximum=cnt[i|w[j]];
41         }
42     for (int i=0;i<t;i++)
43     {
44         s[i]=(~s[i])&(t-1);
45         register int k=s[i];
46         while (k) k^=k&-k,tot[i]++;
47         v[i]=i&-i;
48     }
49     f[0][0]=1;
50     for (register int i=0;i<n;i++)
51         for (register int j=0;j<t;j++)
52         if (f[i][j])
53         {
54             for (register int k=s[j];k;k^=v[k])
55             f[i+1][j|v[k]]=(f[i+1][j|v[k]]+f[i][j])%P;
56             f[i+1][j]=(1ll*f[i][j]*(n-i-tot[j])+f[i+1][j])%P;
57         }
58     for (int i=0;i<t;i++) if (cnt[i]==maximum) ans=(ans+f[n][i])%P;
59     cout<<ans;
60     fclose(stdin);fclose(stdout);
61     return 0;
62 }

方法三:

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #define rep(i,l,r) for (int i=l; i<=r; i++)
 5 typedef long long ll;
 6 using namespace std;
 7
 8 const int N=21,mod=998244353;
 9 int n,m,x,y,inv[N],f[N],mx[1<<N],F[1<<N];
10
11 int main(){
12     scanf("%d%d",&n,&m);
13     rep(i,1,m) scanf("%d%d",&x,&y),x--,y--,f[x]|=1<<y,f[y]|=1<<x;
14     inv[1]=1; f[0]|=1; F[0]=1;
15     rep(i,2,n) f[i-1]|=(1<<(i-1)),inv[i]=1ll*inv[mod%i]*(mod-mod/i)%mod;
16     for (int i=1; i<(1<<n); i++){
17         int tot=0;
18         for (int j=0; j<n; j++) if (i&(1<<j)){
19             int s=i&(~f[j]);
20             if (mx[i]<mx[s]+1) mx[i]=mx[s]+1,F[i]=0;
21             if (mx[i]==mx[s]+1) F[i]=(F[i]+F[s])%mod;
22             tot++;
23         }
24         F[i]=1ll*F[i]*inv[tot]%mod;
25     }
26     printf("%d\n",F[(1<<n)-1]);
27     return 0;
28 }

原文地址:https://www.cnblogs.com/HocRiser/p/9059172.html

时间: 2024-10-11 20:30:07

[LOJ#2540][PKUWC2018]随机算法(概率DP)的相关文章

LOJ2540 [PKUWC2018] 随机算法 【状压DP】

题目分析: 听说这题考场上能被$ O(4^n) $的暴力水过,难不成出题人是毕姥爷? 首先思考一个显而易见的$ O(n^2*2^n) $的暴力DP.一般的DP都是考虑最近的加入了哪个点,然后删除后递归进行状压DP.由于这道题的题目询问方式是反过来的,处理方式也反过来. 令$ f[n][S] $表示当前有$ S $这些点,期望这些点能够构成独立集大小为$ n $.正向的考虑选择了哪个点,并把与这个点有连边的所有点在集合内进行删除,令找到的新状态为$ f[n-1][P] $.我们把$ P $中的结点

[BZOJ5006][LOJ#2290][THUWC2017]随机二分图(概率+状压DP)

https://loj.ac/problem/2290 题解:https://blog.csdn.net/Vectorxj/article/details/78905660 不是很好理解,对于边(x1,y1)和(x2,y2),可以分“x1或y1已匹配”,“x2或y2已匹配”,“x1,x2,y1,y2均未匹配”三种情况考虑拆边的正确性. 状压的时候,对于当前左边已经匹配的集合,只需要枚举左边已匹配的最后一个是用哪条边匹配的即可,也就是程序里的S<(1<<T). 不要用顺推,记忆化搜索会忽略

PKUWC2018 随机算法

给你一个$n$个点$m$条边的无向图,执行如下算法: 1.随机一个$1~n$的排列$P$ 2.从$P$中按顺序一个一个将点加进独立集$S$里,始终保证$S$是独立集(即如果当前点和当前集合里的某个点相邻,就不加了) 求最后得到的$S$是原图的一个最大独立集的概率 $50% n \leq 17$ $100% n \leq 20$ sol: 先考虑部分分吧 很暴力的状压dp,$F[S1][S2]$表示选了$S1$中的点,当前最大独立集是$S2$的方案数,最后除以$n!$就可以了 转移是枚举每个点,所

【算法导论学习-012】n个数随机等概率的抽样m个

算法法导论>P129页课后题5.3-7 suppose we want to create a random sample of the set {1,2,3,-,n}, thatis, an m-element subset S, where0≤m≤n, such that each m-subset is equally likely to be created. One waywould be to set A[i]=i for i=1,2,3,-,n, call RANDOMIZE-IN

Miller_Rabin算法(随机算法,判断一个数是否是素数)

1 const int S = 20;//随机算法判定次数,S越大,判错概率越小 2 LL pow_mod(LL a, LL b, LL mod) { // a^b%mod 3 LL ans = 1; 4 a = a % mod; 5 while(b) { 6 if(b & 1) { 7 ans = (ans * a) % mod; 8 } 9 a = ( a * a ) % mod; 10 b >>= 1; 11 } 12 return ans; 13 } 14 bool check

微信红包随机算法

最近看了一篇文章,讲微信红包随机算法的.感觉很不错,所以自己实现了下,并进行了简单测试. 算法 算法很简单,不是提前算好,而是抢红包时计算: 红包里的金额怎么算?为什么出现各个红包金额相差很大?答:随机,额度在0.01和剩余平均值*2之间. 实现 实现上述算法的逻辑主要是: public static double getRandomMoney(RedPackage _redPackage) { // remainSize 剩余的红包数量 // remainMoney 剩余的钱 if (_red

加权随机算法

加权随机算法一般应用在以下场景:有一个集合S,里面比如有A,B,C,D这四项.这时我们想随机从中抽取一项,但是抽取的概率不同,比如我们希望抽到A的概率是50%,抽到B和C的概率是20%,D的概率是10%.一般来说,我们可以给各项附一个权重,抽取的概率正比于这个权重.那么上述集合就成了: {A:5,B:2,C:2,D:1} 方法一: 扩展这个集合,使每一项出现的次数与其权重正相关.在上述例子这个集合扩展成:{A,A,A,A,A,B,B,C,C,D}然后就可以用均匀随机算法来从中选取. 好处:选取的

hdu 5001 walk 概率dp入门题

Description I used to think I could be anything, but now I know that I couldn't do anything. So I started traveling. The nation looks like a connected bidirectional graph, and I am randomly walking on it. It means when I am at node i, I will travel t

Codeforces Div.301D Bad Luck Island(概率dp+记忆化搜索)

一道概率dp问题. 题目链接:http://codeforces.com/contest/540/problem/D 题目大意:一个岛上有r个石头,s个剪子,p个布,他们之间随机挑出两个相遇,如果不是相同物种,就会有一个消失,分别求出最后这座岛上只剩下一个物种的概率. 我们用dp[i][j][k]来存储i个石头,j个剪刀,k个布时,某物种的存活概率,共dp三次,算出三个物种分别的概率. 首先,我们需要把对应想求的物种概率初始化,这里以石头为例,那么对于i从1到r,不难理解dp[i][0][0]=