大体题意:
给你一颗树,树上的边权是字母,给你一个长度为m 的字符串s,求出树上的两个点,使得这两个点之间的路 构成的字符串 s1,使得s是s1的子字符串(不一定连续),如果多解,任意给出两个节点,否则输出-1 -1?
思路:
比赛没有做出来,之后请教的同学,是一个树形dp。 思路很巧妙!
首先我们任选一个节点作为树根节点(不妨选1号节点吧)
我们可以给每一个节点设置为一个结构体,因为要存储许多信息,有从前往后匹配字符串的长度,和从后往前匹配的字符串长度,和左id,右id
然后从这个树根开始dfs,一直到最深处,在回溯的过程更新信息,如果当前节点u的子节点v 的左长度 + 1位置的s能够符合 边权的话,就要把这个状态向u节点转移,右长度同理。
什么时候合适了呢,就是做长度+右长度 大于等于 s的长度的话,就已经匹配成功了,没必要在回溯递归了,可以return了!
详细见代码:
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int maxn = 500000 + 10;
char s[maxn];
struct Node{
int l,r;
int lid,rid;
}dp[maxn];
vector<pair<int,char> > g[maxn];
int ans1,ans2;
bool ok;
int len;
void dfs(int u,int fa){
if (ok)return;
for (int i = 0; i < g[u].size(); ++i){
int v = g[u][i].first;
if (v == fa)continue;
dfs(v,u);
if (ok)return ;
int L,R;
if (s[dp[v].l+1] == g[u][i].second)L = dp[v].l + 1;
else L = dp[v].l;
if (s[len-dp[v].r] == g[u][i].second)R = dp[v].r + 1;
else R = dp[v].r;
if (L + dp[u].r >= len){
ok = 1;
ans1 = dp[v].lid;
ans2 = dp[u].rid;
return ;
}
if (R + dp[u].l >= len){
ok = 1;
ans1 = dp[u].lid;
ans2 = dp[v].rid;
return ;
}
if (L > dp[u].l){
dp[u].l = L;
dp[u].lid = dp[v].lid;
}
if (R > dp[u].r){
dp[u].r = R;
dp[u].rid = dp[v].rid;
}
}
}
int main(){
int n, m;
while(scanf("%d %d",&n, &m) == 2){
for (int i = 0; i <= n; ++i)g[i].clear();
for (int i = 1; i <= n; ++i){
dp[i].rid = dp[i].lid = i;
dp[i].l = dp[i].r = 0;
}
for (int i = 1; i < n; ++i){
int u,v;
char ch;
scanf("%d %d %c",&u, &v, &ch);
g[u].push_back(make_pair(v,ch));
g[v].push_back(make_pair(u,ch));
}
scanf("%s",s+1);
len = m;
ans1 = ans2 = -1;
ok = 0;
dfs(1,-1);
// for (int i = 1; i <= n; ++i){
// printf("%d %d %d %d\n",dp[i].l,dp[i].r,dp[i].lid,dp[i].rid);
// }
printf("%d %d\n",ans1,ans2);
}
return 0;
}