USACO 5.5.3 Twofive 记忆式搜索+枚举

16 篇文章 0 订阅

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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值