1. 程式人生 > >題解 [USACO5.5]貳五語言Two Five

題解 [USACO5.5]貳五語言Two Five

進行 break 一道 折線 生成算法 表示 new reg stdin

[USACO5.5]貳五語言Two Five


一道記憶化搜索題

一.題面

題目

定義一類行列均單調遞增的\(5\times5?\)矩陣,將其展開後所形成的字符串按字典序編號.

題目要求實現編號與字符串的相互轉換

二.解題步驟

1.求限制條件下的合法矩陣的數量

? 先不管字符串與編號的相互轉換;

? 給你一些限定條件(形如\((x,y)\)處只能填某字符),讓你求滿足條件的合法矩陣數

(1).暴搜

? 有兩種思路,一種是按順序搜每一個格子放哪一個字母,另一種是按順序搜每一個字母放哪一個格子

? 復雜度是\(O(25!)?\),過不了

? 在這裏要特別註意第二種思路,記憶化也是在此基礎上進行

(2).記憶化

? 在上面提到的第二種思路進行優化.

? 既然是按照字母順序依次放到格子裏,那麽會不會有一些美妙的性質呢?

? 其實是有的,如下圖,可有以下性質:

     a.已經填進去的數必在一個聯通塊內.

? 顯然應如此,否則中間的空肯定填不進更大的字母

             b.聯通塊的輪廓線應是連續下降的折線

? 如圖,藍塊是已經填好的塊

? 假設綠塊是我現在要填字母的塊,如果填進去後,其上的黃塊必定將無字母可填(因為填字母是按順序填的!)

技術分享圖片

    c.合法的聯通塊內具體的字母順序不影響剩下的字母填入

? 因為以後將要填入的字母必定比現在所有已經填入的字母大,所以相互之間的"影響"

是一致的

? 綜上所述,我們可以使用記憶化搜索(主要是由於性質c,性質a,b主要是方便設dp狀態)

? 設記憶化數組\(f[a][b][c][d][e]\) 表示第1,2,3,4,5行分別填了a,b,c,d,個數的情況下的可能合法矩陣數量,也就求出來求限制條件下的合法矩陣的數量.

2.實現編號與字符串的相互轉換

? (ps:可以借鑒一下排列的生成算法,感覺本質上差不多QAQ)

? 即通過大家所說的逼近法來實現,

? 現在有一個合法矩陣對應的字符串\(P=p_0p_1p_2...p_{22}p_{23}p_{24}?\),那麽它的前面還有多少個滿足條件的字符串呢?

? 那麽,編號小於\(P\) 的合法矩陣的字符串集合就很明確了,首先將前綴為\(k_0(0<k_0<p_0)\)

的串加上,再加上\(k_0k_1(0<k_0<p_0,0<k_1<p_1)\),以此類推,加上\(k_0...k_i(0<k_j<p_j)\)為前綴的字符串即可.

? 再結合我們已經可以較快地求出一定限制條件下的合法矩陣的數量

? 這題就到此終結了.

三.AC代碼

/*
  此題要點:
  1.要按字母的順序來搜
* 2.可以記憶化,用數組f[][][][][]來實現 * 
  3.有值的f數組的下標a,b,c,d,e應該遞減
  4.最後用逼近法來完成任務
*/
#include<bits/stdc++.h>
#define il inline 
#define R register int
#define ll long long
#define gc getchar
using namespace std;
il int rd()
{
    R x=0,flag=1;
    char ch=0;
    while((ch>'9'||ch<'0')&&ch!='-')ch=gc();
    if(ch=='-')flag=-1,ch=gc();
    while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=gc();
    return x*flag;
}

int S[26],ans;

inline bool check(int letter,int pos)
{
    return (!S[pos])||(letter==S[pos]);
}

int f[6][6][6][6][6];
bool ok[30];
int dfs(int a,int b,int c,int d,int e,int letter)
{
    if(letter>25) return 1;
    if(f[a][b][c][d][e]) return f[a][b][c][d][e];
    int cnt=0;
    if(a<5 && check(letter,a+1)) {cnt+=dfs(a+1,b,c,d,e,letter+1);}
    if(b<a && check(letter,b+6)) {cnt+=dfs(a,b+1,c,d,e,letter+1);}
    if(c<b && check(letter,c+11)) {cnt+=dfs(a,b,c+1,d,e,letter+1);}
    if(d<c && check(letter,d+16)) {cnt+=dfs(a,b,c,d+1,e,letter+1);}
    if(e<d && check(letter,e+21)) {cnt+=dfs(a,b,c,d,e+1,letter+1);}
    return f[a][b][c][d][e]=cnt;
}
void task1(int num)
{
    for(R i=1;i<=25;i++)
    {
        for(S[i]=1;;S[i]++)
        {
            if(ok[S[i]])continue;
            ok[S[i]]=1;
            memset(f,0,sizeof(f));//記得清零
            int tmp=dfs(0,0,0,0,0,1);
            if(ans+tmp>=num) break;
            ans+=tmp;
            ok[S[i]]=0;
        }
    }
    for(R i=1;i<=25;i++) cout<<char(S[i]+'A'-1);
}

void task2()
{
    string s;
    cin>>s;
    ans=0;
    for(R i=0;i<25;i++)
    {
        for(S[i+1]=1;S[i+1]<=s[i]-'A';S[i+1]++)
        {
            if(ok[S[i+1]])continue;
            ok[S[i+1]]=1;
            memset(f,0,sizeof(f));
            ans+=dfs(0,0,0,0,0,1);
            ok[S[i+1]]=0;
        }
    }
    cout<<ans+1<<endl;
}

int main ()
{
    freopen("twofive.in","r",stdin);
    freopen("twofive.out","w",stdout);
    char opt;
    cin>>opt;
    if(opt=='N')task1(rd());
    else task2();
    //dfs(0,0,0,0,0,1);
    //cout<<f[0][0][0][0][0]<<endl;
    return 0;
}

?

?

?

題解 [USACO5.5]貳五語言Two Five