一道求解存在性的区间dp,思路非常巧妙。对于原名字串,通过压缩变换,如果最后能够压缩成单个字母则该字母为一个答案。
如何实现压缩的过程?设计状态 $f(i,j,k)$ 为原串从位置 $i$ 到位置 $j$ 压缩成 $k$ 的可能性。$opt(l,r,t)$ 为 $l$ 和 $r$ 压缩成t的可能性,$opt$ 状态用来存储变换规则。同时,为了方便表示,把四个字母转化成数字。
转移方程为 $f(i,j,k) = f(i,d,x)\ \&\& \ f(d+1,j,y)\ \&\& \ opt(x,y,k)$ 形式上也是比较好理解的,两个字母压缩成一个字母,只要一个条件不存在,整个转移不存在。但是需要注意的是,如果不对三个条件进行判断,需要在转移时加上或运算符。因为可能出现一个压缩在第一次转移时判断为存在,但是在第二次转移时由于条件不符合而被判断为不存在。
#include<iostream> #include<cstring> using namespace std; int W, I, N, G; string ch; bool opt[10][10][10], f[205][205][205], flag; int change(char ch) { if (ch == 'W')return 1; if (ch == 'I')return 2; if (ch == 'N')return 3; if (ch == 'G')return 4; } int main(void) { ios::sync_with_stdio(false); cin.tie(0); cout.tie(0); cin >> W >> I >> N >> G; for (int i = 1; i <= W; i++) cin >> ch, opt[change(ch[0])][change(ch[1])][1] = 1; for (int i = 1; i <= I; i++) cin >> ch, opt[change(ch[0])][change(ch[1])][2] = 1; for (int i = 1; i <= N; i++) cin >> ch, opt[change(ch[0])][change(ch[1])][3] = 1; for (int i = 1; i <= G; i++) cin >> ch, opt[change(ch[0])][change(ch[1])][4] = 1; cin >> ch; for (int i = 1; i <= ch.size(); i++) f[i][i][change(ch[i - 1])] = 1; for (int L = 2; L <= ch.size(); L++) for (int i = 1, j = i + L - 1; j <= ch.size(); i++, j++) for (int k = i; k < j; k++) for (int r = 1; r <= 4; r++) for (int x = 1; x <= 4; x++) for (int y = 1; y <= 4; y++) f[i][j][r] |= (f[i][k][x] && f[k + 1][j][y] && opt[x][y][r]); if (f[1][ch.size()][1])cout << 'W', flag = 1; if (f[1][ch.size()][2])cout << 'I', flag = 1; if (f[1][ch.size()][3])cout << 'N', flag = 1; if (f[1][ch.size()][4])cout << 'G', flag = 1; if (!flag)cout << "The name is wrong!" << endl; return 0; }