P4290 玩具取名题解
介绍一下这个题我自己的思路。
一、暴力
看完这个题,第一感觉就是把所有WING字母可以变形的形式枚举出来,然后直到目标序列出来或者枚举完全部的变形形式(结束条件是目前枚举的序列长度比目标序列长度大的时候)。那么可以用bfs做。经计算,如果每个字母只变成一种双字符的时候,每一层枚举长度大约为
\sum_{i=1}^{n}\frac{i(i+1)}{2}
那么时间复杂程度大约为O(n^3),但是这个只是每个字母变成一种序列,如果每个字母变成m个序列的话大约为O(mn^3),优化的话应该还是可以得到几分的。(多少分我就不知道了,有兴趣的可以自己打一遍试试)
二、动态规划
再重新看这一道题,如果按着上面暴力解法反着想的话,那么久可以把这一道题理解为:给你一个字母序,按规定可以将特定的两个连续字母合并成一个字母,问最后可以合并成哪一个单字母。
这里想后,我第一感觉就是可能类似于石子合并的题了。于是我便用区间dp试做了一下。
首先,区间dp先定义dp[l][r],即再区间[l,r]的dp值(l,r可能可以滚动数组)。但是这一题单只有l,r是不够的,因为每一个字母都可能会合并成区间[l,r],那么就可以添加一维,dp[l][r]['c'],即在区间[l,r]中是否可以合并成c这一个字母。
那么可以得出状态转移方程:
dp[i][j]['c']=dp[i][k]['cl'] & dp[k+1][j]['cr']
其中[i,k]与[k+1,j]为[i,j]的子集,[i,k]∪[k+1,j]=[i,j](废话),‘cl’与‘cr’分别为字符‘c’可以变成的某一个双字符的左右字符。
数组的初始化为:
dp[i][i][s[i]]=1
其中s[i]为读入字符串的第i个字符。(即自己可以变成自己)
这个状态方程的意思就是如果在[i,k],[k+1,j]区间中可以分别转化为cl与cr的话,那么[i,j]就可以由c转化成,其中k可以在[i,j]中枚举。
举个例子,如样例中,初始化为dp[1~4][1~4]['I']=1,在区间i=1,j=2中,dp[1][2]['W']=dp[1][1]['I']=1,同理,在区间dp[3][4]['W']=1,那么i=1,j=4的中,dp[1][4]['N']=dp[1][2]['W']&dp[3][4]['W']=1,因此N为可行解。
算法维护:
-
由于字母转化成数组维度有点麻烦(可以用map或者直接用字母的ASCII来当数组下标)每个字母可以分别映射的数字,即字符W,I,N,G可以分别对应1,2,3,4
-
每个字符变形的双字符可以用邻接表储存
-
另外要强调,在读入字符时,要小心读入到' ' '\n'(即空格与回车换行)
代码(未剪枝50分):
//n为字符串长度
for(int i=0;i<n;i++)
for(int j=i-1;j>=0;j--)//枚举区间
for(int g=1;g<=4;g++)//枚举字母
{
//****标记点****
for(int m=head[g];m;m=que[m].nx)
{
l=que[m].a;r=que[m].b;//a,b分别为左字母右字母
for(int k=j;k<i;k++)//枚举子集
dp[j][i][g]|=(dp[j][k][l]&dp[k+1][i][r]);
}
}
tips:由于我写的是j>i,所以是区间[j,i],如果觉得难看可以让i从n-1枚举到0,j=i+1,j<n,那么就是i>j,是区间[i,j]了;
很明显时间复杂大约也为O(mn^3),还是可能会超时的,但比起暴力枚举好了很多。我们可以进行剪枝,如果g已经可以变成区间[i][j]字符串了,那么针对g就不用继续枚举这个区间了。
即在本代码标记点处加入 if(dp[j][i][g]) continue;
AC总代码(仅供参考):
#include <bits/stdc++.h>
using namespace std;
#define ll int
#define pc(a) putchar(a)
#define rg register
const ll maxn=1100;
void gc(char &a)
{
a=getchar();
while(a==' '||a=='\n') a=getchar();
}
ll read(){
char c;ll x=0;bool flag=0;gc(c);
while(c<'0'||c>'9'){if(c=='-') flag=1;gc(c);}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48),c=getchar();}
return flag?-x:x;
}
void pr(ll x){
if(x<0){x=-x;pc('-');}
if(x>9) pr(x/10);
pc(x%10+48);
}
//----快读区域-----
struct edge
{
char a,b;
ll nx;
edge(char a,char b,ll nx):a(a),b(b),nx(nx){}
edge(){}
}que[80000];//邻接表
ll head[5],en;
map<char,ll> p;//用来更改WING的值
void edgepush(int i,char a,char b)//建边
{
que[++en]=edge(p[a],p[b],head[i]);
head[i]=en;
}
bool dp[maxn][maxn][5];//精髓
ll n,a[5],l,r;
string s;//读入字符
char u,v;
bool flag;//用来判断是否有字符输出
int main()
{
p['W']=1;p['I']=2;p['N']=3;p['G']=4;
for(int i=1;i<=4;i++)
a[i]=read();
for(int i=1;i<=4;i++)
for(int j=1;j<=a[i];j++)
{
gc(u);gc(v);
edgepush(i,u,v);
}
cin>>s;n=s.size();
for(int i=0;i<n;i++)
dp[i][i][p[s[i]]]=1;
for(int i=0;i<n;i++)
for(int j=i-1;j>=0;j--)
for(int g=1;g<=4;g++)
{
if(dp[j][i][g]) continue;
for(int m=head[g];m;m=que[m].nx)
{
l=que[m].a;r=que[m].b;
for(int k=j;k<i&&!dp[j][i][g];k++)
dp[j][i][g]|=(dp[j][k][l]&dp[k+1][i][r]);
}
}
if(dp[0][n-1][1]){pc('W');flag=1;}
if(dp[0][n-1][2]){pc('I');flag=1;}
if(dp[0][n-1][3]){pc('N');flag=1;}
if(dp[0][n-1][4]){pc('G');flag=1;}
if(!flag) puts("The name is wrong!");
return 0;
}