1. 程式人生 > 實用技巧 >COCI20102011 Contest#Final D (dp)

COCI20102011 Contest#Final D (dp)

COCI20102011 Contest#Final D (dp)

我們將一個操作序列看做由左右括號,空格構成的字串,則序列大致長這個樣子

\(\text{_ ( ( ) _ ( ) ) ( _ ( ( ) ) ( }\)

很顯然,一個失配的左括號只能在最外層出現,而空格可以出現在任意位置

dp一個括號序列讓人想到區間dp,但是這個題目的區間實際只需要用長度就可以描述

\(dp[t][l][r][f1][f2]\)表示用\(t\)的時間從\(l\)走到\(r\)\(f1\)表示是不是最外層括號,\(f2\)表示當前\(dp\)是否受到單純括號序列的限制

其中,引入的單純括號序列是為了防止出現重複轉移,其意思就是這個括號序列兩端必須是一對匹配的左右括號且,而中間隨意

轉移大致如下:

1.那麼對於非單純的括號序列,可以在序列插入空格或者失配的左括號(需要滿足\(f1\)),從\(dp[t-1]\)轉移過來

2.對於任何的括號序列,都可以在兩端找到匹配的的左右括號,從\(dp[t-2]\)轉移過來,且完成匹配後\(f1\)應為\(0\)

3.且一個非單純的括號序列是可以分割的,為了不重複,強制分割的左序列是單純的即可

#include<bits/stdc++.h>
using namespace std;
#pragma GCC optimize(2)
typedef long long ll;
#define reg register
#define rep(i,a,b) for(reg int i=a;i<=b;++i)
#define drep(i,a,b) for(reg int i=a;i>=b;--i)
char IO;
int rd(){
	int s=0;
	while(!isdigit(IO=getchar()));
	do s=(s<<1)+(s<<3)+(IO^'0');
	while(isdigit(IO=getchar()));
	return s;
}

const int N=51,P=10007;

int n,m,T;
int E[N][N];
int dp[N][N][N][2][2];

int main(){
	n=rd(),m=rd(),T=rd();
	memset(E,-63,sizeof E);
	rep(i,1,m) {
		int u=rd(),v=rd(),c=IO==' '?getchar():IO;
		if(c>='A' && c<='Z') E[u][v]=c-'A'+1;
		else if(c>='a' && c<='z') E[u][v]='a'-c-1;
		else E[u][v]=0;
	}
	rep(i,1,n) rep(j,0,1) dp[0][i][i][j][0]=1;
	rep(k,1,T){
		rep(l,1,n) rep(r,(k<T?1:n),n) rep(fl,0,1) rep(fl2,0,1) {
			ll res=0;
			if(!fl2) {
				rep(i,2,k-1) rep(j,1,n) res+=dp[i][l][j][0][1]*dp[k-i][j][r][fl][0];
				rep(i,1,n) if(E[l][i]>=0 && (!E[l][i] || fl)) res+=dp[k-1][i][r][fl][fl2];
			}
			if(k>1) rep(i,1,n) if(E[l][i]>0) rep(j,1,n) if(E[j][r]+E[l][i]==0) res+=dp[k-2][i][j][0][0];
			dp[k][l][r][fl][fl2]=res%P;
		}
	}
	int ans=0;
	rep(i,1,T) ans+=dp[i][1][n][1][0];
	printf("%d\n",ans%P);
}