CCSP2019T2_纸牌计数 | 2019苏州CCSP大学生计算机系统与程序设计竞赛

Posted Cwolf9

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CCSP2019T2_纸牌计数 | 2019苏州CCSP大学生计算机系统与程序设计竞赛相关的知识,希望对你有一定的参考价值。

题目描述

偶然在CSDN看到有人写了CCSP2019T2_纸牌计数的题解,突然想起来是一个不错的计数、dp题。
以前的U盘找不到了,记得当时存了一步步偏分到AC代码,可惜。又想起来18年打铁了。。。
此人的题解的链接 CCSP201902纸牌计数——解题报告
当年一共有5题,取自:https://www.sohu.com/a/347851686_610300
T1: 摘水果 fruit
T2:纸牌计数 card
T3:系统实现题 SQL 查询
T4:系统策略题 调度器 scheduler
T5:系统结构体 评测鱼 risc-v
T2的题面:

Description
我们有一副纸牌,它由 n 张牌组成,其中每张牌上标有一个数字(0 到 9)和一个大写字母(A 到 Z),例如 2C、1F。

我们如下定义一个字符串与一张牌之间的匹配关系:
字符串 ?? 与任何一张牌都匹配。
第一位为 ? 而第二位为字母的字符串,与任何一张标有该字母的牌匹配。例如,字符串 ?C 与任何标有 C 的牌匹配。
第一位为数字而第二位为字母的字符串,仅与内容完全一致的牌匹配。例如,字符串 0C 与内容为 0C 的牌匹配。
不会出现第一位为数字而第二位为 ? 的字符串。
我们称 m 个字符串 S1 ... Sm 与 m 张牌 C1 ... Cm 是匹配的,当且仅当:存在集合  1 , … , m  上的一一映射 σ,使得对于任意 1 ≤ i ≤ m ,Si 与 C_σ(i) 匹配。

例如,对于 ?? 和 ?C 这两个字符串,可以匹配内容为 1C 和 2C 的牌,因为字符串 ?? 与内容为 2C 的牌匹配且字符串 ?C 与内容为 1C 的牌匹配,而 ?? 和 ?C 这两个字符串不能匹配内容为 1S 和 1P 的牌。

现在,给定 m 个字符串,你需要求解:如果在 n 张牌中(无放回地)随机选取 m 张,有多大概率与这些字符串匹配?

Input
每个输入文件包含多个输入数据。
第一行输入该文件的数据个数 T。
接下来依次输入每个数据。每个数据包含三行,其中:
第一行输入两个正整数 n 和 m;
第二行输入以空格分隔的 n 个字符串,表示每张纸牌的内容(每个字符串第一位为数字,第二位为大写字母);
第三行输入以空格分隔的 m 个字符串,表示每个需要匹配的字符串(每个字符串第一位为数字,第二位为大写字母;或第一位为 ?,第二位为大写字母;或为 ??)。

Output
对于每个输入数据,输出一行,表示所求的概率。概率需要以最简分数 u/v 的形式输出,其中 0 ≤ u ≤ v 且 u, v 为互质的整数。
特殊地,对于 0 请输出 0/1,对于 1 请输出 1/1。

Subtasks
对于所有的数据,1 <= m <= n <= 60, T <= 1000, $1 \\leq m \\leq n \\leq 60; T \\leq 1000$。
(11分)m=1;
(23分)m=n;
(27分)n=30 且所有的牌为 0C 1C 2C 3C 4C 5C 6C 7C 8C 9C 0S 1S 2S 3S 4S 5S 6S 7S 8S 9S 0P 1P 2P 3P 4P 5P 6P 7P 8P 9P;
(22分)n=40 且所有的牌为 0C 1C 2C 3C 4C 5C 6C 7C 8C 9C 0C 1C 2C 3C 4C 5C 6C 7C 8C 9C 0S 1S 2S 3S 4S 5S 6S 7S 8S 9S 0P 1P 2P 3P 4P 5P 6P 7P 8P 9P;
(17分)没有特殊的限制。

题解

一时间不记得咋写的了,按记忆口胡了一个做法,感觉复杂度有点假,但好像跑不满,以后再看看吧:

  • 求概率/期望的时候,两个独立事件(即正交的情况)可以分开考虑,答案为每个事件概率的乘积。类似于积性函数中的一些理念。
  • n张牌的字母都是固定的,一般m个字符串的字母也是固定的(除非是两个问号??)。
  • 考虑是否可以先分26个字母考虑,再考虑??。
  • rdp[j][k] 表示考虑到数字j用了k个问号的(不)合法情况数(合不合法,即是否存在重复计数的),把单个?的情况用组合数计数分配一下
  • 现在 ?? 还没处理吧,所以??可以在这个层次上再套一层dp
  • val[i][w] 表示考虑到字母i已经用了w个??的合法方案数,最后背包dp一遍求答案
  • (不确定有没有爆 long long,感觉复杂度可以减掉一个60?
#include <bits/stdc++.h>
#define fi first
#define se second
#define o2(x) (x) * (x)
#define mk make_pair
#define eb emplace_back
#define SZ(x) ((int)(x).size())
#define all(x) (x).begin(), (x).end()
#define clr(a, b) memset((a), (b), sizeof((a)))
#define rep(i, s, t) for(register int i = (s), LIM=(t); i < LIM; ++i)
#define per(i, s, t) for(register int i = (s), LIM=(t); i > LIM; --i)
#define GKD std::ios::sync_with_stdio(false);cin.tie(0)
#define my_unique(x) sort(all(x)), x.erase(unique(all(x)), x.end())
using namespace std;
typedef long long LL;
typedef long long int64;
typedef unsigned long long uint64;
typedef pair<int, int> pii;
// mt19937 rng(time(NULL));//std::clock()
// mt19937_64 rng64(chrono::steady_clock::now().time_since_epoch().count());
// shuffle(arr, arr + n, rng64);
inline int64 read() 
    int64 x = 0;int las = 0;char ch = getchar();
    while (ch < \'0\' || ch > \'9\') las |= (ch == \'-\'), ch = getchar();
    while (ch >= \'0\' && ch <= \'9\') x = (x << 3) + (x << 1) + ch - \'0\', ch =
    getchar(); return x = las ? -x : x;

inline void write(int64 x, bool las = true) 
    if (x == 0) putchar(\'0\'); if(las)putchar(\'\\n\');else putchar(\' \');return;
    if (x < 0) putchar(\'-\');x = -x;
    static char s[23];
    int l = 0;
    while (x != 0)s[l++] = x % 10 + 48, x /= 10;
    while (l)putchar(s[--l]);
    if(las)putchar(\'\\n\');else putchar(\' \');

int lowbit(int x)  return x & (-x); 
template <class T>
T big(const T &a1, const T &a2) return a1 > a2 ? a1 : a2;
template <class T>
T sml(const T &a1, const T &a2) return a1 < a2 ? a1 : a2;
template <typename T, typename... R>
T big(const T &las, const R &... r) return big(las, big(r...));
template <typename T, typename... R>
T sml(const T &las, const R &... r) return sml(las, sml(r...));
void debug_out()  cout << \'\\n\'; 
template <typename T, typename... R>
void debug_out(const T &las, const R &... r) 
    cout << las << " ";
    debug_out(r...);

#ifdef LH_LOCAL
#define debug(...) cout << "[" << #__VA_ARGS__ << "]: ", debug_out(__VA_ARGS__);
#else
#define debug(...) ;
#endif
#define debug(...) ;
/*================Header Template==============*/
const int mod = 998244353;// 998244353
int ksm(int a, int64 b, int kmod = mod) int res = 1;for(;b > 0;b >>= 1, a = (int64)a * a % kmod) if(b &1) res = (int64)res * a % kmod;return res;
const int INF = 0x3f3f3f3f;
const int MXN = 2e5 + 5;
const int MXM = 5e6 + 7;
int n, m;
char card[65][3], str[65][3];
// 0 <= . <= 9, A <= . <= Z  -> 1 <= . <= 10
int cnt[30][15], need[30][15];
int sum[2][30];
LL fdp[30], rdp[15][65], val[30][65], dp[30][65];

LL COMB(int n, int m) 
    if(n == m) return 1;
    if(n < m) return 0;
    LL res = 1;
    for(int i = 0; i < m; ++i) 
        res *= (n - i);
        res /= i + 1;
    
    return res;


void work() 
    n = read(), m = read();
    for(int i = 1; i <= n; ++i) scanf("%s", card[i]);
    for(int i = 1; i <= m; ++i) scanf("%s", str[i]);
    int flag = 1;
    for(int i = 1; i <= n; ++i) 
        cnt[card[i][1] - \'A\'][card[i][0] - \'0\' + 1] ++;
        sum[0][card[i][1] - \'A\'] ++;
    
    LL ww = m;
    for(int i = 1; i <= m; ++i) 
        if(str[i][1] != \'?\') 
            if(str[i][0] != \'?\') need[str[i][1] - \'A\'][str[i][0] - \'0\' + 1] ++;
            else need[str[i][1] - \'A\'][0] ++;
            sum[1][str[i][1] - \'A\'] ++;
            ww --;
        
    
    LL res = 1;
    for(int i = 0; i < 26; ++i) 
        if(sum[0][i] < sum[1][i]) 
            flag = 0;
            break;
        
        for(int w = 0; w <= ww; ++w) 
            need[i][0] += w;
            fdp[i] = 1;
            memset(rdp, 0, sizeof(rdp));
            rdp[0][0] = 1;
            // rdp[j][k] 表示考虑到数字j用了k个问号的(不)合法情况数(合不合法,即是否存在重复计数的)
            // 把单个?的情况用组合数计数分配一下
            // ?? 还没处理吧,所以??可以在这个层次上再套一层dp
            // val[i][w] 表示考虑到字母i已经用了w个??的合法方案数
            for(int j = 1; j <= 10; ++j) 
                if(cnt[i][j] < need[i][j]) 
                    flag = 0;
                    break;
                
                fdp[i] = fdp[i] * COMB(cnt[i][j], need[i][j]);
                // for(int k = 0; k <= need[i][0]; ++k) rdp[j][k] = rdp[j - 1][k];
                if(1 || cnt[i][j] > need[i][j] && need[i][j] > 0) 
                    for(int h = 0; h <= cnt[i][j] - need[i][j]; ++h) 
                        for(int k = h; k <= need[i][0]; ++k) 
                            rdp[j][k] = rdp[j][k] + rdp[j - 1][k - h] * COMB(cnt[i][j], need[i][j] + h);
                            // debug(h, j, k, rdp[j][k])
                        
                    
                    // if(i == \'C\' - \'A\') debug(j)
                else 
                    // if(i == \'C\' - \'A\') debug(j, rdp[j][1], rdp[j - 1][1])
                
            
            // rdp[10][0] = 0;
            fdp[i] = fdp[i] * COMB(sum[0][i] - (sum[1][i] - need[i][0]), need[i][0]);
            LL ret = rdp[10][need[i][0]];
            if(i == \'C\' - \'A\') debug(i, w, need[i][0], fdp[i], rdp[10][need[i][0]], fdp[i] - rdp[10][need[i][0]], ret, fdp[i] - ret)
            res *= (ret);
            need[i][0] -= w;
            val[i][w] = ret;
            if(i == \'C\' - \'A\' && val[i][w] > 1) debug(i, w, val[i][w])
        
    
    for(int j = 0; j <= ww; ++j) dp[0][j] = val[0][j];
    for(int i = 1; i < 26; ++i) 
        for(int j = 0; j <= ww; ++j) 
            for(int k = 0; k <= j; ++k) 
                dp[i][j] = dp[i][j] + dp[i - 1][j - k] * val[i][k];
            
        
    
    res = dp[25][ww];
    LL allcase = COMB(n, m);
    LL agcd = __gcd(allcase, res);
    debug(allcase, res, res / agcd, allcase / agcd)
    if(flag == 0) 
        printf("0/1\\n");
    else 
        printf("%lld/%lld\\n", res / agcd, allcase / agcd);
    
    // 不确定有没有爆 long long
    for(int i = 0; i < 26; ++i) 
        sum[0][i] = sum[1][i] = 0;
        for(int j = 0; j <= 10; ++j) 
            cnt[i][j] = need[i][j] = 0;
        
        for(int j = 0; j <= ww; ++j) 
            val[i][j] = 0;
            dp[i][j] = 0;
        
    


int main() 
#ifdef LH_LOCAL
    freopen("c:/lh/system-lab/acm_code/OJ/in.txt", "r", stdin);
    // freopen("c:/lh/system-lab/acm_code/OJ/out.txt", "w", stdout);
#endif
    for(int cas = 1, tim = (1 ? read(): 1); cas <= tim; ++ cas) 
        work();
    
    // 56160000
    debug(26*60*10*60*60)
#ifdef LH_LOCAL
    cout << "Debug log: time cost:" << 1.0 * clock() / CLOCKS_PER_SEC << "s" << endl;
#endif
    return 0;

/*
3
40 2
0C 1C 2C 3C 4C 5C 6C 7C 8C 9C 0C 1C 2C 3C 4C 5C 6C 7C 8C 9C 0S 1S 2S 3S 4S 5S 6S 7S 8S 9S 0P 1P 2P 3P 4P 5P 6P 7P 8P 9P
?C 1C
40 16
0C 1C 2C 3C 4C 5C 6C 7C 8C 9C 0C 1C 2C 3C 4C 5C 6C 7C 8C 9C 0S 1S 2S 3S 4S 5S 6S 7S 8S 9S 0P 1P 2P 3P 4P 5P 6P 7P 8P 9P
0C 0C 1C 2C ?C ?C ?C ?C 3S 4S ?S ?S 5P 6P ?P ?P
60 30
1A 1B 1C 1D 1E 1F 1G 1H 1I 1J 1K 1L 1M 1N 1O 1P 1Q 1R 1S 1T 1U 1V 1W 1X 1Y 1Z 0A 1A 2A 3A 4A 5A 6A 7A 8A 9A 1S 3S 5S 7S 9S 0U 2U 4U 6U 8U 2Z 3Z 5Z 7Z 1C 1C 1C 1C 1C 1C 1C 1C 1S 1P
1C 1C 1S 1P 2A 0A 1A 9A ?S ?U ?Z ?H ?O ?U ?? ?? ?? ?? ?? ?? ?S ?S ?S ?U ?U ?U ?Z ?Z ?C ?C

37/780
167384/2417388525  -> 4351984/x -> 5551 * 28 * 28
50442363273/29566145391215356 -> 201769453092/x

Sample
Input
15
6 1
0C 0S 0P 1C 1S 1P
1S
6 1
0C 0S 0P 1C 1S 1P
?C
6 2
0C 0S 0P 1C 1S 1P
?C ?C
6 2
0C 0S 0P 1C 1S 1P
?C ?S
6 4
0C 0S 0P 1C 1S 1P
?C ?C ?S ?P
6 4
0C 0S 0P 1C 1S 1P
0C ?C ?S ?P
6 4
0C 0S 0P 1C 1S 1P
?C ?C ?S ??
6 4
0C 0S 0P 1C 1S 1P
?C ?C ?? ??
6 4
0C 0S 0P 1C 1S 1P
?A ?? ?? ??
6 4
0C 0S 0P 1C 1S 1P
0C 0C ?S ?P
30 8
0C 1C 2C 3C 4C 5C 6C 7C 8C 9C 0S 1S 2S 3S 4S 5S 6S 7S 8S 9S 0P 1P 2P 3P 4P 5P 6P 7P 8P 9P
0C ?C ?C ?C 1S ?S 2P ?P
40 2
0C 1C 2C 3C 4C 5C 6C 7C 8C 9C 0C 1C 2C 3C 4C 5C 6C 7C 8C 9C 0S 1S 2S 3S 4S 5S 6S 7S 8S 9S 0P 1P 2P 3P 4P 5P 6P 7P 8P 9P
?C 1C
40 2
0C 1C 2C 3C 4C 5C 6C 7C 8C 9C 0C 1C 2C 3C 4C 5C 6C 7C 8C 9C 0S 1S 2S 3S 4S 5S 6S 7S 8S 9S 0P 1P 2P 3P 4P 5P 6P 7P 8P 9P
?C 1S
40 16
0C 1C 2C 3C 4C 5C 6C 7C 8C 9C 0C 1C 2C 3C 4C 5C 6C 7C 8C 9C 0S 1S 2S 3S 4S 5S 6S 7S 8S 9S 0P 1P 2P 3P 4P 5P 6P 7P 8P 9P
0C 0C 1C 2C ?C ?C ?C ?C 3S 4S ?S ?S 5P 6P ?P ?P
60 30
1A 1B 1C 1D 1E 1F 1G 1H 1I 1J 1K 1L 1M 1N 1O 1P 1Q 1R 1S 1T 1U 1V 1W 1X 1Y 1Z 0A 1A 2A 3A 4A 5A 6A 7A 8A 9A 1S 3S 5S 7S 9S 0U 2U 4U 6U 8U 2Z 3Z 5Z 7Z 1C 1C 1C 1C 1C 1C 1C 1C 1S 1P
1C 1C 1S 1P 2A 0A 1A 9A ?S ?U ?Z ?H ?O ?U ?? ?? ?? ?? ?? ?? ?S ?S ?S ?U ?U ?U ?Z ?Z ?C ?C
Output
1/6
1/3
1/15
4/15
4/15
4/15
1/3
2/5
0/1
0/1
252/216775
37/780
1/39
167384/2417388525
50442363273/29566145391215356

Hint
对于分数 a/b,设 g=gcd(a,b),那么其最简分数形式为 (a/g)/(b/g)。


*/

单人纸牌_NOI导刊2011提高(04)

题目描述

单人纸牌游戏,共36张牌分成9叠,每叠4张牌面向上。每次,游戏者可以从某两个不同的牌堆最顶上取出两张牌面相同的牌(如黑桃10和梅花10)并且一起拿走。如果最后所有纸牌都被取走,则游戏者就赢了,否则游戏者就输了。 

    George很热衷于玩这个游戏,但是一旦有时有多种选择的方法,George就不知道取哪一种好了,George会从中随机地选择一种走,例如:顶上的9张牌为KS,KH,KD,9H,8S,8D,7C,7D,6H,显然有5种取法:(KS,KH),(KS,KD),(KH,KD),(8S,8D),(7C,7D),当然George取到每一种取法的概率都是1/5。 

    有一次,George的朋友Andrew告诉他,这样做是很愚蠢的,不过George不相信,他认为如此玩最后成功的概率是非常大的。请写一个程序帮助George证明他的结论:计算按照他的策略,最后胜利的概率。 【输入数据】 

    9行,每行4组用空格分开的字串,每个字串两个字符,分别表示牌面和花色,按照从堆底到堆顶的顺序给出。

输入输出格式

输入格式:

  9行,每行4组用空格分开的字串,每个字串两个字符,分别表示牌面和花色,按照从堆底到堆顶的顺序给出。

输出格式:

一行,最后胜利的概率,精确到小数点后6位。

输入输出样例

输入样例#1:
AS 9S 6C KS 
JC QH AC KH 
7S QD JD KD 
QS TS JS 9H 
6D TD AD 8S 
QC TH KC 8D 
8C 9D TC 7C 
9C 7H JH 7D 
8H 6S AH 6H 
输出样例#1:
0.589314
概率dp.
f[][][][][][][][][]表示当前状态(每一堆的牌数)的概率
从f[0][0][0][0][0][0][0][0][0]=1开始,
每次在当前状态枚举往上放哪两张牌,方法数为cnt,当前概率为p
则把所有放上两张牌的状态f+=p/cnt;
为了方便,此处用数组循环。
由于状态太多,此处不好列出转移方程,请看代码。
 1 #include<iostream>
 2 #include<algorithm>
 3 #include<cstring>
 4 #include<cstdio>
 5 using namespace std;
 6 int x,y,cnt,i[11];
 7 double f[5][5][5][5][5][5][5][5][5];
 8 char a[1001][10];
 9 int main()
10 {
11     for (x=1;x<=9;x++)
12     {
13         char s[15];
14         gets(s+1);
15         a[x][1]=s[10];
16         a[x][2]=s[7];
17         a[x][3]=s[4];
18         a[x][4]=s[1];
19     }
20 f[0][0][0][0][0][0][0][0][0]=1;
21       for (i[1]=0;i[1]<=4;i[1]++)
22       {
23           for (i[2]=0;i[2]<=4;i[2]++)
24           {
25               for (i[3]=0;i[3]<=4;i[3]++)
26               {
27                   for (i[4]=0;i[4]<=4;i[4]++)
28                   {
29                       for (i[5]=0;i[5]<=4;i[5]++)
30                       {
31                           for (i[6]=0;i[6]<=4;i[6]++)
32                           {
33                               for (i[7]=0;i[7]<=4;i[7]++)
34                               {
35                                   for (i[8]=0;i[8]<=4;i[8]++)
36                                   {
37                                       for (i[9]=0;i[9]<=4;i[9]++)
38             if (f[i[1]][i[2]][i[3]][i[4]][i[5]][i[6]][i[7]][i[8]][i[9]])
39                                       {double p=f[i[1]][i[2]][i[3]][i[4]][i[5]][i[6]][i[7]][i[8]][i[9]];
40                                       cnt=0;
41                                           for (x=1;x<9;x++)
42                                            for (y=x+1;y<=9;y++)
43                                             if (i[x]+1<=4&&i[y]+1<=4&&a[x][i[x]+1]==a[y][i[y]+1])
44                                              cnt++;
45                                         if (cnt>0) 
46                                           for (x=1;x<9;x++)
47                                            for (y=x+1;y<=9;y++)
48                                             if (i[x]+1<=4&&i[y]+1<=4&&a[x][i[x]+1]==a[y][i[y]+1]) 
49                                             {
50                                                 i[x]++;i[y]++;
51            f[i[1]][i[2]][i[3]][i[4]][i[5]][i[6]][i[7]][i[8]][i[9]]+=p/(double)(cnt*1.0);
52                                                 i[x]--;i[y]--;
53                                             } 
54                                     }
55                                 }
56                             }
57                         }
58                     }
59                 }
60             }
61         }
62       }
63     printf("%.6lf",f[4][4][4][4][4][4][4][4][4]);
64 }

 

以上是关于CCSP2019T2_纸牌计数 | 2019苏州CCSP大学生计算机系统与程序设计竞赛的主要内容,如果未能解决你的问题,请参考以下文章

CCSP子图计数

CCSP子图计数

CCSP子图计数

CCSP子图计数

SNOI 2019 题解

苏州csp分数线