Codeforces Round #460 (Div. 2) D. Substring 拓扑排序+DP | 记忆化搜索

D. Substring
time limit per test
3 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

You are given a graph with n nodes and m directed edges. One lowercase letter is assigned to each node. We define a path's value as the number of the most frequently occurring letter. For example, if letters on a path are "abaca", then the value of that path is 3. Your task is find a path whose value is the largest.

Input

The first line contains two positive integers n, m (1 ≤ n, m ≤ 300 000), denoting that the graph has n nodes and m directed edges.

The second line contains a string s with only lowercase English letters. The i-th character is the letter assigned to the i-th node.

Then m lines follow. Each line contains two integers x, y (1 ≤ x, y ≤ n), describing a directed edge from x to y. Note that x can be equal to y and there can be multiple edges between x and y. Also the graph can be not connected.

Output

Output a single line with a single integer denoting the largest value. If the value can be arbitrarily large, output -1 instead.

Examples
input
5 4
abaca
1 2
1 3
3 4
4 5
output
3
input
6 6
xzyabc
1 2
3 1
2 3
5 4
4 3
6 4
output
-1
input
10 14
xzyzyzyzqx
1 2
2 4
3 5
4 5
2 6
6 8
6 5
2 10
3 9
10 9
4 6
1 10
2 8
3 7
output
4
Note

In the first sample, the path with largest value is 1 → 3 → 4 → 5. The value is 3 because the letter 'a' appears 3 times.

题意:给定一个有向图,每一个顶点有一个字母,求在所有路径中的某条路径上出现次数最多的字母的次数。若图中有环,输出-1。

思路1:构造一个dp[i][j],代表在第i个顶点处可以得到多少字母j(0~25),并初始化为0。利用拓扑排序,从入度为0的点开始搜起,不断扩展直到遍历完所有的点。若图中有环则不能遍历成功,输出-1。

#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5+10;
int n,m,dp[maxn][30],vert[maxn];
int head[maxn],cnt,deg[maxn];   //head为链式前向星存图的组件,cnt记录点数,deg记录每个点的入度
char a[maxn];

struct node{
    int to,next;
}edge[maxn*2];

void addedge(int u, int v){
    edge[cnt].to=v;
    edge[cnt].next=head[u];
    head[u]=cnt++;
}
//拓扑排序
int topo()
{
    queue<int> q;
    for (int i=1;i<=n;i++){
        if (!deg[i]){
            q.push(i);
            dp[i][vert[i]]++; //将每个点对应的字母处设置为1
        }
    }
    int vcnt=0,res=0;
    while (!q.empty()){
        int u=q.front();
        q.pop();
        vcnt++;
        for (int i=head[u];~i;i=edge[i].next){
            int v=edge[i].to;
            deg[v]--;
            if (!deg[v])
                q.push(v);
            for (int j=0;j<26;j++){
                dp[v][j]=max(dp[v][j],dp[u][j]+(vert[v]==j));
                res=max(res,dp[v][j]);
            }
        }
    }
    if (vcnt<n)
        return -1;
    return res;
}


int main()
{
    memset(head,-1,sizeof(head));
    cnt=0;

    scanf("%d %d",&n,&m);
    char c;
    for (int i=1;i<=n;i++){
        scanf(" %c",&c);
        vert[i]=c-'a';
    }

    for (int i=0;i<m;i++){
        int u,v;
        scanf("%d %d",&u,&v);
        addedge(u,v);
        deg[v]++;
    }
    int ans=topo();
    printf("%d\n",ans);

    return 0;
}
思路2:记忆化搜索。dp数组的意义与上述方法相同。详见代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5+10;
vector<int> G[maxn];
int dp[maxn][26];
int n,m;
int vert[maxn];

//记忆化搜索
int dfs(int u,int c)
{
    if (dp[u][c]==-2){      //若存在环,输出-1
        puts("-1");
        exit(0);
    }

    if (dp[u][c]!=-1)       //若对应的状态之前已经搜索过,则返回之前搜索的结果
        return dp[u][c];

    dp[u][c]=-2;    //标记为已经访问过改点,若之后搜索时图中有环返回到该点时,则会根据该标记退出搜索
    int res=0;
    for (int i=0;i<G[u].size();i++){
        int v=G[u][i];
        int tmp=dfs(v,c);
        res=max(res,tmp);
    }
    if (vert[u]==c)
        res++;

    return dp[u][c]=res;        //保存更新的结果并返回值
}

int main()
{
    memset(dp,-1,sizeof(dp));       //dp的初始值为-1,代表该状态还未访问过
    scanf("%d %d",&n,&m);
    char c;
    for (int i=1;i<=n;i++){
        scanf(" %c",&c);
        vert[i]=c-'a';
    }
    for (int i=0;i<m;i++){
        int v,u;
        scanf("%d %d",&u,&v);
        G[u].push_back(v);
    }
    int ans=0;
    for (int i=1;i<=n;i++){
        for (int j=0;j<26;j++){
            ans=max(ans,dfs(i,j));
        }
    }
    printf("%d\n",ans);

    return 0;
}
有趣的是,如果将代码稍加改动(详见下面),代码花费的时间会增大一倍,以至于做题时会在某个样例TLE,至于原因是什么不太懂,各位看官可以亲自试试哟~若知道这个问题的原因也欢迎留言,互相交流学习~
#include <bits/stdc++.h>
using namespace std;
const int maxn = 3e5+10;
vector<int> G[maxn];
int dp[maxn][26];
int n,m;
int vert[maxn];

//记忆化搜索
int dfs(int u,int c)
{
    if (dp[u][c]==-1){      //若存在环,输出-1
        puts("-1");
        exit(0);
    }

    if (dp[u][c]!=0)       //若对应的状态之前已经搜索过,则返回之前搜索的结果
        return dp[u][c];

    dp[u][c]=-1;    //标记为已经访问过改点,若之后搜索时图中有环返回到该点时,则会根据该标记退出搜索
    int res=0;
    for (int i=0;i<G[u].size();i++){
        int v=G[u][i];
        int tmp=dfs(v,c);
        res=max(res,tmp);
    }
    if (vert[u]==c)
        res++;

    return dp[u][c]=res;        //保存更新的结果并返回值
}

int main()
{ //此时dp的初值为0
    scanf("%d %d",&n,&m);
    char c;
    for (int i=1;i<=n;i++){
        scanf(" %c",&c);
        vert[i]=c-'a';
    }
    for (int i=0;i<m;i++){
        int v,u;
        scanf("%d %d",&u,&v);
        G[u].push_back(v);
    }
    int ans=0;
    for (int i=1;i<=n;i++){
        for (int j=0;j<26;j++){
            ans=max(ans,dfs(i,j));
        }
    }
    printf("%d\n",ans);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值