图论 杂项


分层图

  最短路方面的扩展

      主要解决问题 :你有k次机会使某些上路径上的花费为0,求最少花费

      如何建图 :

k k k次免费的机会,建立 k + 1 k+1 k+1层图
两个点如果有边

  • 同层是原来的权值
  • 相邻层之间权值为0

      核心建图代码 :

for(int i = 1;i<=m;i++){
	int a,b,c;
	cin >> a >> b >> c;
	add(a,b,c);
	add(b,a,c);
	for(int j = 1;j<=k;j++){
		//上下层
		add(a + (j-1)*n, b + j*n, 0);
		add(b + (j-1)*n, a + j*n, 0);
		//本层
		add(a + j*n, b + j*n, c);
		add(b + j*n, a + j*n, c);  
	}
}
//预防hack 每层结尾互通
for(int i=1;i<=k;++i)
    	add(ed+(i-1)*n,ed+i*n,0);

      起点和终点 :

起点第一层,终点最后一层。

例题:[JLOI2011]飞行路线

题目链接

很裸

代 码 代码

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
//526526
using namespace std;
const int N = 2500001;
int h[N],ne[N],e[N],w[N],idx;
int n,m,k;
int s,ed;
int dis[N];
bool vis[N];
int q[N];
void add(int a,int b,int c){
	ne[idx] = h[a],e[idx] = b,w[idx] = c,h[a] = idx++;
}
void Dijkstra(int s)
{
    memset(dis,0x3f,sizeof(dis));
    dis[s]=0;
    priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > > points;
    points.push(make_pair(0,s));
    while(!points.empty())
    {
        int u=points.top().second;
        points.pop();
        if(!vis[u])
        {
            vis[u]=1;
            for(int i=h[u];~i;i=ne[i])
            {
                int to=e[i];
                if(dis[to]>dis[u]+w[i]) 
                {
                    dis[to]=dis[u]+w[i];
                    points.push(make_pair(dis[to],to));
                }
            }
        }
    }
}

int main(){
	cin >> n >> m >> k;
	cin >> s >> ed;
	memset(h,-1,sizeof h);
	for(int i=1;i<=m;i++){
		int a,b,c;
		cin >> a >> b >> c;
		add(a,b,c);
		add(b,a,c);
		for(int j = 1;j<=k;j++){
			//上下层
			add(a + (j-1)*n, b + j*n, 0);
			add(b + (j-1)*n, a + j*n, 0);
			//本层
			add(a + j*n, b + j*n, c);
			add(b + j*n, a + j*n, c);  
		}
	}
	for(int i=1;i<=k;++i)
    	add(ed+(i-1)*n,ed+i*n,0);
	Dijkstra(s);
	cout << dis[ed + k*n] << endl;
	return 0;
}

加工零件

题目链接

思路:
求解奇偶最短路。

可以拆点对于边 ( u , v ) (u,v) (u,v) u u u差成 u u u0 , u ,u ,u1 .

u u u1:表示到达 u u u需要奇数步
u u u0:表示到达 u u u需要偶数步
v v v同理。

连边时:
( u , v ) (u,v) (u,v) – > ( u , v + n ) , ( v , u + n ) (u,v+n),(v,u+n) (u,v+n),(v,u+n)

代码

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N = 2e5+10,M = 4e5+10;
int h[N],e[M],ne[M],idx;
void add(int a,int b){
    ne[idx] = h[a] ,e[idx] = b,h[a] = idx++;
}
int dist[N];
bool st[N];
void spfa(int x){
    memset(dist,0x3f,sizeof dist);
    queue<int> q;
    q.push(x);
    dist[x] = 0;
    while(!q.empty()){
        int u = q.front();q.pop();
        st[u] = false;
        for(int i = h[u];~i;i=ne[i]){
            int y = e[i];
            if(dist[y] > dist[u] + 1){
                dist[y] = dist[u] + 1;
                if(!st[y])st[y]=true,q.push(y);
            }
        }
    }
}
int n,m,s;
int deg;
int main(){
    cin >> n >> m >> s;
    memset(h,-1,sizeof h);
    for(int i = 1;i<=m;i++){
        int a,b;
        cin >> a >> b;
        add(a,b+n);
        add(b+n,a);
        add(b,a+n);
        add(a+n,b);
        if(a==1 || b == 1)deg++;
    }
    spfa(1);
    int k,l;
    while(s--){
        cin >> k >> l;
        if(k == 1){
            if(deg == 0){
                cout << "No" << endl;
                continue;
            }
        }
        if(l&1){
            if(dist[k+n] > l)cout << "No" << endl;
            else cout << "Yes" << endl;
        }else{
            if(dist[k] > l)cout << "No" << endl;
            else cout << "Yes" << endl;
        }
    }
    return 0;
}

并查集

  并查集求解连通块

      

例题:CodeForces - 731C

题目链接

代 码 代码

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#include<set>
//526526
using namespace std;
const int N = 2e5+10;
int color[N];
int f[N];
vector<int> v[N];
int cnt[N];
int find(int x){
	return x != f[x] ? f[x] = find(f[x]) : f[x];
}
void Union(int a,int b){
	int pa = find(f[a]),pb = find(f[b]);
	if(pa == pb)return ;
	f[pb] = pa;
}
int main(){
	int n,m,k;
	cin >> n >> m >> k;
	for(int i = 1;i<=n;i++)f[i] = i,scanf("%d",&color[i]);
	for(int i = 1;i<=m;i++){
		int a,b;
		scanf("%d%d",&a,&b);
		Union(a,b);
	}
	for(int i = 1;i<=n;i++){
		v[find(f[i])].push_back(color[i]);
	}
	int ans = 0,mx = 0;
	for(int i = 1;i <= n;i++){
		if(v[i].size() <= 1)continue;
		mx = 0;
		for(int j = 0;j < v[i].size();j++)mx = max(mx,++cnt[v[i][j]]);
		for(int j = 0;j < v[i].size();j++)cnt[v[i][j]] = 0;
		//cout << i << " " << mx << endl;
		ans += v[i].size() - mx;
	}
	cout << ans << endl;
	return 0;
}

X(or)-mas Tree

题目链接

思路:

假设一个点为根 r o o t root root
d [ u ] : d[u] : d[u]表示 r o o t root root u u u的路径亦或和。
则两点 ( u , v ) (u,v) (u,v)之间的路径亦或和为 f [ u ] f[u] f[u] ^ f [ v ] f[v] f[v];

  • 这对于路径 ( u , v ) (u,v) (u,v)权值二进制为奇数 : 要求 f [ u ] f[u] f[u] f [ v ] f[v] f[v] 中的二进制个数不同奇偶。
  • ( u , v ) (u,v) (u,v)为偶数 : 要求 f [ u ] f[u] f[u] f [ v ] f[v] f[v]同奇偶。

即用并查集维护奇数的在一起,偶数的在一起。
无解就是:一个点既在偶点又在奇点。
对于构造解 : 对于一条边 ( u , v ) (u,v) (u,v),若 f [ u ] f[u] f[u] f [ v ] f[v] f[v] 二进制同奇偶,则 ( u , v ) (u,v) (u,v)二进制个数为偶数(可以用 0 0 0表示.),反正奇数。

判断:
一个32进制无符号整数的二进制中1的个数 : _ _ b u i l t i n _ p o p c o u n t ( ) \_\_builtin\_popcount() __builtin_popcount().
代码

#include<iostream>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int N = 4e5+10;
typedef long long ll;
int w[N],h[N],e[N],ne[N],idx;
int f[N],s[N],ans[N];
int n,m;
void add(int a,int b,int c){
	ne[idx] = h[a],e[idx] = b,w[idx] = c,h[a] = idx++;
}
int find(int x){
	return f[x] != x ? f[x] = find(f[x]) : f[x];
}
void un(int a,int b){
	int fa = find(a),fb = find(b);
	if(fa == fb)return ;
	f[fa] = find(f[fb]);
}
void dfs(int u,int f){
	for(int i = h[u];~i;i=ne[i]){
		int y = e[i];
		if(y == f)continue;
		if(s[y] != s[u])ans[i] = ans[i^1] = 1;
		else ans[i] = ans[i^1] =  0;
		dfs(y,u);
	}
}
int main(){
	int t;
	cin >> t;
	while(t--){
		idx = 0;
		scanf("%d%d",&n,&m);
		for(int i = 0;i<=n*2;i++)f[i] = i,h[i] = -1;
		for(int i = 1,a,b,c;i<n;i++){
			scanf("%d%d%d",&a,&b,&c);
			add(a,b,c);add(b,a,c);
			if(c == -1)continue;
			if(__builtin_popcount(c)&1) un(a,b+n),un(a+n,b);
			else un(a,b),un(a+n,b+n);
		}
		for(int i = 1,a,b,c;i<=m;i++){
			scanf("%d%d%d",&a,&b,&c);
			if(c) un(a,b+n),un(a+n,b);
			else un(a,b),un(a+n,b+n);
		}
		bool flag = true;
		for(int i = 1;i<=n;i++){
			if(find(i) == find(i+n)){
				cout <<"NO"<<endl;
				flag = false;
				break;
			}
		}
		if(!flag)continue;
		cout << "YES" << endl;
		for(int i = 1;i<=n;i++) s[i] = (find(i) <= n ? 1 : 0);
		dfs(1,-1);
		for(int i = 1;i<idx;i+=2){
			if(w[i] != -1)printf("%d %d %d \n",e[i],e[i^1],w[i]);
			else printf("%d %d %d \n",e[i],e[i^1],ans[i]);
		}
	}
	return 0;
}

倍增

模 型 模型

在一个有向图中,每个点最多只有一条出边,每条边有一定的信息,走过一条路径时,就将路径上边的信息依此按一定的规则合并,并且合并规则满足结合律。

维 护 维护

对于一个点 x x x,可以倍增地得到它经过 2 i 2^i 2i条边后到达的点 n e x t ( x , i ) = n e x t ( n e x t ( x , i − 1 ) , i − 1 ) next(x,i) = next(next(x,i-1),i-1) next(x,i)=next(next(x,i1),i1),并维护路径信息 i n f o ( x , i ) = m e r g e ( i n f o ( x , i − 1 ) , i n f o ( n e x t ( x , i − 1 ) , i − 1 ) ) info(x,i) = merge(info(x,i-1),info(next(x,i-1),i-1)) info(x,i)=merge(info(x,i1),info(next(x,i1),i1));

初 始 化 初始化

void init(){
	for(int i = 1;i<=n;i++)f[i][0] = gcd[i][0] = a[i];
	for(int i = 1;(1<<i) <= n;i++)
		for(int j = 1;j + (1<<i) - 1 <= n ;j++)
			f[j][i] = min(f[j][i-1],f[j+(1<<(i-1))][i-1]),
			gcd[j][i] = __gcd(gcd[j][i-1],gcd[j+(1<<(i-1))][i-1]);
}

求 区 间 信 息 求区间信息
最小值为例。

int _min(int l,int r){
	int k = (int)log2((double)(r-l+1));
	return min(f[l][k],f[r-(1<<k)+1][k]);
}

应用

快速幂

线性

RMQ
给定区间,求区间最大最小,gcd等的值。
初 始 化 初始化

void init(){
	for(int i = 1;i<=n;i++)f[i][0] = gcd[i][0] = a[i];
	for(int i = 1;(1<<i) <= n;i++)
		for(int j = 1;j + (1<<i) - 1 <= n ;j++)
			f[j][i] = min(f[j][i-1],f[j+(1<<(i-1))][i-1]),
			gcd[j][i] = __gcd(gcd[j][i-1],gcd[j+(1<<(i-1))][i-1]);
}

求 区 间 信 息 求区间信息
重叠元素对最后区间的结果无影响。
最小值为例。

int _min(int l,int r){
	int k = (int)log2((double)(r-l+1));
	return min(f[l][k],f[r-(1<<k)+1][k]);
}
例题 codeforces : D. Pair of Numbers

D. Pair of Numbers

思路:

  • 区间的最小值 x x x = = = 区间的 g c d gcd gcd = = > ==> ==> x x x满足题目要求。
  • R M Q RMQ RMQ 维护区间的最小值和 g c d gcd gcd.
  • 二分区间长度,枚举每个点是否存在满足条件的。
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
using namespace std;
const int N = 3e5+10;
int f[N][22],gcd[N][22],a[N];
int n;
vector<int> ans;
void init(){
	for(int i = 1;i<=n;i++)f[i][0] = gcd[i][0] = a[i];
	for(int i = 1;(1<<i) <= n;i++)
		for(int j = 1;j + (1<<i) - 1 <= n ;j++)
			f[j][i] = min(f[j][i-1],f[j+(1<<(i-1))][i-1]),
			gcd[j][i] = __gcd(gcd[j][i-1],gcd[j+(1<<(i-1))][i-1]);
}
int _min(int l,int r){
	int k = (int)log2((double)(r-l+1));
	return min(f[l][k],f[r-(1<<k)+1][k]);
}
int _gcd(int l,int r){
	int k = (int)log2((double)(r-l+1));
	return __gcd(gcd[l][k],gcd[r-(1<<k)+1][k]);
}
bool check(int mid){
	vector<int> l;
	for(int i = 1;i + mid <= n;i++){
		if(_min(i,i+mid) == _gcd(i,i+mid)){
			l.push_back(i);
		}
	}
	if(l.size() > 0){
		ans = l;
		return true;
	}
	return false;
}
int main(){
	cin >> n;
	for(int i = 1;i<=n;i++)cin >> a[i];
	init();
	//二分 r-l
	int l = 0,r = n-1;
	while(l < r){
		int mid = l + r + 1 >> 1;
		if(check(mid))l = mid;
		else r = mid-1;
	}
	//2分模板问题0不会执行,最后在执行一下。
	check(l);
	cout << ans.size() << " " << l << endl;
	for(int i = 0;i<ans.size();i++)cout << ans[i] << " ";
	return 0;
}
[例题] D. New Year Concert

CodeForces - 1632D

思路:

题目要求: g c d ( a gcd(a gcd(al, a a al+1 , … … , a ,……,a ,,ar ) ) ) ! = r − l + 1 != r-l+1 !=rl+1;
对于一个 r r r l l l r , g c d r ,gcd r,gcd是不减的,故可以枚举右端点,移动左端点。

	int r = 0,l = 1,ans=0;
	for(int r = 1,c;r<=n;r++){
		while((c = get(l,r)) <= r - l + 1){
			if(c == r-l+1)l = r+1,ans ++;
			else l ++;
		}
		cout << ans << " ";

代码

#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 2e5+10;
int gcd[N][21];
int n;
int get(int l,int r){
	if(l > r)return 1e9+1;
	int k = (int)log2((double)(r-l+1));
	return __gcd(gcd[l][k],gcd[r - (1<<k) + 1][k]);
}
int main(){
	cin >> n;
	for(int i = 1;i<=n;i++)cin >> gcd[i][0];
	for(int i = 1;(1<<i) <= n;i++)
		for(int j = 1;j + (1<<i) - 1 <= n;j++)
			gcd[j][i] = __gcd(gcd[j][i-1],gcd[j + (1<<(i-1))][i-1]);
	int r = 0,l = 1,ans=0;
	for(int r = 1,c;r<=n;r++){
		while((c = get(l,r)) <= r - l + 1){
			if(c == r-l+1)l = r+1,ans ++;
			else l ++;
		}
		cout << ans << " ";
	}
	return 0;
}
/*
3 1 4 2
*/

lca
初始化:
f [ i ] [ j ] f[i][j] f[i][j] 来表示从节点 i i i开始的,向父亲方向走 2 j 2^j 2j能够到达的节点。
d [ i ] d[i] d[i]表示深度
步骤:
1: 先将两个点跳到同一层
[2]: 让两个点同时往上跳,一直跳到他们的最近公共祖先的下一层。
预处理 : n ( n l o g n ) n(nlogn) n(nlogn) 查询 o ( n ) o(n) o(n);

预 处 理 预处理

void bfs(int root){
	memset(d,0x3f,sizeof d);
	d[0] = 0, d[root] = 1;
	queue<int> q;
	q.push(root);
	while(!q.empty()){
		int u = q.front();
		q.pop();
		for(int i = h[u];~i;i=ne[i]){
			int y = e[i];
			if(d[y] > d[u] + 1){
				d[y] = d[u] + 1;
				q.push(y);
				f[y][0] = u;
				for(int k = 1;k<22;k++)f[y][k] = f[f[y][k-1]][k-1];
			}
		}
	}
}

求 解 求解

int lca(int a,int b){
	//跳到同一层
	if(d[a] < d[b])swap(a,b);
	for(int k = 21;k>=0;k--)
		if(d[f[a][k]] >= d[b])
			a = f[a][k];
	
	if(a == b)return a;
	
	//跳到公共祖先的下一层
	for(int k = 21;k>=0;k--)
		if(f[a][k] != f[b][k])
			a = f[a][k],b = f[b][k];
	return f[a][0];
}
例题求树的k级祖先

预处理复杂度: O(nlogn) 查询复杂度: 最坏O(logn)。

//[tops]:还能用重链剖分和长链剖分求。

//预处理
void dfs(int u,int fa){
	dep[u] = dep[fa] + 1;
	f[u][0] = fa;
	for(int i = 1;i<=20;i++)f[u][i] = f[f[u][i-1]][i-1];
	for(int i = h[u];~i;i=ne[i]){
		int  y = e[i];
		if(y == fa)continue;
		dfs(y,u);
	}
}

//x的k级祖先
int get(int x,int k){
	if(!k)return x;
	int index = log2(k);//取k的2的最高位 
	return get(f[x][index],k - (1<<index)); 
}
例题 P4180 [BJWC2010]严格次小生成树

次小生成树
思路:

1. 先求最小生成树( k r u s k a l kruskal kruskal),并建立树,记录已经使用过的边,权值为 s u m sum sum
[2]. 倍增求路径 (某一点 x x x r o o t root root路径上的最大权值) 上的最大权值严格次大权值 (后面要删除最大权值那条边在加一条边可能使生成树权值不变,这里求的严格次小生成树)
[3]. 枚举没有遍历的边(求解生成树时没枚举)添加该边 ( u , v ) (u,v) (u,v),权值为 w w w,求解 a = ( u , l c a ( u , v ) ) a=(u,lca(u,v)) a=(u,lca(u,v)) b = ( v , l c a ( u , v ) b=(v,lca(u,v) b=(v,lca(u,v)路径最大值,res = min(sum - max(a,b) + w);

参考思路 博客园
参考代码 oiwiki
分段代码:
1:

void kruskal(){
	for(int i = 1;i<=n;i++)f[i] = i;
	sort(edge+1,edge + 1 + m);
	for(int i = 1;i<=m;i++){
		int u = edge[i].u , v = edge[i].v;
		long long w = edge[i].w;
		if(find(f[u]) != find(f[v])){
			f[find(f[u])] = find(f[v]);
			sum += w;
			add(u,v,w);
			add(v,u,w);
			used[i] = 1;
		}
	}
}

[2]:

void dfs(int u,int father){
	depth[u] = depth[father] + 1;
	fa[u][0] = father;
	max2[u][0] = -inf;
	for(int i = 1;(1<<i) <= depth[u];i++){
		fa[u][i] = fa[fa[u][i-1]][i-1];
		int kk[4] = {max1[u][i - 1], max1[fa[u][i - 1]][i - 1],
					max2[u][i - 1], max2[fa[u][i - 1]][i - 1]};
		// 从四个值中取得最大值
		sort(kk, kk + 4);
		max1[u][i] = kk[3];
		// 取得严格次大值
		int ptr = 2;
		while (ptr >= 0 && kk[ptr] == kk[3]) ptr--;
		max2[u][i] = (ptr == -1 ? -inf : kk[ptr]);	
	}
	
	for(int i = h[u];~i;i=ne[i]){
		int y = e[i];
		if(y == father)continue;
		max1[y][0] = w[i];
		dfs(y,u);
	}
}

[3]:


int lca(int a,int b){
	if(depth[a] < depth[b])swap(a,b);
	for(int i = 21;i>=0;i--)
		if(depth[fa[a][i]] >= depth[b])
			a = fa[a][i];
	if(a == b)return a;
	for(int i = 21;i>=0;i--)
		if(fa[a][i]!=fa[b][i])
			a = fa[a][i], b = fa[b][i];
	return fa[a][0];
}
long long query(int a,int b,int c){
	int res =  -inf;
	for(int i = 21;i>=0;i--){
		if(depth[fa[a][i]] >= depth[b]){
			//记录路径最大值
			if(c != max1[a][i])
				res = max(res,max1[a][i]);
			else 
				res = max(res,max2[a][i]);
			a = fa[a][i];	
		}
	}
	return res;
}

//main
for(int i = 1;i<=m;i++){
		if(used[i])continue;
		int u = edge[i].u,v = edge[i].v,w = edge[i].w;
		int LCA = lca(u,v);
	//	cout << u << " " << u << " " << LCA << endl;
		long long tempa = query(u,LCA,w);
		long long tempb = query(v,LCA,w);
		if(max(tempa , tempb) > -inf)
			//判断是否存在严格次小生成树
			ans = min(ans,sum - max(tempa,tempb) + w);
}

代码:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N = 1e5+10,M = 6e5+10,inf = 0x7fffffff;
const long long inf64 = 0x7fffffffffffffff;
int h[N],e[M],ne[M],idx;
long long w[M];
void add(int a,int b,int c){
	ne[idx] = h[a],e[idx] = b,w[idx] = c,h[a] = idx++;
}
struct node{
	int u,v;
	long long w;
	bool operator<(node a)const{
		return w < a.w;
	}
}edge[M];
int n,m;
long long sum;
int f[N];
bool used[M];
//深度 , 前驱 , 最大值, 严格次大值。
int depth[N],fa[N][22],max1[N][22],max2[N][22];
int find(int x){return x != f[x] ? f[x] = find(f[x]) : f[x] ;}
void kruskal(){
	for(int i = 1;i<=n;i++)f[i] = i;
	sort(edge+1,edge + 1 + m);
	for(int i = 1;i<=m;i++){
		int u = edge[i].u , v = edge[i].v;
		long long w = edge[i].w;
		if(find(f[u]) != find(f[v])){
			f[find(f[u])] = find(f[v]);
			sum += w;
			add(u,v,w);
			add(v,u,w);
			used[i] = 1;
		}
	}
}
void dfs(int u,int father){
	depth[u] = depth[father] + 1;
	fa[u][0] = father;
	max2[u][0] = -inf;
	for(int i = 1;(1<<i) <= depth[u];i++){
		fa[u][i] = fa[fa[u][i-1]][i-1];
		int kk[4] = {max1[u][i - 1], max1[fa[u][i - 1]][i - 1],
					max2[u][i - 1], max2[fa[u][i - 1]][i - 1]};
		// 从四个值中取得最大值
		sort(kk, kk + 4);
		max1[u][i] = kk[3];
		// 取得严格次大值
		int ptr = 2;
		while (ptr >= 0 && kk[ptr] == kk[3]) ptr--;
		max2[u][i] = (ptr == -1 ? -inf : kk[ptr]);	
	}
	
	for(int i = h[u];~i;i=ne[i]){
		int y = e[i];
		if(y == father)continue;
		max1[y][0] = w[i];
		dfs(y,u);
	}
}
int lca(int a,int b){
	if(depth[a] < depth[b])swap(a,b);
	for(int i = 21;i>=0;i--)
		if(depth[fa[a][i]] >= depth[b])
			a = fa[a][i];
	if(a == b)return a;
	for(int i = 21;i>=0;i--)
		if(fa[a][i]!=fa[b][i])
			a = fa[a][i], b = fa[b][i];
	return fa[a][0];
}
long long query(int a,int b,int c){
	int res =  -inf;
	for(int i = 21;i>=0;i--){
		if(depth[fa[a][i]] >= depth[b]){
			//记录路径最大值
			if(c != max1[a][i])
				res = max(res,max1[a][i]);
			else 
				res = max(res,max2[a][i]);
			a = fa[a][i];	
		}
	}
	return res;
}
int main(){
	cin >> n >> m;
	memset(h,-1,sizeof h);
	for(int i = 1;i<=m;i++)
		scanf("%d%d%lld",&edge[i].u,&edge[i].v,&edge[i].w);
	kruskal();
	dfs(1,0);
	long long ans = inf64;
	for(int i = 1;i<=m;i++){
		if(used[i])continue;
		int u = edge[i].u,v = edge[i].v,w = edge[i].w;
		int LCA = lca(u,v);
	//	cout << u << " " << u << " " << LCA << endl;
		long long tempa = query(u,LCA,w);
		long long tempb = query(v,LCA,w);
		if(max(tempa , tempb) > -inf)
			//判断是否存在严格次小生成树
			ans = min(ans,sum - max(tempa,tempb) + w);
	}
	cout << ans << endl;
	return 0;
}

kruskal重构树

模型:
K r u s k a l Kruskal Kruskal算法进行的过程中,我们把最小生成树的边权改为点权(独立出一个点链接相邻的两个点)。
特点:
1. 会构造成个二叉堆的形式 参考链接 : 图片.
[2]:原树两点之间的边权最大值是重构树上两点Lca的权值.

构 造 构造

void krus(){
	int cnt = n;
	sort(edge+1,edge+1+m);
	for(int i = 1;i<=m;i++){
		int u = find(edge[i].u),v = find(edge[i].v);
		if(u != v){
			f[u] = f[v] = ++cnt;
			add(cnt,u);
			add(cnt,v);
			w[cnt] = edge[i].w;
		}
	}
}

例题 [Network]

Network

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 3e4+10,M = 1e5+10;
int h[N],ne[M],e[M],idx;
void add(int a,int b){
	ne[idx] = h[a],e[idx] = b,h[a] = idx++;
}
struct node{
	int u,v,w;
	bool operator<(node a)const{
		return w < a.w;
	}
}edge[M];
int n,m,k;
int f[N];
int w[N];
int dep[N],fa[N],siz[N],son[N],top[N];
int find(int x){return f[x] != x ? f[x] = find(f[x]) : f[x];}
void krus(){
	int cnt = n;
	sort(edge+1,edge+1+m);
	for(int i = 1;i<=m;i++){
		int u = find(edge[i].u),v = find(edge[i].v);
		if(u != v){
			f[u] = f[v] = ++cnt;
			add(cnt,u);
			add(cnt,v);
			w[cnt] = edge[i].w;
		}
	}
}
void dfs1(int u,int father){
	dep[u] = dep[father] + 1;
	fa[u] = father;
	siz[u] = 1;
	for(int i = h[u];~i;i=ne[i]){
		int y = e[i];
		if(y == father)continue;
		dfs1(y,u);
		siz[u] += siz[y];
		if(siz[y] > siz[son[u]])son[u] = y;
	}
}
void dfs2(int u,int v){
	top[u] = v;
	if(!son[u])return ;
	dfs2(son[u],v);
	for(int i = h[u];~i;i=ne[i]){
		int y = e[i];
		if(y != fa[u] && y != son[u])dfs2(y,y);
	}
}
int lca(int x,int y){
	while(top[x] != top[y]){
		if(dep[top[x]] < dep[top[y]])swap(x,y);
		x = fa[top[x]];
	}
	return dep[y] > dep[x] ? x : y;
}
int main(){
	cin >> n >> m >> k;
	memset(h,-1,sizeof h);
	for(int i = 1;i<=n<<1;i++)f[i] = i;
	for(int i = 1;i<=m;i++)
		scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
	krus();
	dfs1((n<<1)-1,0),dfs2((n<<1)-1,(n<<1)-1);	
	for(int i = 1;i<=k;i++){
		int a,b;
		cin >> a >> b;
		cout << w[lca(a,b)] << endl;
	}
	return 0;
}

例题 P1967 [NOIP2013 提高组] 货车运输

题目链接

#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int N = 2e4+10,M = 5e4+10;
int h[N],e[M],ne[M],idx;
void add(int a,int b){
	ne[idx] = h[a],e[idx] = b,h[a] = idx++;
}
struct node{
	int u,v,w;
	bool operator<(node a)const {
		return w > a.w;
	}
}edge[M];
int f[N];
int w[N];
int n,m;
int find(int x){return f[x] != x ? f[x] = find(f[x]) : f[x];}
int kurskal(){
	memset(h,-1,sizeof h);
	int cnt = n;
	for(int i = 1;i<=(n<<1);i++)f[i] = i;
	sort(edge+1,edge+1+m);
	for(int i = 1;i<=m;i++){
		int u = find(edge[i].u),v = find(edge[i].v);
		if(u != v){
			f[u] = f[v] = ++cnt;
			add(cnt,u);add(cnt,v);
			w[cnt] = edge[i].w;
		}	
	}
	return cnt;
}
int fa[N],dep[N],top[N],siz[N],son[N];
void dfs1(int u,int father){
	fa[u] = father;
	siz[u] = 1;
	dep[u] = dep[father] + 1;
	for(int i = h[u];~i;i=ne[i]){
		int y = e[i];
		if(y == father)continue;
		dfs1(y,u);
		siz[u] += siz[y];
		if(siz[y] > siz[son[u]])son[u] = y;
	}
}
void dfs2(int u,int v){
	top[u] = v;
	if(!son[u])return ;
	dfs2(son[u],v);
	for(int i = h[u];~i;i=ne[i]){
		int y = e[i];
		if(y != fa[u] && y != son[u])dfs2(y,y);
	}
}
int lca(int x,int y){
	while(top[x] != top[y]){
		if(dep[top[x]] < dep[top[y]])swap(x,y);
		x = fa[top[x]];
	}
	return dep[y] > dep[x] ? x : y;
}
int main(){
	cin >> n >> m;
	for(int i = 1;i<=m;i++){
		scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
	}
	int t = kurskal();
	dfs1(t,0);dfs2(t,t);
	int q;
	cin >> q;
	while(q -- ){
		int u,v;
		cin >> u >> v;
		if(find(f[u]) != find(f[v]))cout << -1 << endl;
		else cout << w[lca(u,v)] << endl;
	}
	return 0;
}

最小割树

求解将任意两点割开(分到两个集合)的最小权值.
参考链接
核心代码(分治):

void slove(int L,int R){
	if(L == R)return ;
	int l = L ,r = R,tep[N];
	int t = id[L],s = id[R];
	long long tt = dinic(s,t);
	for(int i = 1;i<=n;i++)
		if(d[i])
			for(int j = 1;j<=n;j++)
				if(!d[j])
					mp[i][j] = mp[j][i] = min(mp[i][j],tt);
	for(int i = L;i<=R;i++)
		tep[d[id[i]] ? l++ : r--] = id[i];
	for(int i = L;i<=R;i++)
		id[i] = tep[i];
	slove(L,r),slove(l,R);	
}

例题

例题1
例题2

通用代码

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
typedef long long type;
const int N = 520,M = 2e4+10,inf = 0x3f3f3f3f;
const long long inf64 = 1e12;
int h[N],e[M],ne[M],idx;
int id[N];
type f[M];
int t,n,m,q;
long long mp[N][N];
void add(int a,int b,type c){
	ne[idx] = h[a],e[idx] = b,f[idx] = c,h[a] = idx++;
	ne[idx] = h[b],e[idx] = a,f[idx] = 0,h[b] = idx++;
}
int cur[N],d[N];
bool bfs(int s,int t){
	memset(d,0,sizeof d);
	queue<int> q;
	q.push(s);
	d[s] = 1;
	while(!q.empty()){
		int u = q.front();
		q.pop();
		for(int i = h[u];~i;i=ne[i]){
			int y = e[i];
			if(f[i] && !d[y])d[y] = d[u] + 1,q.push(y);
		}
	}
	return d[t];
}
type dfs(int u,int t,type mw){
	if(u == t)return mw;
	for(int &i=cur[u];~i;i=ne[i]){
		int y = e[i];
		if(f[i] <= 0 || d[y] != d[u] + 1)continue;
		type cw=dfs(y,t,min(f[i],mw));
		if(cw == 0)continue;
		f[i] -= cw;
		f[i^1] += cw;
		return cw;
	}
	return 0;
}
type dinic(int s,int t){
	//退流
	for(int i = 1;i<idx;i+=2)f[i] = f[i] + f[i^1] , f[i^1] = 0;
	type flow = 0;
	while(bfs(s,t)){
		memcpy(cur,h,sizeof cur);
		while(type d = dfs(s,t,inf64))flow += d;
	}
	return flow;
}

void slove(int L,int R){
	if(L == R)return ;
	int l = L ,r = R,tep[N];
	int t = id[L],s = id[R];
	long long tt = dinic(s,t);
	for(int i = 1;i<=n;i++)
		if(d[i])
			for(int j = 1;j<=n;j++)
				if(!d[j])
					mp[i][j] = mp[j][i] = min(mp[i][j],tt);
	for(int i = L;i<=R;i++)
		tep[d[id[i]] ? l++ : r--] = id[i];
	for(int i = L;i<=R;i++)
		id[i] = tep[i];
	slove(L,r),slove(l,R);	
}

int main(){
	cin >> n >> m;
	idx = 0;
	memset(h,-1,sizeof h);
	memset(mp,0x3f,sizeof mp);
	for(int i = 1;i<=n;i++)id[i] = i;
	for(int i = 1;i<=m;i++){
		int a,b;
		a++,b++;
		type c;
		scanf("%d%d%lld",&a,&b,&c);
		add(a,b,c);
		add(b,a,c);
	}
	n++;
	slove(1,n);
	cin >> q;
	while(q--){
		int x,y;
		cin >> x >>y;
		cout << mp[x][y] << endl;
	}
	return 0;
}

欧拉路径和欧拉回路

前提:图是联通
无向图
1 . 欧拉路径判断:

度数为奇数的点只有0个或2个。

[2].欧拉回路判断

度数为奇数的点只有0个。

有向图
1 . 欧拉路径判断:

所有点的出度等于入读,或除了两个点,所有点的出度等于入读,其中一个点的入读比出度大一,另一个点的出度比入读大一。

[2].欧拉回路判断

所有点的出度等于入读

例题 欧拉回路

题目链接

#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int N = 1e5+10,M = 4e5+10;
int h[N],e[M],ne[M],idx;
void add(int a,int b){
    ne[idx] = h[a] , e[idx] = b,h[a] = idx++;
}
int n,m,type;
int in[N],out[N];
int vis[M];
vector<int > ans;
void dfs(int u){
    for(int &i = h[u];~i;){
        if(vis[i]){i = ne[i];continue;}
        
        vis[i] = 1;
        if(type == 1)vis[i^1] = 1;
        
        int t;
        if(type == 1){
            t = i / 2 + 1;
            if(i & 1)t = -t;
        }
        else t = i + 1;
        
        int y = e[i];
        i = ne[i];
        dfs(y);
        
        ans.push_back(t);
    }
}
int main(){
    cin >> type >> n >> m;
    memset(h,-1,sizeof h);
    for(int i = 1;i<=m;i++){
        int a,b;
        cin >> a >> b;
        add(a,b);
        if(type == 1)add(b,a);
        in[b] ++ ,out[a] ++;
    }
    if(type == 1)
    {
        for(int i = 1;i<=n;i++)
            if( (in[i] + out[i]) & 1)return puts("NO"),0;
    }
    else 
    {
        for(int i = 1;i<=n;i++)
            if(in[i] != out[i])return puts("NO"),0;
    }
    for(int i = 1;i<=n;i++)if(h[i]!=-1){dfs(i);break;}
    if(ans.size()!=m)return puts("NO"),0;
    puts("YES");
    for(int i = ans.size()-1;i>=0;i--){
        printf("%d ",ans[i]);
    }
    return 0;
}

基环树

n个顶点n条边的图,一定会存在环(可能是森林)。
作法:
1: 找环,(建成外向树 :对于任何一个点只有一个父亲)。
[2]: 拆环,(求解这条边两端点的信息)。
[3]:合并。

long long find(int x){
    st[x] = 1;
    while(!st[fa[x]]){//想前找;
        x = fa[x];
        st[x] = true;
    }
    //以x为根遍历
    dfs(x); //dfs时标记已经遍历的点
    //以fa[x]为根遍历
    dfs(fa[x]);
	//合并
	、、
}

例题 1 骑士

题目链接

注意点合并的时候两个根(u1,u2)不能同时选,合并结果应该为 f [ u 1 ] [ 0 ] , f [ u 2 ] [ 0 ] f[u1][0],f[u2][0] f[u1][0],f[u2][0] 中的最大值。
对于 f [ u 1 ] [ 0 ] f[u1][0] f[u1][0] 表示 u 1 u1 u1不选, u 2 u2 u2选不选不知道. f [ u 2 ] [ 0 ] f[u2][0] f[u2][0]同理。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1e6+1000;
int h[N],e[N],ne[N],idx;
void add(int a,int b){
    ne[idx] = h[a],e[idx] = b,h[a] = idx++;
}
long long w[N],f[N][2];
int fa[N];
int n,st[N],root;

void dfs(int u){
    st[u] = true;
    f[u][1] = w[u],f[u][0] = 0;
    
    for(int i = h[u];~i;i=ne[i]){
        int y = e[i];
        
		if(root == y)continue;
        
	    dfs(y);
    
        f[u][1] += f[y][0];
        f[u][0] += max(f[y][1],f[y][0]);
        
    }
}
long long find(int x){
    st[x] = 1;
    while(!st[fa[x]]){//想前找;
        x = fa[x];
        st[x] = true;
    }

    long long ans = 0;
    
    root = x;
    dfs(x);
    ans = max(ans,f[x][0]);
    
    x = fa[x];
    root = x;
    dfs(x);
    ans = max(ans,f[x][0]);
    
    return ans;
}
int main(){
    cin >> n;
    memset(h,-1,sizeof h);
    for(int i = 1;i<=n;i++){
        int a;
        scanf("%lld%d",&w[i],&a);
        add(a,i);
        fa[i] = a;
    }
    
    long long res = 0;
    for(int i = 1;i<=n;i++){
        if(!st[i])res += find(i);
    }
    
    cout << res << endl;
    return 0;
}

359. 创世纪

dp规则:选x不能全选x的子节点。

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1e6+10;
int h[N],e[N],ne[N],idx;
void add(int a,int b){
    ne[idx] = h[a],e[idx] = b,h[a] = idx++;
}
int f[N][2],fa[N];
bool st[N];
int root;
void dfs(int u){
    f[u][1] = 1,f[u][0] = 0;
    st[u] = true;
    int tp = -0x3f3f;
    bool flag = false;
    for(int i=h[u];~i;i=ne[i]){
        int y = e[i];
        if(y == root)continue;
        
        dfs(y);
        
        if(f[y][1] > f[y][0])f[u][1] += f[y][1],tp = max(tp,f[y][0] - f[y][1]);
        else f[u][1] += f[y][0],flag = true;
        f[u][0] += max(f[y][0],f[y][1]);
        
    }
    
    if(!flag) f[u][1] += tp;

}
int find(int x){
    st[x] = true;
    while(!st[fa[x]]){
        x = fa[x];
        st[x] = true;
    }

    int ans;
    root = x;
    dfs(x);
    ans = max(f[x][0],f[x][1]);
 
    x = fa[x];
    root = x;
    dfs(x);
    
    return max(ans,(f[x][0],f[x][1]));
}
int main(){
    int n,ans=0,tp;
    cin >> n;
    memset(h,-1,sizeof h);
    for(int i = 1;i<=n;i++)cin >> tp,add(tp,i),fa[i] = tp;
    for(int i = 1;i<=n;i++){
        if(!st[i])ans += find(i);
    }
    cout << ans << endl;
    return 0;
}

多维差分

参考链接

例题 平面图的判定

题目链接

条件1 : 对于一个平面图边数 m m m,点数 n n n .有 : m < = 3 ∗ n − 6 : m <= 3*n-6 :m<=3n6;
条件2:对于一个哈密顿回路(所有顶点组成一个环),其他的边(不包含在回路上的边)要不在环内要不在环外(2-sat)。

#include<iostream>
#include<cstring>
using namespace std;
const int N = 2e6+10;
int h[N],e[N],ne[N],idx;
void add(int a,int b){
	ne[idx] = h[a],e[idx] = b,h[a] = idx++;
} 
struct node{
	int u,v;
}edge[N];
int cir[210],point[210];
int cut[210][210];
int t,n,m;
int dfn[N],low[N],tim;
bool st[N];
int stk[N],top;
int scc[N],cnt;
void tarjan(int u){
	dfn[u] = low[u] = ++tim;
	stk[++top] = u;
	st[u] = true;
	for(int i = h[u];~i;i=ne[i]){
		int y = e[i];
		if(!dfn[y]){
			tarjan(y);
			low[u] = min(low[u],low[y]);
		}
		else if(st[y])low[u] = min(low[u],dfn[y]);
	}
	if(dfn[u] == low[u])
	{
		int y;
		++cnt;
		do{
			y = stk[top--];
			st[y] = false;
			scc[y] = cnt;
		}while(y!=u);
	}
}
void init(){
	memset(h,-1,sizeof h);
	top = idx = cnt = tim = 0;
	memset(dfn,0,sizeof dfn);
	memset(cut,0,sizeof cut);
}
int main(){
	cin >> t;
	while(t--){
		init();
		cin >> n >> m;
		for(int i = 1;i<=m;i++)
			cin >> edge[i].u >> edge[i].v;
		
		
		for(int i = 1,a;i<=n;i++){
			cin >> point[i];
			cir[point[i]] = i;
		}
		
		if(m > 3*n-6){
			cout << "NO" << endl;
			continue;
		}
		for(int i = 1;i<n;i++){
			cut[point[i]][point[i+1]] = cut[point[i+1]][point[i]] = true;
		}
		cut[point[1]][point[n]] = cut[point[n]][point[1]] = true;
		
		for(int i = 1;i <= m;i++){
			int u = edge[i].u , v = edge[i].v;
			if(cut[u][v])continue;
			if(cir[u] > cir[v])swap(u,v);
			u = cir[u],v = cir[v];
			for(int j = i+1;j<=m;j++){
				int uu = edge[j].u , vv = edge[j].v;
				if(cut[uu][vv])continue;
				if(cir[uu] > cir[vv])swap(uu,vv);
				uu = cir[uu],vv = cir[vv];
				if((u < uu && uu < v && v < vv) || (uu < u && u < vv && vv < v)){
					add(i,j+m),add(i+m,j);
					add(j+m,i),add(j,i+m);	
				}
			}
		}
		
		for(int i = 1;i<=m*2;i++)if(!dfn[i])tarjan(i);
		
		bool flag = true;
		for(int i = 1;i<=m;i++){
			if(scc[i]==scc[i+m]){flag = false;break;}
		}
		if(flag)cout << "YES" << endl;
		else cout << "NO" << endl;
	}
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值