LUOGU 2593 : [Zjoi2006] 超级麻将

传送门

解题思路

直接爆搜全T。。状态数太多了,所以我们考虑贪心+剪枝。贪心:先拿三个连着的,再拿四个一样的,再拿三个一样的,最后拿两个一样的这样的搜索顺序最优,两个的放最后是因为只要这样的一个,三个连着的放开头是因为这样可以照顾到后面很少的情况。这样肯定还是T,我们考虑剪枝,用hash减。考虑如果数据为
12 0 1 0 0 0…. 这样的 ,我们第一种搜的方案是 a[1]里拿三个4,后面发现不行,返回。第二种方案是a[1]拿四个3,发现这两种方案拿完a[1]后 剩下的序列都是0 1 0 0…,所以将剩下的序列hash一下存到set里,如果再次遇到就直接返回false,注意这里直接去模会T的很惨,所以要用到unsigned long long 里的自然溢出。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
#define ULL unsigned long long

using namespace std;
const int MAXN = 105;
const ULL base = 131ll;

int n,a[MAXN];
ULL hash[MAXN],sum;
set<ULL> s;

bool dfs(int x,bool two,ULL S){
    if(s.find(S)!=s.end()) return 0;
    s.insert(S);
    while(a[x]==0 && x<=100) x++;
    if(x==101) return two;
    if(a[x]>0 && a[x+1]>0 && a[x+2]>0) {
        a[x]--;a[x+1]--;a[x+2]--;
        if(dfs(x,two,S-hash[x]-hash[x+1]-hash[x+2])) return 1;
        a[x]++;a[x+1]++;a[x+2]++;
    }
    if(a[x]>=4) {
        a[x]-=4;
        if(dfs(x,two,S-hash[x]*4)) return 1;
        a[x]+=4;
    } 
    if(a[x]>=3){
        a[x]-=3;
        if(dfs(x,two,S-hash[x]*3)) return 1;
        a[x]+=3;
    }
    if(a[x]>=2 && !two){
        a[x]-=2;
        if(dfs(x,1,S-hash[x]*2-hash[100])) return 1;
        a[x]+=2;
    }return 0;
}

int main(){
//  freopen("rand.txt","r",stdin);
//  freopen("B.txt","w",stdout);
    scanf("%d",&n);
    hash[1]=1ll;
    for(register int i=2;i<=100;i++) {
        hash[i]=hash[i-1]*base;
    }
    for(register int i=1;i<=n;i++){
        sum=0;
        for(register int j=1;j<=100;j++){
            scanf("%d",&a[j]);sum+=hash[j]*a[j];
        } 
        s.clear();
        puts(dfs(1,0,sum)==true?"Yes":"No");            
    }
    return 0;
}
原文地址:https://www.cnblogs.com/sdfzsyq/p/9676923.html