USACO 5.5.3 Twofive 记忆式搜索+枚举
2018-02-09 23:50
183 查看
http://train.usaco.org/usacoprob2?a=qNexyVCSx2z&S=twofive
题目大意:给一个5*5的字符方阵(A-Z),阅读顺序为从上到下逐行从左到右念下来,要求每一个位置的字母比它上方与左侧的字母要大。题目有两个要求:1、给一个方阵求它在所有可能的方阵中排第几个(按字典序升序排列)2、给序号,求出方阵
1、要枚举的话有25!种情况,肯定不是这么搞的
2、想到从小到大逼近,可以看作在25个字符中获得5个升序序列,保证序列中有序且五个序列的对应位置是有序的。五个一组考虑,在十个字符的情况下是C(10,5) = 252,但是当选择多了之后C(15,5)*C(10,5) = 756756,而且很难枚举(从一个状态到下一个状态的计算)
看了题解才知道确实是使用这样的按前缀逼近的方法,但是要找到某个前缀对应的可能性总数使用的是叫做记忆式搜索的方法
*所谓记忆式搜索感觉就是递归+dp,只不过他把每一步递归的结果记录了下来可以直接取用,相当于边dp边在用
就算知道了方法我还是不会做,因为不可能开一个[25]^25的矩阵,而且这样的枚举情况中有很多可能是不满足矩阵要求的
所以最后采用的方法是对每一个当前前缀进行一次完整的递归,使用数组arr[6][6][6][6][6],下标表示每一行当前有多少个字母,按照A-Z的顺序逐个加入字母(从左上到右下,这样就保证枚举的情况必然满足条件),加入字母之前要判断保证:1、每一行的字母数小于等于上一行的字母数;2、在每个位置插入的字母与当前已确定前缀不冲突(插入的位置为未定位置或插入的字母正好与确定字母相同)
字符串求序号:从前往后每一位按A-arr[i]的顺序枚举求该前缀的可能总数,累加,最后再加一
序号求字符串:从前往后每一位按A-Z枚举求该前缀可能总数(之后当前枚举的那一位是待定的,前面的是确定的,后面的是任意的),如果小于序号,序号减小,如果大于序号,该位确定/*
ID: frontie1
TASK: twofive
LANG: C++
*/
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
char oper;
char tar[30] = {0};
int num;
char t[30] = {0};
int arr[6][6][6][6][6];
bool valid(int pos, int chr)
{
if(t[pos] == 0 || (int)t[pos] == 'A'+chr) return true;
else return false;
}
int DFS(int a, int b, int c, int d, int e, int cur)
{
if(cur == 25) return 1;
//这里我一开始写了==24就跳出,因为我认为Y可以插入的位置只有一个
//但是是错误的,因为DFS求得并不是cur可插入的位置,而是每一行确定的字符数量为abcde时,矩阵可能性总数
//只有当矩阵被塞满时,其可能性才为一
int &output = arr[a][b][c][d][e];
if(output != 0) return output;
if(a < 5 && valid(a, cur)) output += DFS(a+1, b, c, d, e, cur+1);
if(b < a && valid(b+5, cur)) output += DFS(a, b+1, c, d, e, cur+1);
if(c < b && valid(c+10, cur)) output += DFS(a, b, c+1, d, e, cur+1);
if(d < c && valid(d+15, cur)) output += DFS(a, b, c, d+1, e, cur+1);
if(e < d && valid(e+20, cur)) output += DFS(a, b, c, d, e+1, cur+1);
return output;
}
int main()
{
freopen("twofive.in", "r", stdin);
freopen("twofive.out", "w", stdout);
cin >> oper;
//arr[0][0][0][0][0] = 1;
if(oper == 'N'){
cin >> num;
int tem;
for(int i = 0; i < 25; ++i){
for(t[i] = 'A'; memset(arr, 0, sizeof(arr)), (tem = DFS(0, 0, 0, 0, 0, 0)) < num; ++t[i]){
num -= tem;
}
cout << t[i];
}
cout << endl;
}
else if(oper == 'W'){
cin >> tar;
int answer = 0;
for(int i = 0; i < 25; ++i){
for(t[i] = 'A'; t[i] < tar[i]; ++t[i]){
memset(arr, 0, sizeof(arr));
answer += DFS(0, 0, 0, 0, 0, 0);
}
}
cout << answer+1 << endl;
}
return 0;
}
参考:https://www.cnblogs.com/txhwind/archive/2012/08/18/2645339.html
题目大意:给一个5*5的字符方阵(A-Z),阅读顺序为从上到下逐行从左到右念下来,要求每一个位置的字母比它上方与左侧的字母要大。题目有两个要求:1、给一个方阵求它在所有可能的方阵中排第几个(按字典序升序排列)2、给序号,求出方阵
1、要枚举的话有25!种情况,肯定不是这么搞的
2、想到从小到大逼近,可以看作在25个字符中获得5个升序序列,保证序列中有序且五个序列的对应位置是有序的。五个一组考虑,在十个字符的情况下是C(10,5) = 252,但是当选择多了之后C(15,5)*C(10,5) = 756756,而且很难枚举(从一个状态到下一个状态的计算)
看了题解才知道确实是使用这样的按前缀逼近的方法,但是要找到某个前缀对应的可能性总数使用的是叫做记忆式搜索的方法
*所谓记忆式搜索感觉就是递归+dp,只不过他把每一步递归的结果记录了下来可以直接取用,相当于边dp边在用
就算知道了方法我还是不会做,因为不可能开一个[25]^25的矩阵,而且这样的枚举情况中有很多可能是不满足矩阵要求的
所以最后采用的方法是对每一个当前前缀进行一次完整的递归,使用数组arr[6][6][6][6][6],下标表示每一行当前有多少个字母,按照A-Z的顺序逐个加入字母(从左上到右下,这样就保证枚举的情况必然满足条件),加入字母之前要判断保证:1、每一行的字母数小于等于上一行的字母数;2、在每个位置插入的字母与当前已确定前缀不冲突(插入的位置为未定位置或插入的字母正好与确定字母相同)
字符串求序号:从前往后每一位按A-arr[i]的顺序枚举求该前缀的可能总数,累加,最后再加一
序号求字符串:从前往后每一位按A-Z枚举求该前缀可能总数(之后当前枚举的那一位是待定的,前面的是确定的,后面的是任意的),如果小于序号,序号减小,如果大于序号,该位确定/*
ID: frontie1
TASK: twofive
LANG: C++
*/
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
char oper;
char tar[30] = {0};
int num;
char t[30] = {0};
int arr[6][6][6][6][6];
bool valid(int pos, int chr)
{
if(t[pos] == 0 || (int)t[pos] == 'A'+chr) return true;
else return false;
}
int DFS(int a, int b, int c, int d, int e, int cur)
{
if(cur == 25) return 1;
//这里我一开始写了==24就跳出,因为我认为Y可以插入的位置只有一个
//但是是错误的,因为DFS求得并不是cur可插入的位置,而是每一行确定的字符数量为abcde时,矩阵可能性总数
//只有当矩阵被塞满时,其可能性才为一
int &output = arr[a][b][c][d][e];
if(output != 0) return output;
if(a < 5 && valid(a, cur)) output += DFS(a+1, b, c, d, e, cur+1);
if(b < a && valid(b+5, cur)) output += DFS(a, b+1, c, d, e, cur+1);
if(c < b && valid(c+10, cur)) output += DFS(a, b, c+1, d, e, cur+1);
if(d < c && valid(d+15, cur)) output += DFS(a, b, c, d+1, e, cur+1);
if(e < d && valid(e+20, cur)) output += DFS(a, b, c, d, e+1, cur+1);
return output;
}
int main()
{
freopen("twofive.in", "r", stdin);
freopen("twofive.out", "w", stdout);
cin >> oper;
//arr[0][0][0][0][0] = 1;
if(oper == 'N'){
cin >> num;
int tem;
for(int i = 0; i < 25; ++i){
for(t[i] = 'A'; memset(arr, 0, sizeof(arr)), (tem = DFS(0, 0, 0, 0, 0, 0)) < num; ++t[i]){
num -= tem;
}
cout << t[i];
}
cout << endl;
}
else if(oper == 'W'){
cin >> tar;
int answer = 0;
for(int i = 0; i < 25; ++i){
for(t[i] = 'A'; t[i] < tar[i]; ++t[i]){
memset(arr, 0, sizeof(arr));
answer += DFS(0, 0, 0, 0, 0, 0);
}
}
cout << answer+1 << endl;
}
return 0;
}
参考:https://www.cnblogs.com/txhwind/archive/2012/08/18/2645339.html
相关文章推荐
- CodeVS 1416|USACO Train 5.5.3|Two Five|二五语言|搜索
- usaco 1.4 枚举&搜索
- [USACO5.5.3]Twofive
- USACO-Section1.3 Wormholes 【深度优先搜索】【暴力枚举】
- USACO5.5.3 (twofive)
- USACO-Section2.1 Healthy Holsteins [搜索][枚举]
- POJ 2785 4 Values whose Sum is 0 折半枚举(双向搜索)
- usaco 4.1 Beef McNuggets 搜索
- [kuangbin]专题一 简单搜索 D - Fliptile(二进制枚举)
- ppt Fibonacii数列的第n项------记忆式搜索
- [bzoj1603]: [Usaco2008 Oct]打谷机 搜索
- USACO-Section1.5 Arithmetic Progressions(枚举)
- [USACO] 从Mother’s Milk看搜索
- 枚举 + 深度搜索 ----炸弹人
- USACO Section 1.4 Mother's Milk 搜索
- POJ3279 Fliptile 枚举+简单搜索
- 08-27~29 HDU1010 USACO4.3~4.4 搜索剪枝,模拟,拓扑,网络流
- bzoj 1600: [Usaco2008 Oct]建造栅栏 枚举
- 【USACO】Mother's Milk(搜索)
- 【USACO】Subset Sums(双向搜索 dfs)