Hash+折半枚举 51Nod1440 序列分解

传送门:点击打开链接

题意:小刀和大刀是双胞胎兄弟。今天他们玩一个有意思的游戏。 大刀给小刀准备了一个长度为n的整数序列。小刀试着把这个序列分解成两个长度为n/2的子序列。
这两个子序列必须满足以下两个条件:
1.他们不能相互重叠。
2.他们要完全一样。
如果小刀可以分解成功,大刀会给小刀一些糖果。
然而这个问题对于小刀来说太难了。他想请你来帮忙。(n <= 40)

思路:调整了几天,今天手感好到爆啊,好久没有连编译错误都没有一次性写完的了。

这道题很有意义,n的大小为40,会比较容易想到,如果为20时就可以枚举子集,那么40是不是应该考虑折半的方法。

这题的折半非常有意思,先取前n/2个数字,去枚举所有状态,把前n/2个数字分到两个串中,如果一个串是另一个串的前缀,那么就把非前缀的后面部分插入到hash表中。

再考虑后n/2个数字,枚举所有状态,把后n/2个数字放到两个串中,如果一个串是另一个串的后缀,那么去hash表中查找非后缀部分是否存在,如果存在,那么就是有解了,否则就是无解

#include <map>
#include <set>
#include <cmath>
#include <ctime>
#include <stack>
#include <queue>
#include <cstdio>
#include <cctype>
#include <bitset>
#include <string>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <functional>
#define fuck(x) cout<<"["<<x<<"]";
#define FIN freopen("input.txt","r",stdin);
#define FOUT freopen("output.txt","w+",stdout);
//#pragma comment(linker, "/STACK:102400000,102400000")
using namespace std;
typedef long long LL;
typedef pair<int, int> PII;
typedef unsigned long long ULL;

const int MX = 1e5 + 5;
const int HMAX = 1000007;
const int sed = 131;

int n;
int A[100], B[100], S[2][MX];
struct HData {
    ULL x;
    int nxt;
} HA[MX];
int Head[HMAX], rear;
void Hash_init() {
    rear = 0;
    memset(Head, -1, sizeof(Head));
}
void Hash_add(ULL x) {
    int h = x % HMAX;
    for(int i = Head[h]; ~i; i = HA[i].nxt) {
        if(x == HA[i].x) return;
    }
    HA[rear].x = x;
    HA[rear].nxt = Head[h];
    Head[h] = rear++;
}
bool Hash_query(ULL x) {
    int h = x % HMAX;
    for(int i = Head[h]; ~i; i = HA[i].nxt) {
        if(x == HA[i].x) return true;
    }
    return false;
}

int main() {
    int T; //FIN;
    scanf("%d", &T);
    while(T--) {
        Hash_init();
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) {
            scanf("%d", &A[i]);
            B[i] = A[i];
        }
        sort(B + 1, B + 1 + n);
        int sz = unique(B + 1, B + 1 + n) - B - 1;
        for(int i = 1; i <= n; i++) {
            A[i] = lower_bound(B + 1, B + 1 + sz, A[i]) - B;
        }

        int hn = n / 2;
        for(int s = 0; s < (1 << hn); s++) {
            int cur[2] = {0, 0}, sign = true;
            for(int i = 0; i < hn; i++) {
                int t = s >> i & 1;
                S[t][++cur[t]] = A[i + 1];
                if(cur[t] <= cur[t ^ 1] && S[t][cur[t]] != S[t ^ 1][cur[t]]) {
                    sign = false; break;
                }
            }
            if(sign) {
                ULL x = 0;
                int id = cur[0] >= cur[1] ? 0 : 1;
                for(int i = cur[id ^ 1] + 1; i <= cur[id]; i++) {
                    x = x * sed + S[id][i];
                }
                Hash_add(x);
            }
        }

        bool ans = false;
        for(int s = 0; s < (1 << hn); s++) {
            int cur[2] = {0, 0}, sign = true;
            for(int i = 0; i < hn; i++) {
                int t = s >> i & 1;
                S[t][++cur[t]] = A[n - i];
                if(cur[t] <= cur[t ^ 1] && S[t][cur[t]] != S[t ^ 1][cur[t]]) {
                    sign = false; break;
                }
            }
            if(sign) {
                ULL x = 0;
                int id = cur[0] >= cur[1] ? 0 : 1;
                for(int i = cur[id]; i != cur[id ^ 1]; i--) {
                    x = x * sed + S[id][i];
                }
                if(Hash_query(x)) {
                    ans = true; break;
                }
            }
        }
        printf("%s\n", ans ? "Good job!!" : "What a pity!");
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值