題目
小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題就可以了...