洛谷P4907【CYH-01】小奔的國慶練習賽 :$A$換$B$ $problem$(DFS,剪枝)

洛谷題目傳送門c++

順便提一下題意有一個地方不太清楚,就是若是輸出No還要輸出最少須要添加多少張牌才能知足要求。蒟蒻考完之後發現四個點Too short on line 2。。。spa

比較須要技巧的搜索code

既然是同一個花色要連續,那就枚舉每個花色在哪一段區間連續並選中四個區間,累計每一個點數的選中次數。get

最後來一個\(O(13)\)\(\text{check}\),首先每一個點數選中次數要很多於已有的個數。接着,只有全部點數的選中次數和已有點數相等時,才能判爲'Yes',而後統計某張牌的花色的區間未包含這張牌的總數更新答案。不然判爲'No',而後統計每一個點數選多了的總數更新答案。it

總複雜度\(\binom{13}{2}^4* 13=481195728\),超過了\(10^8\),確定須要剪枝。class

可行性剪枝:每一個點數選中次數之和很多於原有牌的總數。搜索

最優性剪枝:實時統計選多了的總數,在No的狀態下,若是超過答案則剪掉;在Yes的狀態下,只要不爲\(0\)就剪掉。技巧

700+ms比標程快多了。可能標程比較良心沒加什麼剪枝。統計

#include<bits/stdc++.h>
#define R register int
using namespace std;
const int N=99;
int a[N],b[N],l[N],r[N],cnt[N],n,now1,ans=N,ans1=N;
char s[N];
void dfs(R h,R lef){//lef爲n-當前已選中總次數
    if(h==5){
        R now=0;
        for(R i=1;i<=13;++i){
            if(cnt[i]>0)return;//不合法
            now|=cnt[i]<0;
        }
        if(now){ans1=now1;return;}//No狀態
        for(R i=1;i<=n;++i)//Yes狀態,統計答案
            if((l[a[i]]>b[i]||r[a[i]]<b[i])&&++now==ans)return;
        ans=now;ans1=1;return;//注意ans1=1的剪枝做用
    }
    for(R i=max(lef-(4-h)*13,0),j,rr;i<=13;++i){//枚舉區間長度,可行性剪枝
        if(i==0){l[h]=r[h]=0;dfs(h+1,lef);continue;}
        for(rr=i;rr<=13;++rr){//枚舉右端點
            for(j=rr-i+1;j<=rr;++j)now1+=--cnt[j]<0;//動態維護當前選多了的總數
            if(now1<ans1)l[h]=(r[h]=rr)-i+1,dfs(h+1,lef-i);//最優性剪枝
            for(j=rr-i+1;j<=rr;++j)now1-=++cnt[j]<=0;
        }
    }
}
int main(){
    scanf("%d",&n);
    for(R i=1;i<=n;++i){
        scanf("%d%s",&a[i],s);
        if(s[0]=='A')b[i]=1;//把點數處理一下
        else if(s[0]=='1')b[i]=10;
        else if(s[0]=='J')b[i]=11;
        else if(s[0]=='Q')b[i]=12;
        else if(s[0]=='K')b[i]=13;
        else b[i]=s[0]-'0';
        ++cnt[b[i]];
    }
    dfs(1,n);
    if(ans!=N)printf("Yes\n%d\n",ans);
    else printf("No\n%d\n",ans1);
    return 0;
}
相關文章
相關標籤/搜索