ZOJ3777 Problem Arrangement 期望+状态压缩+meet in the middle中途相遇法

Problem Arrangement Time Limit: 2 Seconds        Memory Limit: 65536 KB

The 11th Zhejiang Provincial Collegiate Programming Contest is coming! As a problem setter, Edward is going to arrange the order of the problems. As we know, the arrangement will have a great effect on the result of the contest. For example, it will take more time to finish the first problem if the easiest problem hides in the middle of the problem list.

There are N problems in the contest. Certainly, it's not interesting if the problems are sorted in the order of increasing difficulty. Edward decides to arrange the problems in a different way. After a careful study, he found out that the i-th problem placed in the j-th position will add Pij points of "interesting value" to the contest.

Edward wrote a program which can generate a random permutation of the problems. If the total interesting value of a permutation is larger than or equal to M points, the permutation is acceptable. Edward wants to know the expected times of generation needed to obtain the first acceptable permutation.

Input

There are multiple test cases. The first line of input contains an integer T indicating the number of test cases. For each test case:

The first line contains two integers N (1 <= N <= 12) and M (1 <= M <= 500).

The next N lines, each line contains N integers. The j-th integer in the i-th line is Pij (0 <= Pij <= 100).

Output

For each test case, output the expected times in the form of irreducible fraction. An irreducible fraction is a fraction in which the numerator and denominator are positive integers and have no other common divisors than 1. If it is impossible to get an acceptable permutation, output "No solution" instead.

Sample Input

2
3 10
2 4 1
3 2 2
4 5 3
2 6
1 3
2 4

Sample Output

3/1
No solution

题意:比赛有n道题,每道题放在不同的位置有不同的价值,对于n道题的不同位置的方法,随机等概率选取一个排列,可以得到一个价值v,求选取到的v第一次大于等于m的选取次数的期望。

题解:n道题的不同排列个数为n!个,选取每个排列的概率均为1/n!,题目求的就是n!/s.(其中s为n!个的排列里,排列价值v大于等于m的排列个数,要问为什么代码后面有推导)

那么求s就是本题的重点了。

如果暴力的话,复杂度为O(n!*n*T),T为数据组数,训练的时候队友写的这个暴力TLE了。下面是我想到的方法。

12!暴力不行就只能考虑状态压缩了。

大体思路:对于n个题,先把前n/2道题放到n个位置的n/2个位置上,然后计算这个状态(这里的状态的定义为:选取了哪些位置。因为位置最多12个,压缩到一个12位二进制数即可,int)能够得到的不同价值并记录在vec1。然后对于后面的n-n/2个题做同样的事情,并把价值记录到另一个vec2(并排序),然后对于暴力枚举前n/2个题的位置的得分v,然后在剩下的位置记录里二分查找大于m-v的值有多少,累加就可以得到s(即中途相遇法)。

只能举例子才能明白呀:设n=4,n/2=2,对于4个位置压缩成一个整数(为了方便表示出来,位置的编号为0~n-1)

那么前2个题可以任意放到4个位置的其中任意2个上,共有一下几个情况:

0 0 1 1 = 3 (前2道题放在0号和1号位置)

0 1 0 1 = 5 (前2道题放在0号和2号位置)

0 1 1 0 = 6 (前2道题放在1号和2号位置)

1 0 0 1 = 9 (前2道题放在0号和3号位置)

1 0 1 0 = 10(前2道题放在1号和3号位置)

1 1 0 0 = 12(前2道题放在2号和3号位置)

看其中一种情况,0 0 1 1 ,位置为0号位置和1号位置,但是前2道题可以有两种方法:①第1题放在0号位置,第2题放在1号位置,得到价值v1;②第1题放在1号位置,第2题放在0号位置,得到价值v2。其实共有(n/2)!个不同的价值vi,这样会让前n/2道题在这n/2个位置上全排。

所以上面过后,vector<int> vec1[]里面存的应该是:

vec1[3]存2!个价值;

vec1[5]存2!个价值;

vec1[6]存2!个价值;

vec1[9]存2!个价值;

vec1[10]存2!个价值;

vec1[12]存2!个价值。

然后同样的方式求出vector<int> vec2[]存的东西:

0 0 1 1 = 3 (2道题放在0号和1号位置)

0 1 0 1 = 5 (后2道题放在0号和2号位置)

0 1 1 0 = 6 (后2道题放在1号和2号位置)

1 0 0 1 = 9 (后2道题放在0号和3号位置)

1 0 1 0 = 10(后2道题放在1号和3号位置)

1 1 0 0 = 12(后2道题放在2号和3号位置)

vec2[3]存2!个价值;

vec2[5]存2!个价值;

vec2[6]存2!个价值;

vec2[9]存2!个价值;

vec2[10]存2!个价值;

vec2[12]存2!个价值。

vec1[3]和vec2[3]里存的是不一样,这个应该明白的,因为vec1[3]里存的前2个题的排列得到的价值,而vec2[3]存的是后2个题的排列得到的价值。其他的同理。

有了vec1和vec2就可以求s了,看我们来怎么合并:

如果前一半n/2道题选了某一些位置,那么后面的n-n/2道题应该就要选剩下的位置,即两者的位置不能有冲突,比如前2道题选了0号位置和3号位置,那么后面2道题应该选1号和2号才行,而我们可以对前者位置的状态取反得到后者的位置状态(注意上面对状态的定义),比如

0 0 1 1 = 3  ----(位置取反,即0便1,1变0)--->1 1 0 0 = 9

vec1[3]                                                                  vec2[9];

所以我们枚举vec1[3]里面的2!个值,设为v,然后在对应的vec2[9]里通过二分的方式求得大于等于m-v的价值的个数,累加到答案里即可。

再比如:

1 0 1 0 = 10  ----(位置取反,即0便1,1变0)--->0 1 0 1 = 5

vec1[10]                                                                  vec2[5];

枚举vec1[10]里的值,然后在vec2[5]里二分查找即可。

单组数据复杂度大概为O((C(n,n/2)*(n/2)!+2^n) + (C(n,n/2)*(n/2)!+2^n) + (C(n,n/2)*(n/2)!+2^n)*log((n/2)!)),

表达能力有限,哪里有写错的或者不明白的菊苣多多提出来。

代码中题目的编号也是0~n-1的

/****************
*PID:zoj3777
*Auth:Jonariguez
*****************
*/
#include<stdio.h>
#include <string.h>
#include <vector>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=12;
int n,m,P[maxn][maxn];
int p[maxn+1];
vector<int> vec1[(1<<maxn)],vec2[(1<<maxn)];
int fact[30];

int gcd(int a,int b){
    return b==0?a:gcd(b,a%b);
}

int main()
{
    int i,j,T;
    fact[0]=1;
    for(i=1;i<=12;i++)
        fact[i]=fact[i-1]*i;
    scanf("%d",&T);
    while(T--){
        scanf("%d%d",&n,&m);
        for(i=0;i<n;i++)
            for(j=0;j<n;j++)
                scanf("%d",&P[i][j]);
        if(n==1){               //n=1要特判
            if(P[0][0]<m)
                puts("No solution");
            else printf("1/1\n");
            continue;
        }
        int mask,ALL=(1<<n)-1,cnt,sum;
        int n1=n/2;
        for(mask=1;mask<=ALL;mask++){   //暴力计算vec1[mask]
            cnt=0;sum=0;
            for(i=0;i<n;i++)
                if((mask>>i)&1) cnt++;
            if(cnt!=n1) continue;       //位置不是n1个则continue
            vec1[mask].clear();
            for(i=1;i<=n1;i++)
                p[i]=i;
            do{
                sum=0;
                j=1;
                for(i=0;i<n;i++){
                    if((mask>>i)&1){
                        sum+=P[p[j]-1][i];  //注意题目编号也是从0~n-1的
                        j++;
                    }
                }
                vec1[mask].push_back(sum);
            }while(next_permutation(p+1,p+n1+1));   //n/2道题在n/2个位置上的全排
        }
        int n2=n-n1;
        for(mask=1;mask<=ALL;mask++){   //暴力计算vec2[mask]
            cnt=0;sum=0;
            for(i=0;i<n;i++)
                if((mask>>i)&1) cnt++;
            if(cnt!=n2) continue;
            vec2[mask].clear();
            for(i=1;i<=n2;i++)
                p[i]=i;
            do{
                sum=0;
                j=1;
                for(i=0;i<n;i++){
                    if((mask>>i)&1){
                        sum+=P[p[j]+n1-1][i];   //注意这里要+n1
                        j++;
                    }
                }
                vec2[mask].push_back(sum);
            }while(next_permutation(p+1,p+n2+1));
            sort(vec2[mask].begin(),vec2[mask].end());  //后面要二分,所以这里要排序。
        }
        int res=0;
        for(mask=1;mask<=ALL;mask++){
            cnt=0;
            int t=0;
            for(i=0;i<n;i++){
                if((mask>>i)&1) cnt++;
                else t|=(1<<i);         //mask为vec1的状态,t为mask的反状态,即vec1[mask]-->vec2[t]
            }
            if(cnt!=n1) continue;
            for(i=0;i<vec1[mask].size();i++){
                int v=vec1[mask][i];
                res+=vec2[t].size()-(lower_bound(vec2[t].begin(),vec2[t].end(),m-v)-vec2[t].begin());
            }
        }
        if(!res)
            puts("No solution");
        else {
            int g=gcd(res,fact[n]);
            printf("%d/%d\n",fact[n]/g,res/g);
        }
    }
    return 0;
}

关于本题的期望公式结论的推导:

设随机产生一个排列,该排列的价值v大于等于m的概率为p。那么p=s/n!(s为符合价值v大于等于m的排列个数)

因为题目要求是第一次出现v大于等于m的次数期望,我们来讨论次数:

假如随机产生1次,就得到价值v>=m的概率为:p

假如随机产生2次,就得到价值v>=m的概率为:(1-p) *p

假如随机产生3次,就得到价值v>=m的概率为:(1-p)^2 *p

假如随机产生4次,就得到价值v>=m的概率为:(1-p)^3 *p

假如随机产生5次,就得到价值v>=m的概率为:(1-p)^4 *p

...............

设期望为E,则

          E     = 1*p + 2*(1-p)*p + 3*(1-p)^2 *p + 4*(1-p)^3 *p +....             (期望=变量取值*对应的概率)

          E/p  =  1   +   2*(1-p)  +   3*(1-p)^2  +   4*(1-p)^3 + .....             (两边同时除以p)

(1-p)*(E/p)=             (1-p)     +   2*(1-p)^2  +   3*(1-p)^3 + ......            (两边同时乘以(1-p))

p*(E/p)=E= 1     +    (1-p)    +     (1-p)^2    +    (1-p)^3+....        =1/p          (两式相减,等比数列求和)

所以:E=1/p=n!/s.

你可能感兴趣的:(ZOJ)