题目
小H 今天学习了「缓慢的路径寻找算法」,下课后便准备找一道题练习一下。题目是这样的:给定一张
有向图,每条边上都有一个小写英文字母,小H 需要寻找一条路径使得路径上出现最多的字母的出现次
数最大。然而小H 想了很久也只会jV j = 1 的情形,于是他找到了你,请你帮他解决这个问题。
Input
输入文件包含多组测试数据。
第一行一个整数T (1 T 105),表示测试数据的组数。
每组测试数据的第一行两个整数n, m (1 n 105, 0 m 2 105),分别表示有向图的点数和边数。
接下来m 行,每行两个整数ui, vi (1 ui; vi n) 和一个小写英文字母ci,表示从ui 到vi 有一条有向
边,上面的字母为ci。
保证
Σ
n 106;
Σ
m 2 106。
Output
对于每组测试数据,如果路径上出现最多的字母的出现次数可以是任意大,输出一行-1。
否则,在第一行依次输出一个整数ans,一个字母c 和一个整数k (1 k n),依次表示路径上出现最
多的字母的出现次数,达到最多出现次数的字母以及路径上的点数。
第二行输出k 个整数p1; p2; : : : ; pk (1 pi n),表示这条路径依次经过的点。
如果有多条满足条件的路径,输出任意一条。
Scoring
本题共有5 个测试点,每个测试点20 分。
测试点1:n;m 5。
测试点2:每组测试数据中的ci 均相同。
测试点3:T; n;m 100。
测试点4:保证图中不存在环。
测试点5:无特殊限制。
Page 2 of 5
CSP2019 Training jiangly Contest 1
High School Affiliated to Southwest University, Chongqing, 23 Oct 2019
Example
spfa.in spfa.out
3
1 0
1 1
1 1 a
4 6
1 2 i
1 3 a
1 4 k
2 3 i
2 4 o
3 4 i
0 a 1
1
-1
3 i 4
1 2 3 4
Note
在第一组数据中,只有一个点,没有边,所以唯一的路径就是[1],其中每个字母的出现次数都是0。
在第二组数据中,有一个点和它到自己的一条边,只需要选择路径[1; 1; 1; : : :],就能使得字母a 出现任
意多次。
题解
/*
容易想到这是一道dp题,且dp式子也可列出来dp[i][j]表示终点为i的路径其中最多出现字符串是j的次数
那么就可以直接用邻接表转移
那么起点是什么呢?应该是入度为0的点(这道题是Special Judge)
就可以用拓扑排序先把顺序记录下来,在进行dp
*/
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
#include <stack>
#include <queue>
using namespace std;
#define ll long long
const int MAXN = 1e6 +3;
int n , m;
bool vis[MAXN];
int dp[MAXN][30];
int pre[MAXN][30];
int in[MAXN];
struct node{
int v;char s;
node(){}
node( int V , char W ){
v= V;s = W;
}
};
int siz[MAXN] , cnt;
vector<node>G[MAXN];
void read( int &x ){
x = 0 ;char s = getchar();
while( s < '0' || s > '9' ){
s = getchar();
}
while( s >='0' && s <= '9' ){
x = x * 10 + s - '0';
s = getchar();
}
}
int tot , ans , last;
char c;
bool flag;
int ne[MAXN];
void TP( ){
queue<int>q;
for( int i = 1 ; i <= n ; i ++ ){
if( !in[i] )
q.push( i ) , ne[++cnt] = i;
}
while( !q.empty() ){
int x = q.front();q.pop();
for( int i = 0 ; i < G[x].size() ; i++ ){
int v = G[x][i].v;
if( --in[v] == 0 ){
q.push( v );
ne[++cnt] = v;
}
}
}
}
int sum[MAXN];
void print( int x , int y ){
if( x == pre[x][y] ){
tot ++;
sum[tot] = x;
return ;
}
print( pre[x][y], y );
tot ++;
sum[tot] = x;
}
int main()
{
//freopen( "spfa.in" , "r" , stdin );
//freopen( "spfa.out" , "w" , stdout );
int T;
scanf( "%d" , &T );
while( T -- ){
flag = 0;
scanf( "%d%d" , &n , &m );
for( int i = 1 ; i <= n ; i ++ ){
G[i].clear();
siz[i] = 0 , vis[i] =0 ;
sum[i] = 0;in[i] = 0;
for( int j = 1 ; j <= 26 ; j ++ )
pre[i][j] = i , dp[i][j] = 0;
}
tot = 0;
for( int i = 1 ; i <= m ; i ++ ){
int x , y;char p;
read( x);read( y );scanf( "%c" , &p );
G[x].push_back( node( y , p ) );
in[y] +=1;
}
tot = 0;
ans = 0 , last = 1;
c = 'a';
cnt = 0;
TP();
if( cnt < n ){
printf( "-1\n" );
continue;
}
for( int i = 1 ; i <= n ; i ++ ){
int x = ne[i];
for( int j = 0 ; j < G[x].size() ; j ++ ){
int v = G[x][j].v , s = G[x][j].s - 96;
for( int k = 1 ; k <= 26 ; k ++ ){
if( dp[v][k] < dp[x][k] + ( s == k ? 1 :0 ) ){
dp[v][k] = dp[x][k] + ( s == k ? 1 :0 );
pre[v][k] = x;
if( ans < dp[v][k] ){
ans = dp[v][k];c = k +96;
last = v;
}
}
}
}
}
printf( "%d %c" , ans , c );
if( !ans ){
printf( " 1\n1\n" );
continue;
}
else
print( last , c - 96 );
printf( " %d\n" , tot );
for( int i = 1 ; i <= tot ; i ++ )
printf( "%d " , sum[i] );
printf( "\n" );
}
return 0;
}
总结
这道题最开始想到的是tarjan判环,感觉也可以
但是核心是拓扑排序,这也是关于DAG的题的一种应该想到的思路
拓扑排序还未完全掌握,需要做一些练习
最后一周了,复习一下各种板子
再练一下dp题就可以了...