zoj3777 Problem Arrangement 状态压缩+动态规划

题目链接在这里

题目大意

有N(N <= 12)道题,第i个解决的问题是题目j的话,就会得到arr[i][j]分。问解决完所有问题之后,得分能超过M的概率有多少。其中分子是有多少种解决问题的方式,分母是得分大于等于M的方案数。

思路分析

思路来自另一位博主,我讲得不是很清楚,可以看他的。链接

N道题一共有N!种排序,也就是有N!种解决方案,所以分子的问题我们已经解决了。

我们考虑dp[i][j]代表做了i种题(比如做了1、3、5题)之后得分为j的方案数。对于i,因为N <= 12,每道题都有做/未做两种状态,所以共有1 << 12种状态,每一种状态对应着一个i。

我们用cnt描述现在已经做了多少题。

比如说我现在做了1、3、5题,想做第2题了。因为第2题是第4个要做的题,所以我们就知道可以得到arr[4][2](C语言里要减一,因为输入从0开始)。那么i = 1<<1 + 1<<3 + 1<<5 ,那么dp[ i  + 1 << 2 ] [ j + arr [ 4 ] [ 2 ] ] += dp[ i ][ j ]。为了方便计数,如果j + arr[4][2] >= m的话,我们就直接按m处理就行了。

最后取出dp[1<<n - 1][m]就行了。

代码如下

#include <cstdio>
#include <cstring>
#include <string>
#include <iostream>
#include <algorithm>
#define clr(x) memset(x, 0, sizeof(x))
#define rep(i, n) for(int i = 0; i < n; ++i)
using namespace std;
const int MaxM = 510;
const int MaxN = 12;
int t;
int n, m;
int dp[1 << MaxN][MaxM];
int arr[MaxN][MaxN];

int f[MaxN + 1];

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

void solve(){
    clr(dp);
    dp[0][0] = 1;   //一个没选,得分为0的情况有1个
    rep(i, (1 << n)){
        int cnt = 0;
        rep(j, n)
            if(i & (1 << j)) ++cnt; //计数,算目前已经做了多少题

        rep(j, n){
            if(i & (1 << j))    continue;   //如果这道题已经做过了,就略过
            for(int g = 0; g <= m; ++g){    //g代表已经得到了多少分
                if(g + arr[cnt][j] >= m)    //如果g加上做完这道题得到的分数大于m了,就直接按m算。方便最后计算结果
                    dp[i + (1 << j)][m] += dp[i][g];
                else
                    dp[i + (1 << j)][g + arr[cnt][j]] += dp[i][g];
            }
        }
    }
    if(dp[(1 << n) - 1][m]){
        int a = dp[(1 << n) - 1][m];
        int b = f[n];
        int tmp = gcd(a, b);
        cout << b / tmp << "/" << a / tmp << endl;
    }
    else
        cout << "No solution" << endl;
}

int main(){
    ios::sync_with_stdio(false);
    f[0] = 1;
    for(int i = 1; i <= MaxN; ++i)
        f[i] = f[i - 1] * i;    //计算阶乘

    cin >> t;
    while(t--){
        cin >> n >> m;
        rep(i, n){
            rep(j, n){
                cin >> arr[i][j];
            }
        }

        solve();
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值