首先说明一下:我只是用暴力过了4道题的小数据,就是简单的枚举,大数据都不会做!下面的题解,是我从网上搜到的解答以及查看排行榜上大神的答案得出来的。
首先贴一下主要的题解来源:http://codeforces.com/blog/entry/47796,基本上解题思路都是从这里看到的,你可以直接查看这个链接,或者看下面的题解。
这个解题报告也很好:http://blog.csdn.net/zhoufenqin/article/details/52840475
还有排行榜大神们的答案,https://code.google.com/codejam/contest/5264486/scoreboard?c=5264486#vf=1,你可以下载所有的答案,进行查看。
当然,首先你得看懂题意,题目从官网上查看,这里就不介绍了。
第一题
1.看完题,怎么感觉这道这么难,难道不是第一道题最简单的套路了么?其实,如果你知道那个问题的话,答案非常简单。
简单的想法,就是枚举合法的序列,长度为(n + m),枚举过程中保证合法性,最后把所有的结果加起来,就是最后的答案,注意,这里求的是概率,所有可能的个数是(n + m)!,而且最后串的长度也是n+m,可以考虑每一次枚举除以因子,从1到n+m,这样算出来就是概率。这里小数据很容易就过了,小数据的数据范围是20,2^20 = 1e6,可以满足要求。大数据是4000,2^4000,根本不行,所以考虑其他途径。
2. 那就是动态规划,枚举当前a和b得票的个数,记dp[a][b],考虑最后一次是投给谁的票,然后可以从dp[a - 1][b]和dp[a][b-1]进行转移,注意这个过程中转移的条件,以及a>b必须满足,其他的dp[a][b] = 0;最后的结果是dp[n][m].这个计算过程也要考虑上面的分母因子。
1 void solve() { 2 int x, y; 3 cin >> x >> y; 4 memset(dp, 0, sizeof dp); 5 dp[0][0] = 1; 6 for (int i = 1; i <= x; i++) { 7 for (int j = 0; j <= y; j++) { 8 if(i <= j) continue; 9 dp[i][j] = dp[i - 1][j] * (x - i + 1) / (x + y - i - j + 1); 10 if(i >= j + 1 && j > 0) 11 dp[i][j] += dp[i][j - 1] * (y - j + 1) / (x + y - i - j + 1); 12 } 13 } 14 printf("%.10f ", dp[x][y]); 15 }
第一名比较巧妙的递推:
1 void init() { 2 for (int i = 1; i <= 2000; i++) dp[i][0] = 1; 3 for (int i = 1; i <= 2000; i++) { 4 for (int j = 1; j < 2000; j++) { 5 if(i <= j) continue; 6 double p1 = 1.0 * i / (i + j); 7 double p2 = 1 - p1; 8 dp[i][j] = dp[i - 1][j] * p1 + dp[i][j - 1] * p2; 9 } 10 } 11 }
3. 其实答案就是(n - m)/(n + m), https://en.wikipedia.org/wiki/Bertrand%27s_ballot_theorem,可以看一下证明过程
第二题
1. 小数据5*5,一共有2^25 = 32000000种方式,然后检查一下是否满足条件,最后找到满足要求的最大值。注意这里的情况可能有25种输入,而小数据的测试数据是100,也就是每种情况可以重复4次,可以提前算出所有答案,进行查表。
2. 大数据是100,100,无法进行枚举,然后就观察情况,找规律。可以发现下面的规律。
3. (r,c)和(c,r)的答案是一样的,所以只考虑r<=c的情况。r = 1的时候,答案是 c - c / 3, 例如110110110110这样。r = 2的时候,ans= 2*(c - c / 3),答案是r=1的时候重复2行。下面考虑其他情况。
4. r >= 3.观察
110110110110
101101101101
011011011011
可以发现每三行这样重复,一定是满足要求的,而且这样摆放是最多的,因为每三个至少有一个空白,上面的摆法就是没3个一个空白。然后根据r%3的情况考虑那一行应该放前面。
r%3 == 0, 随意都可以,观察,答案就是 r / 3 * c * 2, 3行一组, 每一组每一列刚好2个1.
r%3 == 1, 只需要考虑最后一行,使得左边的1尽可能的多,循环的3行里面满足要求的只有第一行,110110110,这样左边,然后答案也很明显,就是 r / 3 * c * 2 + c - c / 3;
r%3 == 2,也是考虑最后2行,使得左边的1尽可能的多,然后就有2种组合:
110110110110
101101101101
或者:
110110110110
011011011011
显然,上面那种情况比较优, 答案就是 r / 3 * c * 2 + c + (c + 2) / 3.
然后,就是这样。
第三题
1. 遇到这题,很容易想到的一个解法就是在当前位置,枚举接下来可能出现的单词,从前往后枚举,如果所有字符都是单词,则结果+1,可以把每个字符出现过的单词链接起来,加速枚举过程,小数据,就这样很容过了,但是大数据,通过 一个一个数的方式,大数据是过不了的,还得想其他的办法。
2.还是动态规划,以单词长度为一维,考虑到该位置的所有可能数,考虑这个数怎么计算。我的想法是依次枚举每个单词,看这个单词能不能出现,然后结果就是dp[i] = dp[i] + dp[i - size(word)]。这里每一个word都要判断。最后的结果就是dp[size(sentence)]。计算一下复杂度,4000 * 400 * 20 = 32000000,可以在时限内完成。
贴一下代码:
1 const int mod = 1e9 + 7; 2 vector<string> s; 3 int dp[4010]; 4 int n, m; 5 void solve() { 6 cin >> n >> m; 7 s.clear(); string t; 8 for (int i = 0; i < n; i++) { 9 cin >> t; s.pb(t); 10 } 11 while(m--) { 12 cin >> t; 13 int len = t.size(); 14 memset(dp, 0, sizeof dp); 15 dp[0] = 1; 16 for (int i = 1; i <= len; i++) { 17 for (int j = 0; j < n; j++) { 18 vector<int> v(26, 0); 19 if(i >= s[j].size()) { 20 for (int x = 0; x < s[j].size(); x++) { 21 v[t[i - x - 1] - 'a']++; 22 v[s[j][x] - 'a' ]--; 23 } 24 bool f = 1; 25 for (int x = 0; x < 26; x++) { 26 if(v[x] != 0) {f = 0; 27 break; 28 } 29 } 30 if(f) { 31 dp[i] = (dp[i] + dp[i - s[j].size() ]) % mod; 32 } 33 } 34 } 35 } 36 cout << ' ' << dp[len]; 37 } 38 }
这里可以考虑加速过程,2个单词,如果他们含有的字母以及个数相同,虽然顺序不同,但是可以算作一个单词,可以通过长度26的数组,算出一个值,统计每个字符的个数,算作这个word的hash值,这样复杂度就可以缩小一个因子,通过map来统计这样处理后相同的单词的个数来做,由于每个单词长度最多是20,所以复杂度就是4000 * 20,map查找的过程可以认为是O(1),下面贴一下第一名大神的代码。
1 #include <cstdio> 2 #include <map> 3 #include <vector> 4 5 using namespace std; 6 7 const int N = 4001; 8 const int mod = 1000000007; 9 10 map<vector<int>, int> c; 11 char s[N]; 12 int dp[N]; 13 int n, m; 14 15 inline void update(int &a, long long b) { 16 a = (a + b) % mod; 17 } 18 19 int main() { 20 int t, tt; 21 scanf("%d", &t); 22 for (tt = 1; tt <= t; tt++) { 23 scanf("%d%d", &n, &m); 24 c.clear(); 25 for (int i = 0; i < n; i++) { 26 vector<int> a(26); 27 scanf("%s", s); 28 for (int j = 0; s[j] != '