dp+记忆化搜索 Codeforces567F Mausoleum

传送门:点击打开链接

题意:用1~n这些数,每个数用2次,构造出一个长度为2n的数列,使得其先不下降,后上升,或者整个都是不下降,或者整个都是不上升,然后再给出k个限制条件,如果是3 < 5,那么就表示第3个位置的数字要小于第5个位置的数字。问这样的数列有多少个

思路:一开始会感觉没任何思路,,我们来慢慢的分析。。先考虑数字1,数字1只可能出现在数列前两个位置,或者最后两个位置,或者第一个位置和最后一个位置。同样的,2会在1的基础上,从两边开始填充。可以想象,所有的数字应该是按照从小到大的顺序,从两边向中间填入的。

在不考虑限制条件的情况下,我们设dp[i][j]为区间[i,j]满足条件的数量,那么有dp[i][j]=dp[i+1][j-1]+dp[i+2,j]+dp[i,j-2],边界是dp[i][j]=1如果j-i==1,答案就是dp[1][n]

那么现在有了限制条件,其实限制条件只不过是在限制方程的转移

那么,这些限制条件,到底是如何影响方程的转移呢。。

如果要删除的是a和b,

那么说明u和v在转移的时候,与a相等的和与b相等的必须同时删除,换句话说,与u相等的所有数必须只能是u或者v,与v相等的所有数必须只能是u或者v,这样两个才能同时被删除

那么删除a时,存在限制条件a>v,必须要确保所有的v都在[L,R]外面, 这样才能说明v那个位置在之前已经被填了数字,所以v的数字肯定是<现在在考虑a的数字的。同样的也要考虑删除b时的情况

限制条件<=的考虑方法,其实就是上面两种方法的综合。我们正考虑是否能删除a和b这两个,先枚举出a所有的满足a>=v的限制条件,这些v必须要在[L,R]外面或者恰好等于a或者b。


总的来说,难点1在于是否能想出填充数字的规律,难点2在于是否能想清楚限制条件是如何影响方程的转移的,只要想清楚了这些这道题就算是解决了

#include<map>
#include<set>
#include<cmath>
#include<ctime>
#include<stack>
#include<queue>
#include<cstdio>
#include<cctype>
#include<string>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<functional>
#define fuck printf("fuck")
#define FIN freopen("input.txt","r",stdin)
#define FOUT freopen("output.txt","w+",stdout)
using namespace std;
typedef long long LL;

const int MX = 100 + 5;
const int INF = 0x3f3f3f3f;
typedef pair<int, int>PII;
#define pb push_back

int n, k;
LL dp[MX][MX];
vector<int>A[MX][3];

bool check_0(int a, int b, int L, int R) {
    for(int i = 0; i < A[a][0].size(); i++) {
        int v = A[a][0][i];
        if(L <= v && v <= R) return false;
    }
    for(int i = 0; i < A[b][0].size(); i++) {
        int v = A[b][0][i];
        if(L <= v && v <= R) return false;
    }
    return true;
}

bool check_1(int a, int b, int L, int R) {
    for(int i = 0; i < A[a][1].size(); i++) {
        int v = A[a][1][i];
        if(v != a && v != b) return false;
    }
    for(int i = 0; i < A[b][1].size(); i++) {
        int v = A[b][1][i];
        if(v != a && v != b) return false;
    }
    return true;
}

bool check_2(int a, int b, int L, int R) {
    for(int i = 0; i < A[a][2].size(); i++) {
        int v = A[a][2][i];
        if(L <= v && v <= R && v != a && v != b) return false;
    }
    for(int i = 0; i < A[b][2].size(); i++) {
        int v = A[b][2][i];
        if(L <= v && v <= R && v != a && v != b) return false;
    }
    return true;
}

LL DP(int L, int R) {
    if(dp[L][R] != -1) return dp[L][R];
    if(R - L == 1) {
        return dp[L][R] = check_0(L, R, L, R) ? 1 : 0;
    }

    dp[L][R] = 0;
    if(check_0(L, R, L, R) && check_1(L, R, L, R) && check_2(L, R, L, R)) dp[L][R] += DP(L + 1, R - 1);
    if(check_0(L, L + 1, L, R) && check_1(L, L + 1, L, R) && check_2(L, L + 1, L, R)) dp[L][R] += DP(L + 2, R);
    if(check_0(R - 1, R, L, R) && check_1(R - 1, R, L, R) && check_2(R - 1, R, L, R)) dp[L][R] += DP(L, R - 2);
    return dp[L][R];
}

int main() {
    //FIN;
    while(~scanf("%d%d", &n, &k)) {
        memset(dp, -1, sizeof(dp));
        for(int i = 1; i <= n; i++) {
            for(int j = 0; j < 3; j++) A[i][j].clear();
        }

        for(int i = 1; i <= k; i++) {
            int u, v; char op[10];
            scanf("%d%s%d", &u, op, &v);
            int len = strlen(op);
            if(op[0] == '=') {
                A[u][1].pb(v);
                A[v][1].pb(u);
            }
            if(op[0] == '<') {
                if(len == 1) A[v][0].pb(u);
                else A[v][2].pb(u);
            }
            if(op[0] == '>') {
                if(len == 1) A[u][0].pb(v);
                else A[u][2].pb(v);
            }
        }

        printf("%I64d\n", DP(1, 2 * n));
    }
    return 0;
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值