SDOI 2009 BIll的挑战

题目描述 Description

Sheng bill不仅有惊人的心算能力,还可以轻松地完成各种统计。在昨天的比赛中,你凭借优秀的程序与他打成了平局,这导致Sheng bill极度的不满。于是他再次挑战你。这次你可不能输!

这次,比赛规则是这样的:

给N个长度相同的字符串(由小写英文字母和′?′组成),S1,S2,...,SN,求与这N个串中的刚好K个串匹配的字符串T的个数(答案模1000003)。

若字符串Sx(1 ≤ x ≤ N)和T匹配,满足以下条件:

1. Sx.length = T.length。

2. 对于任意的1 ≤ i ≤ Sx.length,满足Sx[i]=′?′或者Sx[i]= T[i]。

其中T只包含小写英文字母。

输入描述 Input Description

本题包含多组数据。

第一行:一个整数T,表示数据的个数。

对于每组数据:

第一行:两个整数,N和K(含义如题目表述)。

接下来N行:每行一个字符串。

输出描述 Output Description

对于每组数据,输出方案数目(共T行)。

样例输入 Sample Input

1

2 1

a?

?b

样例输出 Sample Output

50

数据范围及提示 Data Size & Hint

对于30%的数据,T ≤ 5,N ≤ 5,字符串长度≤ 20;

对于70%的数据,T ≤ 5,N ≤ 13,字符串长度≤ 30;

对于100%的数据,T ≤ 5,N ≤ 15,字符串长度≤ 50。

  写代码用一个小时,dp转移写错,调了一个多小时,把lsany写成any,半小时改过来一个,半小时后发现还有一个没改过来。别人写的程序比我短一半,结果后几个点效率还比我好(前几个比我差)。存天理,灭蒟蒻啊。

  好了,我们来看一下这一个题目。我们可以一个一个字母的枚举,在枚举的过程中,我们不妨进行如下的动态规划。f[i][j]表示,当前已经处理了i位,匹配状态为j。j是我们用状态压缩得到的一个数字,就是用n个二进制位,分别表示一个字符串是否被匹配。

  状态转移的时候,我们把j所表示的已经被匹配的字符串分离出来,然后处理他们的下一位。建一个动态数组,用来储存下一位是X的字符串有哪几个。处理完后,将所有下一位是?的字符串直接判定为被匹配,然后枚举下一个字母,进行动态转移。

  输出的时候,枚举第几位被匹配就好。

  代码:

 1 #include<iostream>
 2 #include<cstring>
 3 #include<string>
 4 #include<vector>
 5 using namespace std;
 6
 7 const int mod = 1000003;
 8
 9 int f[51][65536];
10 int t, n, len, k;
11 int ans;
12 vector<int> G[27];       //动态数组
13 char c[16][52];
14
15 void dp() {
16     memset(f, 0, sizeof(f));
17     int num = 0;
18     for (int i = 0; i < 27; i++)  //预先处理第一位
19         G[i].clear();
20     for (int i = 1; i <= n; i++) {
21         if (c[i][0] == ‘?‘)
22             G[26].push_back(i);
23         else G[(int)c[i][0] - 97].push_back(i);
24     }
25     int any = 0;
26     for (int i = 0; i < G[26].size(); i++)
27         any = any + (1 << (G[26][i] - 1));   //any表示下一位为?的字符串的匹配状态
28     for (int i = 0; i < 26; i++) {
29         int lsany = any;   //lsany表示分别下一位为a - z时的匹配状体
30         for (int j = 0; j < G[i].size(); j++)
31             lsany = lsany + (1 << (G[i][j] - 1));
32         if (lsany != 0)
33             f[1][lsany] = f[1][lsany] % mod + 1;   //动态转移
34         if (lsany > num) num = lsany;
35     }
36     for (int i = 1; i < len; i++)
37         for (int j = 1; j <= num; j++)
38             if (f[i][j] != 0)  {
39                 for (int k = 0; k < 27; k++)
40                     G[k].clear();
41                 for (int k = 0; k < n; k++)
42                     if ((1 & j >> k) == 1)
43                         if (c[k+1][i] == ‘?‘)
44                             G[26].push_back(k+1);
45                         else G[(int) c[k+1][i] - 97].push_back(k+1);
46                 int any = 0;
47                 for (int k = 0; k < G[26].size(); k++)
48                     any = any + (1 << (G[26][k] - 1));
49                 for (int k = 0; k < 26; k++) {
50                     int lsany = any;
51                     for (int w = 0; w < G[k].size(); w++)
52                         lsany = lsany + (1 << (G[k][w] - 1));
53                     if (lsany != 0)
54                         f[i+1][lsany] =(f[i+1][lsany]  + f[i][j])  % mod;
55                 }
56             }
57 }
58
59
60 void print(int now, int where, int w){   //输出
61     if (now == k) {ans =(ans + f[len][w]) % mod; return;}
62     if (where > n) return;
63     print(now+1, where+1, w + (1 << (where - 1)));
64     print(now, where+1, w);
65 }
66
67 int main() {
68     ios::sync_with_stdio;
69     cin >> t;
70     for (int o = 0; o < t; o++) {
71         cin >> n >> k;
72         for (int i = 1; i <= n; i++)
73             cin >> c[i];
74         len = 0;
75         while ((int)c[1][len] > 96 || c[1][len] == ‘?‘)
76             len++;
77         dp();
78         ans = 0;
79         print(0, 1, 0);
80         cout << ans << "\n";
81     }
82     return 0;
83 }
时间: 2024-11-03 14:09:17

SDOI 2009 BIll的挑战的相关文章

[BZOJ 1879][SDOI 2009]Bill的挑战 题解(状压DP)

[BZOJ 1879][SDOI 2009]Bill的挑战 Description Solution 1.考虑状压的方式. 方案1:如果我们把每一个字符串压起来,用一个布尔数组表示与每一个字母的匹配关系,那么空间为26^50,爆内存: 方案2:把每一个串压起来,多开一维记录匹配字符,那么空间为nlen26,合法,但不便于状态的设计和转移: 方案3:把每一个串同一个位置的字符放在一起,用一个布尔数组记录与每一个小写字母的匹配关系,那么空间为26^15*len,爆内存: 方案4:把每一个串同一个位置

[bzoj1879][Sdoi2009]Bill的挑战_动态规划_状压dp

Bill的挑战 bzoj-1879 Sdoi-2009 题目大意: 注释:$1\le t \le 5$,$1\le m \le 15$,$1\le length \le 50$. 想法: 又是一个看数据范围想做法的题,我们想到状压dp. 看了题解... ...网上给的状态是f[len][s]表示长度为len满足状态s的字符串个数. 光看状态... ...可能算重啊?! 其实... ... 状态:dp[len][s]表示长度为len,能且只能满足状态为s的字符串个数. 转移:我们先预处理出g[i]

$[SDOI2009]Bill$的挑战

\([SDOI2009]Bill\)的挑战 观察数据范围,显然是状压. 但是如果你将\(K\)加进状态中,手推一下就会发现这里要用到容斥. 但我又不是讲容斥的是吧... 所以我们尝试不将\(K\)加入状态中,而是在最后枚举恰好含有\(K\)个元素的子集个数. 我们设\(f[i][j]\)表示对于所有集合\(i\)中的元素,匹配到第\(j\)位时的方案数. 实际上我们涉及到集合的状压转移时如果考虑一个状态由哪里转移来,要枚举合法子集,显然麻烦. 我们可以考虑当前状态可转移到哪里,这样只用造出合法状

BZOJ 1879 [Sdoi2009]Bill的挑战 ——状压DP

本来打算好好写写SDOI的DP题目,但是忒难了, 太难了,就写的这三道题仿佛是可做的. 生在弱省真是兴奋. 这题目直接状压,f[i][j]表示匹配到i,状态集合为j的方案数,然后递推即可. #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define F(i,j,k) for (int i=j;i<=k

[BZOJ 1875] [SDOI 2009] HH去散步【矩阵乘法】

题目链接:BZOJ - 1875 题目分析: 这道题如果去掉“不会立刻沿着刚刚走来的路走回”的限制,直接用邻接矩阵跑矩阵乘法就可以了.然而现在加了这个限制,建图的方式就要做一些改变.如果我们把每一条边看做点建矩阵,那么每次从一条边出发都只会到其他的边,不能仍然在这条边上“停留”,所以这就可以满足题目的限制.将每条边拆成两条单向边,比如一条编号为 4,一条编号为 5.那么 4^1=5, 5^1=4.这样只要不从第 i 条边走到 i 或 i^1 就可以了.初始的矩阵中以 A 为起点的边到达的方案数为

【BZOJ 1875】【SDOI 2009】HH去散步

水啊水,最后ans别忘了%哦! #include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int mo = 45989; int cnt = 1, point[23], n, m, t, S, T; struct node { int u, v, nxt; } E[1003]; struct Mt { int v[123][123]; Mt() {memset(v,

BZOJ 1878 SDOI 2009 HH项链 树状数组 + 脱机处理

标题效果:一些珠子项链.珠具有不同的颜色.我们问了很多次有多少种不同的颜色有过一段范围. 思考:这个问题让我学会聪明的离线实践.按左端点排序问题.加工出来的位置每种颜色首次出现.每一种颜色的下一次出现的位置.然后,1至cnt周期,这里有一个问题的左端点是当前节点,就处理他的答案.方法是前缀合,能够用树状数组.然后把这个颜色的下一个出现的位置+1. 这样做就避免了一种颜色在询问中被处理两次. CODE: #include <cstdio> #include <cstring> #in

[SDOI 2009]HH的项链

Description HH有一串由各种漂亮的贝壳组成的项链.HH相信不同的贝 壳会带来好运,所以每次散步 完后,他都会随意取出一段贝壳,思考它们所表达的含义.HH不断地收集新的贝壳,因此, 他的项链变得越来越长.有一天,他突然提出了一个问题:某一段贝壳中,包含了多少种不同 的贝壳?这个问题很难回答...因为项链实在是太长了.于是,他只好求助睿智的你,来解 决这个问题. Input 第一行:一个整数N,表示项链的长度. 第二行:N个整数,表示依次表示项链中贝壳的编号(编号为0到1000000之间

BZOJ 1878 SDOI 2009 HH的项链 树状数组 + 离线处理

题目大意:有一些珠子串成的项链,珠子有不同的颜色.多次询问一段区间内有多少不同的颜色. 思路:这个题让我学会了一种巧妙的离线做法.将问题按左端点排序.处理出来每个颜色第一个出现的位置,和每个颜色下一个出现的位置.然后1到cnt循环,如果这里有一个问题的左端点是当前节点,就处理他的答案,方法是前缀合,可以用树状数组.然后把这个颜色的下一个出现的位置+1. 这样做就避免了一种颜色在询问中被处理两次. CODE: #include <cstdio> #include <cstring>