文章目录
分层图
最短路方面的扩展
主要解决问题 :你有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,i−1),i−1),并维护路径信息 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,i−1),info(next(x,i−1),i−1));
初 始 化 初始化 初始化
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
思路:
- 区间的最小值 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
思路:
题目要求: g c d ( a gcd(a gcd(al, a a al+1 , … … , a ,……,a ,……,ar ) ) ) ! = r − l + 1 != r-l+1 !=r−l+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]
#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);
}
例题
通用代码
#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<=3∗n−6;
条件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;
}