• ZOJ-3777-Problem Arrangement(状压DP)


    http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3777

    题意:

    输入n和m,接下来一个n*n的矩阵,a[i][j]表示第i道题放在第j个顺序做可以加a[i][j]的分数,问做完n道题所得分数大于等于m的概率。用分数表示,分母为上述满足题意的方案数,分子是总的方案数,输出最简形式。

    分析:

    一开始用的DFS做的,复杂度太高,果断超时。。

    正解是状压DP:

    由于总的方案数为n! ,简化为求给一个n*n的矩阵,每一行每一列各选一个数使得n个数之和大于等于m的方案数。

    n的范围是1 <= n <= 12,每一列选与不选分别用1和0表示,状态数最多达到1<<12。

    dp[sta][score]表示状态为i得分为j的方案数。

    sta通过二进制上1的位置来表示哪几列被选了。

    在当前状态下,对于某一列j,若sta&(1<<j) == 0说明第j列还未选,继而可以由第j列来更新,否则说明第j列已经被选。

    状态转移方程为:

  •                        dp[sta][g ] = sum(dp[i][g-a[ cnt+1][j]]);

    其中i当前状态的可行的上一状态,cnt为当前sta选到了第几行。

    由于从当前状态寻找上一状态很困难,所以我们反着来,用上一状态更新它可达的下一状态(具体看代码)

    最后dp[ (1<<n) -1 ][ m ]表示每行每列各取一个数,最后取n个数并得分大于等于m的方案数。(将动态转移方程变化了一下)

    说的不是很明白,代码写的很清楚。

    代码:

      1 #include <stdio.h>
      2 #include <string.h>
      3 #include <algorithm>
      4 using namespace std;
      5 
      6 int dp[1<<12][510];
      7 int f[13];
      8 int a[13][13];
      9 
     10 int gcd(int a, int b)
     11 {
     12     if(b == 0) return a;
     13     return gcd(b,a%b);
     14 }
     15 
     16 int main()
     17 {
     18     int test;
     19     int n,m;
     20 
     21     f[0] = 1;
     22     for(int i = 1; i <= 12; i++)
     23         f[i] = f[i-1] * i;
     24 
     25     scanf("%d",&test);
     26     while(test--)
     27     {
     28         scanf("%d %d",&n,&m);
     29         for(int i = 1; i <= n; i++)
     30             for(int j = 1; j <= n; j++)
     31                 scanf("%d",&a[i][j]);
     32 
     33         for(int i = 0; i < (1<<n); i++)
     34             for(int j = 0; j <= m; j++)
     35                 dp[i][j] = 0;
     36         dp[0][0] = 1;
     37 
     38         for(int i = 0; i < (1<<n); i++)
     39         {
     40             int cnt = 0;
     41             for(int j = 1; j <= n; j++)
     42             {
     43                 if(i & (1<<(j-1)) )
     44                     cnt++;
     45             }
     46 
     47             for(int j = 1; j <= n; j++)
     48             {
     49                 if(i & (1<<(j-1))) continue;
     50 
     51                 for(int g = 0; g <= m; g++)
     52                 {
     53                     if(g + a[cnt+1][j] >= m)
     54                         dp[i+(1<<(j-1))][m] += dp[i][g];
     55                     else
     56                         dp[i+(1<<(j-1))][g+a[cnt+1][j]] += dp[i][g];
     57                 }
     58             }
     59         }
     60         if(dp[(1<<n)-1][m] == 0)
     61             printf("No solution
    ");
     62         else
     63         {
     64             int tmp = gcd(f[n],dp[(1<<n)-1][m]);
     65             printf("%d/%d
    ",f[n]/tmp, dp[(1<<n)-1][m]/tmp);
     66         }
     67     }
     68 
     69     return 0;
     70 }
     
  • 相关阅读:
    【转】Geary's C
    ArcGIS中影像与影像,影像与点云之间的配准
    [转] EPSG CODE的含义
    地图投影
    【从翻译mos文章】oracle linux 和外部存储系统 关系
    找呀志_使用SQLiteDatabase增删改提供的搜索方法和事务
    [049] 微信公众平台视频公开课1说话-基础知识
    我看到西电通院考试——学生应该做的事情?
    使用 Eclipse 的 SVN 主要插件创建项目/支/标签
    【SSH三个框架】Hibernate第十篇基础:inverse属性具体解释
  • 原文地址:https://www.cnblogs.com/liuzhanshan/p/6685105.html
  • Copyright © 2020-2023  润新知