复试机试常用算法复习

kmp

子串
kmp模板,偷懒直接find()了 0.0

#include 
using namespace std;
int main() {
	int n ;
	string s , t ;
	cin >> n >> t ; 
	vector<int>v ;
	for( int k = 2 ; k <= 16 ; k++ ) {
		s = "" ;
		for( int i = 1 ; i <= n ; i++ ) {
			int tmp = i ;
			while( tmp ) {
				v.push_back( tmp % k ) ;
				tmp /= k ;
			}
			for( int i = v.size() - 1 ; i >= 0 ; i-- ) {
				if( v[i] >= 10 ) {
					s += (char)( v[i] - 10 + 'A' ) ;
				} else {
					s += (char)( v[i] + '0' ) ;
				}
			}
			v.clear() ;
		}
		int pos = s.find(t) ;
		if( pos >= 0 && pos < s.size() ) {
			cout << "yes" << endl ;
			return 0 ;
		}
	}
	cout << "no" << endl;
	return 0 ;
}

栗酱的数列
思路:kmp+差分
满足( a1 + b1 ) % k == ( a2 + b2 ) % k , 与满足 ( a2 - a1 == b1 - b2 ) 一样
转换之后套kmp,找总共几个串满足

#include 
using namespace std;
#define LL long long 
const int AX = 2e5 + 666 ;
LL a[AX] ;
LL b[AX] ;
int nxt[AX] ;
int res , n , m ; 
void get_next(){
	nxt[0] = -1 ;
	int j = 0 , k = -1 ; 
	while( j < m ){
		if( k == -1 || b[k] == b[j] ){
			nxt[++j] = ++k ;
		}else{
			k = nxt[k] ; 
		}
	}
}

void kmp(){
	int i = 0 , j = 0 ;
	while( i < n && j < m ){
		if( j == -1 || a[i] == b[j] ){
			i ++ ;
			j ++ ;
		}else j = nxt[j] ; 
		if( j == m ){   // 
			res ++ ;
			j = nxt[j] ; 
		}
	}
}

int main(){
	int T ;
	LL	k ;
	cin >> T ;
	while( T-- ){
		res = 0 ;
		memset( nxt , 0 ,sizeof(nxt) ) ;
		cin >> n >> m >> k ;
		for( int i = 0 ; i < n ; i++ ){
			cin >> a[i] ;
		}
		for( int i = 0 ; i < m ; i++ ){
			cin >> b[i] ;
		}
		for( int i = 1 ; i < n ; i++ ){
			a[i-1] = ( ( a[i-1] - a[i] ) % k + k ) % k ;
		}
		for( int i = 1 ; i < m ; i++ ){
			b[i-1] = ( ( b[i] - b[i-1] ) % k + k ) % k ;
		}
		m --;
		n --;
		get_next();
		kmp();
		cout << res << endl ;
	}
	return 0 ;
}

manacher

回文串
Manacher 模板

#include 
using namespace std;
const int AX = 3e3 + 666 ;
char s[AX] ;
int p[AX] ;
int main(){
	while( ~scanf("%s",s) ){
		int len = strlen(s) ;
		for( int i = len ; i >= 0 ; i-- ){
			s[2*i+1] = '#' ;
			s[2*i+2] = s[i] ;
		}
		s[0] = '$' ;
		int id = 0 , mx = 0 ; 
		len *= 2 ; 
		memset( p , 0 , sizeof(p) );
		int res = 0 ;
		for( int i = 2 ; i <= len ; i++ ){
			if( p[id] + id > i ) p[i] = min( p[id] + id - i , p[2*id-i] );
			else p[i] = 1 ;
			while( s[i-p[i]] == s[i+p[i]] ) p[i]++ ;
			if( p[i] + i > mx ){
				mx = p[i] + i ; 
				id = i ;
			}
			res = max( res , p[i] );
		}
		printf("%d\n",res-1);
	}
	return 0 ;
}

最长回文
思路:要求两个串的子串拼起来为回文 的最大长度,首先分别对两个串求最长回文,枚举回文中心,然后向两边扩展,A的左边 == B的右边,就扩展。

#include 
using namespace std;
const int AX = 2e5 + 666 ;
char a[AX] , b[AX] ;
int pa[AX] , pb[AX] ;
int n ;
void manacher( char *s , int *p ){
	for( int i = n ; i >= 0 ; i-- ){
		s[2*i+2] = s[i] ;
		s[2*i+1] = '#' ;
	}
	s[0] = '$';
	int id = 0 , mx = 0 ; 
	for( int i = 2 ; i <= 2 * n ; i++ ){
		if( p[id] + id > i ) p[i] = min( p[id] + id - i , p[2*id-i] ) ;
		else p[i] = 1 ;
		while( s[i+p[i]] == s[i-p[i]] ) p[i] ++ ;
		if( i + p[i] > mx ){
			mx = p[i] + i ; 
			id = i ;
		}
	}
}
int main() {
	cin >> n ;
	cin >> a >> b ;
	manacher( a , pa ); 
	manacher( b , pb );
	int res = 0 ; 
	for( int i = 2 ; i <= 2 * n ; i++ ){
		int tmp = max( pa[i] , pb[i-2] );
		while( a[i-tmp] == b[i+tmp-2] ) tmp ++ ;
		res = max( res , tmp );
	}
	cout << res - 1 << endl;
	return 0 ;
}

Tarjan

可达性

/*
求出若干个强连通分量后,进行缩点:
    将每个强连通分量看做DAG图中的一个点,建立各点之间的边(本题直接统计各点(连通分量)入度)
    入度为0的强连通分量点集中取最小的点 构成所求集合
*/
#include 
using namespace std ;
const int AX = 1e5 + 666 ;
struct Node {
	int u , v , nxt ;
	Node() {}
	Node( int u , int v , int nxt ):u(u),v(v),nxt(nxt) {}
} G[AX];
vector<int>res ;
int head[AX] ;
int dfn[AX] ;
int low[AX] ;
int vis[AX] ;
int scc_id[AX] ;
int tot , cnt ;
int scc ; 
int in[AX] ;
vector<int>id[AX] ;  // 强连通分量集合
void add( int u , int v ) {
	G[tot] = Node( u , v , head[u] ) ;
	head[u] = tot ++ ;
}
stack<int>s;
void Tarjan( int u ) {
	dfn[u] = low[u] = ++cnt ;
	s.push(u);
	vis[u] = 1 ;
	for( int i = head[u] ; ~i ; i = G[i].nxt ) {
		int v = G[i].v ;
		if( !dfn[v] ) {
			Tarjan( v ) ;
			low[u] = min( low[u] , low[v] ) ;
		}else if( vis[v] ){
			low[u] = min( low[u] , low[v] ) ;
		}
	}
	if( dfn[u] == low[u] ) {
		int v = 0 ;
		while( u != v ){
			vis[s.top()] = 0 ;
			v = s.top() ; 
			scc_id[v] = scc ; 
			id[scc].push_back(v);
			s.pop() ; 
		}
		scc ++ ; 
	}
}
int main() {
	int n , m , x , y ;
	scanf("%d%d",&n,&m);
	cnt = tot = 0 ;
	scc = 0 ;
	memset( head , -1 , sizeof(head) ) ;
	memset( dfn , 0 , sizeof(dfn) ) ;
	memset( vis , 0 , sizeof(vis) ) ;
	memset( in , 0 , sizeof(in) ) ;
	for( int i = 0 ; i < m ; i++ ) {
		scanf("%d%d",&x,&y);
		add( x , y );
	}
	for( int i = 1 ; i <= n ; i++ ) {
		if( !dfn[i] ) {
			Tarjan( i ) ;
		}
	}
	
	for( int i = 1 ; i <= n ; i++ ){
		for( int j = head[i] ; ~j ; j = G[j].nxt ){
			if( scc_id[G[j].v] != scc_id[i] ){
				in[scc_id[G[j].v]]++ ; 		// 缩点
			}
		}
	}
	for( int i = 0 ; i < scc ; i++ ){
		sort( id[i].begin() , id[i].end() );
	}
	for( int i = 0 ; i < scc ; i++ ){
		if( !in[i] ) res.push_back(id[i][0]); // 入度0的最小点
	}
	sort( res.begin() , res.end() );
	int len = res.size() ;
	printf("%d\n",len);
	for( int i = 0 ; i < len ; i++ ) {
		printf("%d ",res[i]);
	}
	printf("\n");
	return 0 ;
}

Blockade
思路:去掉割点会造成若干个集合之间的点不能连接,去掉普通的点只会造成1个点与其余所有点不能连接。
故本题关键求割点对答案的贡献,有三部分:
一是和普通点一样,去掉的点与其余各点( n-1 ) * 2
二是去掉割点后,割点的 每个 子连通块与其余节点的数对
三是去掉割点后,割点所有上部的节点其余节点的数对

#include 
#define LL long long 
using namespace std;
const int AX = 1e5 + 66 ;
int head[AX] ;
int dfn[AX] ;
int low[AX] ; 
LL res[AX] ; 
int num[AX] ;
int tot , cnt ;
int n , m ;
struct Node{
	int v , nxt ; 
	Node(){}
	Node( int v , int nxt ):v(v),nxt(nxt){}
}G[1000010] ;
void add( int u , int v ){
	G[tot] = Node( v , head[u] ) ;
	head[u] = tot ++ ;
	G[tot] = Node( u , head[v] ) ;
	head[v] = tot ++ ;
}

void Tarjan( int u , int pre ){
	dfn[u] = low[u] = ++cnt ;
	res[u] += 2LL * ( n - 1 ) ; // 1. 被去掉的点到其他各点
	num[u] = 1 ; 
	LL sum = 0 ;
	for( int i = head[u] ; ~i ; i = G[i].nxt ){
		int v = G[i].v ; 
		if( !dfn[v] ){
			Tarjan( v , u ) ;
			
			num[u] += num[v] ;	// u节点及子树节点数目
			
			low[u] = min( low[u] , low[v] ) ;
			
			if( low[v] >= dfn[u] ){ // u为割点
				res[u] += 1LL * num[v] * ( n - num[v] - 1 ) ;  // 2. 割点去除后的每个子连通块同其他连通块的数对
				
				sum += num[v] ; // 割点子树节点数目
			}
			
		}else if( v != pre ){
			low[u] = min( low[u] , dfn[v] );
		}
	}
	res[u] += 1LL * sum * ( n - sum - 1 ) ; // 3.sum为割点子树的所有点构成的连通块节点数目(tarjan在求割点的过程中是一棵搜索树往下遍历)
}

int main() {
	int u , v ;
	memset( head , -1 , sizeof(head) ) ;
	memset( res , 0 , sizeof(res) );
	memset( low , 0 , sizeof(low) );
	memset( dfn , 0 , sizeof(dfn) );
	memset( num , 0 , sizeof(num) );
	tot = cnt = 0 ; 
	scanf("%d%d",&n,&m);	
	for( int i = 0 ; i < m ; i++ ) {
		scanf("%d%d",&u,&v);
		add( u , v ) ;
	}
	Tarjan( 1 , 0 ) ;
	for( int i = 1 ; i <= n ; i++ ){
		printf("%lld\n",res[i]);
	}
	return 0 ;
}

最小生成树

1.道路建设
思路:模板

#include 
#define INF 0x3f3f3f3f
using namespace std;
const int AX = 2e2 + 66 ;
int a[AX][AX];
int dis[AX] ;
int vis[AX] ;
int c , n , m , x , y , w ;
int prim() {
	int res = 0 ;
	memset( dis , 0x3f , sizeof(dis) ) ;
	memset( vis , 0 , sizeof(vis) ) ;
	dis[1] = 0 ;
	for( int i = 1 ; i <= n ; i++ ) {
		int minu = INF , id = 1 ;
		for( int j = 1 ; j <= n ; j++ ) {
			if( !vis[j] && minu > dis[j] ) {
				id = j ;
				minu = dis[j] ;
			}
		}
		if( minu == INF ) return -1 ;
		res += minu ;
		vis[id] = 1 ;
		for( int j = 1 ; j <= n ; j++ ) {
			if( !vis[j] && dis[j] > a[id][j] ) {
				dis[j] = a[id][j] ;
			}
		}
	}
	return res ;
}
int main() {
	while( ~scanf("%d%d%d",&c,&m,&n) ) {
		memset( a , 0x3f , sizeof(a) ) ;
		for( int i = 0 ; i < m ; i++ ) {
			scanf("%d%d%d",&x,&y,&w);
			a[x][y] = min( a[x][y] , w ) ;
			a[y][x] = a[x][y] ;
		}
		int res = prim();
		//cout << res << endl;
		if( res != -1 && res <= c ) printf("Yes\n");
		else printf("No\n");
	}
	return 0 ;
}

2.[HAOI2006]聪明的猴子
思路:找最小生成树的最大边。

#include 
#define INF 0x3f3f3f3f
#define eps 1e-10
using namespace std ;
const int AX = 1e3 + 66 ;
struct Node {
	double x , y ;
} G[AX] ;
int n , m ;
double get_dis( Node a , Node b ) {
	return sqrt( ( a.x - b.x ) * ( a.x - b.x ) + ( a.y - b.y ) * ( a.y - b.y ) ) ;
}
double a[AX] ;
double dis[AX] ;
int vis[AX] ;
double ans ;
void prim() {
	for( int i = 0 ; i < n ; i++ ){
		dis[i] = INF ;
	}
	memset( vis , 0 , sizeof(vis) ) ;
	dis[0] = 0 ;
	for( int i = 1 ; i <= n ; i++ ) {
		double minu = INF ;
		int id = 0 ;
		for( int j = 0 ; j < n ; j++ ) {
			if( !vis[j] && minu > dis[j] ) {
				minu = dis[j] ;
				id = j ;
			}
		}
		if( minu == INF ) return ;
		
		if( minu > ans ) {
			ans = minu ; 
		}
		
		vis[id] = 1 ;
		for( int j = 0 ; j < n ; j++ ) {
			double tmp = get_dis( G[id] , G[j] ) ;
			if( !vis[j] && dis[j] > tmp ) {
				dis[j] = tmp ;
			}
		}
	}
}
int main() {
	scanf("%d",&m);
	for( int i = 0 ; i < m ; i++ ) {
		scanf("%lf",&a[i]) ;
	}
	scanf("%d",&n);
	for( int i = 0 ; i < n ; i++ ) {
		scanf("%lf%lf",&G[i].x,&G[i].y);
	}
	ans = 0 ;
	prim();
	int res = 0 ; 

	for( int i = 0 ; i < m ; i++ ){
		if( a[i] >= ans ) res ++ ; 
	}
	printf("%d\n",res);
	return 0 ;
}

货车运输
思路:最大生成树+LCA
先求出最大生成树,再通过Tarjan算法,利用minu[x]记录点x到其 目前集合根节点 路径中 边的最小值
每次访问完以u为根的树后,处理查询(x,y),且处理的查询满足 x , y 的LCA都为u
其路径中最短边长度是 合并集合后 的 min(minu[x],minu[y])

#include 
#define INF 0x3f3f3f3f
using namespace std;
typedef pair<int,int>P;
const int AX = 5e4 + 66 ;
int n , m ;
int tot ;
struct Edge {
	int u , v , w ;
	Edge( int u , int v , int w ):u(u),v(v),w(w) {}
	bool operator < ( const Edge &a )const {
		return w > a.w ;
	}
};
struct Node {
	int u , v , w , nxt ;
	Node() {}
	Node( int u , int v , int w , int nxt ):u(u),v(v),w(w),nxt(nxt) {}
} G[AX] ;

struct RES{
	int x , y , id ;
	RES( int x , int y , int id ):x(x),y(y),id(id){}
};
vector<RES>t_u[AX] ;
vector<Edge>e;
vector<P>que[AX] ;
int res[AX] ; 
int head[AX] ; 
int vis[AX] ;
int pre[AX] ;
int minu[AX] ;
void add( int u , int v , int w ){
	G[tot] = Node( u , v , w , head[u] ) ; head[u] = tot ++ ;
	G[tot] = Node( v , u , w , head[v] ) ; head[v] = tot ++ ;
}

int find( int x ){
	//return x == pre[x] ? x : pre[x] = find(pre[x]) ; 
	int tmp = pre[x] ; 
	if( x != tmp ){
		pre[x] = find(pre[x]);
	} 
	minu[x] = min( minu[x] , minu[tmp] );
	return pre[x] ; 
}
void mix( int x , int y ){
	x = find(x) ;
	y = find(y) ;
	if( x != y ){
		pre[x] = y ;
	}
}
void Tarjan( int u ){
	vis[u] = 1 ; 
	for( int i = head[u] ; ~i ; i = G[i].nxt ){
		int v = G[i].v ;
		if( vis[v] ) continue ; 
		Tarjan(v) ;
		minu[v] = G[i].w ; 
		pre[v] = u ;
	}
	for( int i = 0 ; i < (int)que[u].size() ; i++ ){ // 访问完以u为根的所有子树后,处理与u有关的询问 
		int v = que[u][i].first ; 
		int id = que[u][i].second ; 
		if( !vis[v] ) continue ;
		t_u[find(v)].push_back(RES(u,v,id));		// 与u相关的查询(u,v),加入到find(v)集合去处理
 	}
	for( int i = 0 ; i < t_u[u].size() ; i++ ){ // 处理所有LCA为u的查询(x,y),其路径中最短边长度是合并集合后的min(minu[x],minu[y])
		int id = t_u[u][i].id ; 
		int x = t_u[u][i].x ;
		int y = t_u[u][i].y ;
		find(x) ; find(y) ;
		res[id] = min( minu[x] , minu[y] );
	}
}
int main() {
	int x , y , w , q ;
	tot = 0 ;
	scanf("%d%d",&n,&m);
	for( int i = 1 ; i <= n ; i++ ){
		pre[i] = i ;
	}
	memset( head , -1 , sizeof(head) );
	memset( res , -1 , sizeof(res) ) ;
	memset( vis , 0 , sizeof(vis) ) ;
	while( m-- ) {
		scanf("%d%d%d",&x,&y,&w);
		e.push_back(Edge(x,y,w));
		e.push_back(Edge(y,x,w));
	}
	scanf("%d",&q);
	for( int i = 0 ; i < q ; i++ ) {
		scanf("%d%d",&x,&y);
		que[x].push_back(P(y,i));
		que[y].push_back(P(x,i));
	}
	sort( e.begin() , e.end() ) ;
	for( int i = 0 ; i < (int)e.size() ; i++ ){
		int u = e[i].u ;
		int v = e[i].v ; 
		if( find(u) != find(v) ){
			mix( u ,v ) ; 
			add( u , v , e[i].w );
		}
	}
	for( int i = 1 ; i <= n ; i++ ){
		pre[i] = i ;
	}
	memset( minu , 0x3f , sizeof(minu) );
	Tarjan(1);
	for( int i = 0 ; i < q ; i++ ){
		printf("%d\n",res[i]);
	}
	return 0 ;
}

【注】:这个题目求任意两点路径中的最小值,最大值同理。
另外,这个思路可以作为求次小生成树的更优解法。常规做法见次小生成树 , 利用这题的思路时间上可以更优: 枚举不在最小生成树中的边,查询生成树中x到y路径中的最大边长度,去除此最大长度,加上边,取最小值

最短路

Travel
思路:点u到点v有两种方式:
一是不经过传送,只在环上逆时针或者顺时针走
二是会用到传送门(经过传送点x最短路径就是u到x最短路+x到v最短路
两种情况取最小值就是答案

#include 
#define LL long long
#define INF 0x3f3f3f3f3f3f3f3f
using namespace std;
typedef pair<LL,int>P;
const int AX = 6e4 + 666 ;
int tot ;
int head[AX] ;
LL dis[50][AX] ;
LL pre[AX];
struct Node {
	int u , v , nxt ;
	LL w ;
	Node() {}
	Node( int u , int v , LL w , int nxt ):u(u),v(v),w(w),nxt(nxt) {}
} G[AX<<1];
int vis[AX] ;

void add( int u , int v , LL w ) {
	G[tot] = Node( u , v , w , head[u] ) ;
	head[u] = tot++ ;
	G[tot] = Node( v , u , w , head[v] ) ;
	head[v] = tot++ ;
}
void dijstra( int id ) {
	memset( vis , 0 ,sizeof(vis) ) ;
	int s = t[id] ;
	dis[id][s] = 0 ;
	priority_queue<P,vector<P>, greater<P> >q ;
	q.push(P(0,s)) ;
	while( !q.empty() ) {
		P tmp = q.top() ;
		q.pop() ;
		int u = tmp.second ;
		if( vis[u] ) continue ;
		vis[u] = 1 ;
		for( int i = head[u] ; ~i ; i = G[i].nxt ) {
			int v = G[i].v ;
			if( !vis[v] && dis[id][v] > dis[id][u] + G[i].w ) {
				dis[id][v] = dis[id][u] + G[i].w ;
				q.push(P(dis[id][v],v));
			}
		}
	}
}
int main() {
	int n , m , x , y ,q ;
	LL w ;
	scanf("%d%d",&n,&m);
	tot = 0 ;
	memset( head , -1 , sizeof(head) ) ;
	memset( dis , 0x3f , sizeof(dis) ) ;
	pre[0] = 0 ;
	for( int i = 1 ; i <= n ; i++ ) {
		scanf("%d",&x) ;
		add( i , ( i % n ) + 1 , x ) ;
		pre[i] = pre[i-1] + x ;
	}
	map<int,int>mp;
	while( m-- ) {
		scanf("%d%d%lld",&x,&y,&w) ;
		if( x == y ) continue ;
		add( x , y , w ) ;
		if( !mp[x] ) t.push_back(x);
		if( !mp[y] ) t.push_back(y);
		mp[x] = 1 ;
		mp[y] = 1 ;
	}
	int len = t.size() ;
	for( int i = 0 ; i < len ; i++ ) {  // 求出带传送的点到其余各点距离
		dijstra( i ) ;
	}
	scanf("%d",&q);
	while( q-- ) {
		scanf("%d%d",&x,&y);
		LL res = min( abs(pre[y-1] - pre[x-1]), pre[n] - abs(pre[y-1] - pre[x-1]) ) ; // 不经过传送,顺逆时针
		for( int i = 0 ; i < len ; i++ ) {
			res = min( res , dis[i][x] + dis[i][y] ) ;  // 经过传送
		}
		printf("%lld\n",res);
	}
	return 0 ;
}

最短路
思路:有负权无负环,用SPFA

#include 
using namespace std;
const int AX = 2e4 + 666 ;
int n , m ;
struct Node{
	int to , w ; 
	Node( int to , int w ):to(to),w(w){}
};
vector<Node>G[AX] ;
int vis[AX] ; 
int dis[AX] ; 
void spfa(){
	queue<int>q;
	q.push(1);
	vis[1] = 1 ;
	memset( dis , 0x3f , sizeof(dis) ) ;
	dis[1] = 0 ; 
	while( !q.empty() ){
		int u = q.front() ;
		q.pop() ; 
		vis[u] = 0 ; 
		for( int i = 0 ; i < G[u].size() ; i++ ){
			int v = G[u][i].to ; 
			if( dis[v] > dis[u] + G[u][i].w ){
				dis[v] = dis[u] + G[u][i].w ;
				if( !vis[v] ){
					vis[v] = 1 ;
					q.push(v);
				}
			}
		}
	}
}
int main(){
	scanf("%d%d",&n,&m);
	int x , y , w ; 
	while( m-- ){
		scanf("%d%d%d",&x,&y,&w);
		G[x].push_back(Node(y,w));
	}
	memset( vis , 0 , sizeof(vis) ) ;
	spfa();
	for( int i = 2 ; i <= n ; i++ ){
		printf("%d\n",dis[i]);
	}
	return 0 ; 
}

旅行
思路:枚举中间点,求到各点距离,取两段长度和的最大值。

#include 
#define INF 0x3f3f3f3f
using namespace std;
typedef pair<int,int>P;
const int AX = 1e3 + 66;
struct Node {
	int v , w , nxt ;
	Node() {}
	Node( int v , int w , int nxt ):v(v),w(w),nxt(nxt) {}
} G[AX<<1] ;
int head[AX] ;
int vis[AX] ;
int tot ;
int dis[AX] ;
vector<int>ans ;
void add( int u , int v , int w ) {
	G[tot] = Node( v , w , head[u] ) ;
	head[u] = tot ++ ;
	G[tot] = Node( u , w , head[v] ) ;
	head[v] = tot ++ ;
}
bool cmp( int a , int b ) {
	return a > b ;
}
int n , m , x , y , w ;
int dijstra( int s ) {
	priority_queue< P , vector<P> , greater<P> >q ;
	q.push(P(0,s)) ;
	memset( vis , 0 , sizeof(vis) ) ;
	memset( dis , 0x3f , sizeof(dis) ) ;
	ans.clear() ;
	dis[s] = 0 ;
	while( !q.empty() ) {
		P tmp = q.top() ;
		q.pop() ;
		int u = tmp.second ;
		vis[u] = 1 ;
		for( int i = head[u] ; ~i ; i = G[i].nxt ) {
			int v = G[i].v ;
			if( !vis[v] && dis[v] > dis[u] + G[i].w ) {
				dis[v] = dis[u] + G[i].w ;
				q.push(P(dis[v],v));
			}
		}
	}
	for( int j = 1 ; j <= n ; j++ ) {
		if( j != s && dis[j] < INF )
			ans.push_back( dis[j] ) ;
	}
	if( ans.size() < 2 ) return -1 ;
	sort( ans.begin() , ans.end() , cmp ) ;
	return ans[0] + ans[1] ;
}
int main() {
	int T ;
	scanf("%d",&T);
	while( T-- ) {
		tot = 0 ;
		int res = -1 ;
		memset( head , -1 , sizeof(head) ) ;
		memset( dis , 0x3f , sizeof(dis) ) ;
		scanf("%d%d",&n,&m);
		while( m-- ) {
			scanf("%d%d%d",&x,&y,&w);
			add( x , y , w ) ;
		}
		for( int i = 1 ; i <= n ; i++ ) {
			res = max( res , dijstra( i ) ) ;
		}
		printf("%d\n",res);
	}
	return 0 ;
}

最短路
思路:i到j路径长度是 i ^ j ,这样边很多,可以将每条路径分解成2的幂次长度相加,故每个点相连路径只添加2的幂次长度,只有nlogn条边。

#include 
using namespace std;
int n , m , c , s , t ;
const int AX = 2e5 + 66 ;
struct Node{
	int v , w ;
	Node( int v , int w ):v(v),w(w){}
	bool operator < ( const Node &a )const{
		return w > a.w ; 
	}
};
vector<Node>G[AX] ;
int dis[AX] ; 
int vis[AX] ; 
void dijstra(){
	priority_queue<Node>q ;
	q.push(Node(s,0));
	memset( dis , 0x3f , sizeof(dis) ) ;
	dis[s] = 0 ;
	while( !q.empty() ){
		Node tmp = q.top() ; 
		q.pop();
		vis[tmp.v] = 1 ; 
		for( int i = 0 ; i < G[tmp.v].size() ; i++ ){
			int to = G[tmp.v][i].v ; 
			if( !vis[to] && dis[to] > dis[tmp.v] + G[tmp.v][i].w ){
				dis[to] = dis[tmp.v] + G[tmp.v][i].w ;
				q.push(Node(to,dis[to]));
			}
		}
	}
}
int main() {
	scanf("%d%d%d",&n,&m,&c);
	int x , y , w ; 
	for( int i = 0 ; i < m ; i++ ) {
		scanf("%d%d%d",&x,&y,&w) ;
		G[x].push_back(Node(y,w));
	} 
	scanf("%d%d",&s,&t);
	int tot = 1 ; 
	while( tot <= n ) tot <<= 1 ; 
	for( int i = 1 ; i < tot ; i++ ){
		for( int p = 1 ; p < tot ; p <<= 1 ){
			G[i].push_back(Node(i^p,p*c));
		}
	}
	dijstra();
	printf("%d\n",dis[t]);
	return 0 ;
}



强连通分量:

这题跟求强连通似乎并无关系0.0
不过简单题练练手
B题

思路:因为是个环,解一定是从1顺时针,或者逆时针。

#include 
#define INF 0x3f3f3f3f
using namespace std;
const int AX = 1e2 + 66 ; 
int n ;
int a[AX][AX];
int res ; 
void dfs( int x , int pre , int num , int sum ){
	if( num == n + 1 ){
		res = min( res , sum );
		return ; 
	}
	for( int i = 1 ; i <= n ; i++ ){
		if( a[x][i] >= 0 && i != pre ){
			dfs( i , x , num + 1 , sum + a[x][i] );
		}
	}
}
int main() {
	int x , y , w ; 
	while( ~scanf("%d",&n) ){
		memset( a , -1 , sizeof(a) ) ;
		res = INF ; 
		for( int i = 0 ; i < n ; i++ ){
			scanf("%d%d%d",&x,&y,&w);
			a[y][x] = w ;
			a[x][y] = 0 ; 
		}
		dfs( 1 , -1 , 1 , 0 );
		printf("%d\n",res);
	}
	return 0 ;
}

二分图匹配

Graph Coloring I

思路:
染色法判断有无简单奇环
有简单奇环则染色不成功

输出环那部分比较难受

#include 
using namespace std;
const int AX = 3e5 + 66 ;
int n , m ;
vector<int>odd;
int col[AX] ;
vector<int>G[AX];
int pre[AX] ;
int f , s ;
void dfs( int x , int val ) {
	if( f ) return ;
	col[x] = val ;
	for( int i = 0 ; i < (int)G[x].size() ; i ++ ) {
		int y = G[x][i] ;
		if( col[y] == -1 ) {
			pre[y] = x ;
			dfs( y , val ^ 1 );
		} else if( col[x] == col[y] ) { // 有奇数环
			f = y ;   // 环终点
			s = x ;   // 环起点
			return ;
		}
	}
	return ;
}
int main() {
	int x , y ;
	scanf("%d%d",&n,&m);
	f = 0 ; // 无奇环
	memset( col , -1 , sizeof(col) ) ;
	for( int i = 0 ; i < m ; i++ ) {
		scanf("%d%d",&x,&y);
		G[x].push_back(y);
		G[y].push_back(x);
	}
	dfs( 1 , 0 ) ;
	if( !f ) {
		printf("0\n");
		for( int i = 1 ; i <= n ; i++ ) {
			printf("%d ",col[i]);
		}
		printf("\n");
	} else {
		int k = 0 ;
        for( int i = f ; i != s ; i = pre[i] ){
            k ++ ;
        }
        printf("%d\n", k + 1 );
        for( int i = f ; i != s ; i = pre[i] ){
            printf("%d ",i);
        }
        printf("%d\n",s);
	}
	return 0 ;
}

字典树

单词查找树
比板子题还要简单些的一道,以前复试题也没出过,就不写了

#include 
using namespace std ;
struct Trie{
	struct Trie *nxt[26] ;
	char c ; 
};
Trie* create(){
	Trie* rt = new Trie() ; 
	memset( rt -> nxt , 0 , sizeof(rt->nxt) ) ;
	return rt ; 
}
void insert(  Trie* rt , char *s ){
	int len = strlen(s) ;
	Trie* t = rt ; 
	while( *s ){
		int id = (*s)-'A' ; 
		if( t -> nxt[id] == NULL ){
			t -> nxt[id] = create();
		}
		t = t -> nxt[id] ;
		s ++ ; 
	}
} 
int res ;
void dfs( Trie* rt ){
	res ++ ;
	for( int i = 0 ; i < 26 ; i++ ){
		if( rt -> nxt[i] ){
			dfs( rt -> nxt[i] );
		}
	}
}
int main(){
	char s[66];
	res = 0 ;
	Trie* rt = create() ;
	while( ~scanf("%s",s) ){
		insert( rt , s ) ;
	}
	dfs( rt );
	printf("%d\n",res);
	return 0 ; 
}

你可能感兴趣的:(最小生成树,最短路,最近公共祖先)