題解 [USACO5.5]貳五語言Two Five
[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)\)
? 再結合我們已經可以較快地求出一定限制條件下的合法矩陣的數量
? 這題就到此終結了.
三.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