图论板子

1.spfa
队列

#include <stdio.h>
#include<iostream>
#include<vector>
#include<algorithm>
#include<map> 
#include<queue>
using namespace std;
int n,m,s;
int end;
int head[500005];
bool vis[10005];
int dis[10005];
const int inf=2147483647;
struct p{
 int next,to,dis;
};
p a[500005];
int cnt=0;
//链式前向星 这里是双向图 所以存两次
void add(int from,int to,int dis){
 a[++cnt].next=head[from];
 a[cnt].to=to;
 a[cnt].dis=dis;
 head[from]=cnt;
  
 a[++cnt].next=head[to];
 a[cnt].to=from;
 a[cnt].dis=dis;
 head[to]=cnt;
}

void spfa(){
 queue<int> q;
 for(int i=1;i<=n;i++){
  dis[i]=inf;
  vis[i]=0;
 } 
 //vis是入队标记 1为入队
 q.push(s); dis[s]=0;vis[s]=1;
 while(!q.empty()){
  int u=q.front();
  q.pop(); vis[u]=0;//出队啊
  for(int i=head[u];i;i=a[i].next){
   int v=a[i].to;
   if(dis[v]>dis[u]+a[i].dis){
    dis[v]=dis[u]+a[i].dis;
    //如果可以松弛,那么经过这个的需要更新 
    //没入对的话就入队
    if(!vis[v]){
     vis[v]=1;
     q.push(v);
    }
   } 
  }
 }
}
int main(){
cin>>n>>m>>s>>end;
for(int i=1;i<=m;i++){
 int f,g,w;
 cin>>f>>g>>w;
 add(f,g,w);
} 
spfa();
cout<<dis[end]<<endl;
}

2.floyd
由于复杂度的原因,私以为是比赛中不会出现的算法
但是确实相当好写,非常令人愉悦

#include <stdio.h>
#include<iostream>
#include<vector>
#include<algorithm>
#include<map> 
#include<queue>
int dis[104][103];
int p[104];
int ans=999999;
const int inf=9999999;
using namespace std;
int main(){
 int n;
 cin>>n;
 for(int i=1;i<=n;i++){
  for(int j=1;j<=n;j++){
   if(i!=j) dis[i][j]=inf;
  }
 }
 for(int i=1;i<=n;i++){
  int x,y,z;
  cin>>x>>y>>z;
  p[i]=x;
  if(y) {
   dis[i][y]=1;
   dis[y][i]=1;
  }
  if(z){
   dis[i][z]=1;
   dis[z][i]=1;
  }
 }
 for(int k=1;k<=n;k++){
  for(int i=1;i<=n;i++){
   for(int j=1;j<=n;j++){
    if(dis[i][j]>dis[i][k]+dis[k][j]){
     dis[i][j]=dis[i][k]+dis[k][j]; 
    }
   }
  }
 }
 int t=0;
 for(int i=1;i<=n;i++){
  for(int j=1;j<=n;j++){
   if(j!=i)
   t+=dis[i][j]*p[j];
  }
  ans=min(ans,t);
  t=0;
 }
 cout<<ans<<endl;
 return 0;
}

3.强连通分量 - tarjan
这个板子是洛谷2341(奶牛)tarjan缩点,并加了统计出度的部分,出度为0的缩点中奶牛就是最受欢迎的奶牛

#include <cstdio>
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm> 
#include<iomanip>
#include<cmath>
#include<string>
#include<stack>
using namespace std;
const int inf=0x3f3f3f;
const int maxn=10005;
int head[maxn],dfn[maxn],low[maxn],all[maxn],id[maxn];
bool in[maxn];
int du[maxn];
int n,m,ans,cnt,tot,gg;
struct edge{
 int nxt,to;
} e[maxn*20];
stack<int> s; 
void add(int x,int y){
 e[++cnt].to=y;
 e[cnt].nxt=head[x];
 head[x]=cnt;
}
void tarjan(int x){
 dfn[x]=low[x]=++tot;
 s.push(x);
 in[x]=1;
 for(int i=head[x];i;i=e[i].nxt){
  int u=e[i].to;
  if(!dfn[u]){
   tarjan(u);
   low[x]=min(low[x],low[u]);
  }
  else if(in[u]) low[x]=min(low[x],low[u]);
 }
 int k;
 if(low[x]==dfn[x]){
  gg++;
  do{
   k=s.top();
   s.pop();
   in[k]=0;
   id[k]=gg;
   all[gg]++;
  }
  while(x!=k);
 } 
}
int  main(){
cin>>n>>m;
for(int  i=1;i<=m;i++){
 int a,b;
 cin>>a>>b;
 add(a,b);
}
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=e[j].nxt){
  int u=e[j].to;
  if(id[i]!=id[u]) du[id[i]]++;
 }
}
int tt=0;
for(int i=1;i<=gg;i++){
 if(!du[i]){
  if(tt) {
  cout<<0<<endl;
  return 0;
}
  tt=i;
 }
}
cout<<all[tt]<<endl;
 return 0;
}

4.洛谷P1330
染色问题

#include <cstdio>
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm> 
#include<iomanip>
#include<cmath>
#include<string>
#include<stack>
using namespace std;
const int maxn=200005;
int n,m,ans;
struct edge{
 int nxt,to;
} e[maxn];
bool use[maxn];
int col[maxn];
int head[20000],cnt;
int sum[2];
//还是链式前向星 
void add(int u,int v){
 e[++cnt].to=v;
 e[cnt].nxt=head[u];
 head[u]=cnt;
}
//dfs 
bool dfs(int node,int c){
//如果节点访问过 判断颜色 
 if(use[node]){
  if(col[node]==c) return 1;
  return 0;
 }
//若未访问过 ,标记颜色,记录个数 
 use[node]=1;
 col[node]=c;
 sum[c]++;
//这个t有何用 不太懂 
bool t=1;
 for(int i=head[node];i;i=e[i].nxt){
  //1-c改变颜色 
  t = t && dfs(e[i].to,1-c);
 }
 return t; 
}
int main(){
 cin >> n >>m;
 for(int i=1;i<=m;i++){
  int a,b;
  cin >> a>>b;
  add(a,b);
  add(b,a);
 }
 for(int i=1;i<=n;i++){
  if(use[i]) continue;
  sum[0]=sum[1]=0;
 //如果已经访问过,代表此点所在的联通块已经扫描完毕 
 //如果没有过,清空数组,扫描此点所在联通块 
   if(!dfs(i,0)){
    cout << "Impossible"<<endl;
    return 0;
   }
   //更新答案,因为是黑白染色,所以两种取最优解即可 
   ans += min(sum[0],sum[1]);
 }
 cout << ans << endl;
 return 0;
}

5.1
洛谷P2661信息传递
判断成环的,也可用并查集

#include <cstdio>
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm> 
#include<iomanip>
#include<cmath>
#include<string>
#include<stack>
using namespace std;
int n,ans=0x3f3f3f;
//fir存储的是第一次经过该节点的序号,那么成环时
//大小 = 遍历序号-fir[node] 
int nxt[200005],fir[200005];
//vis是否访问过, forevis:是否已经搜过 
bool vis[200005],forevis[200005];
void dfs(int node,int c){
 if(forevis[node]) return ;
 //成环 
   if(vis[node]){
  ans = min(ans,c-fir[node]);
  return; 
 }
 vis[node]=1;
 fir[node]=c;
 dfs(nxt[node],c+1);
 forevis[node]=1;
}
int main(){
 cin>>n;
 for(int i=1;i<=n;i++)cin>>nxt[i];
 for(int i=1;i<=n;i++){
  dfs(i,0);
 }
 cout << ans << endl;
 return 0;
}

5.2洛谷P2921
与上一题很像,是上一题的补充
不一样的是这里成环
一开始的无脑代码(什么时候能改掉无脑提交的毛病。
显然TLE

#include <cstdio>
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm> 
#include<iomanip>
#include<cmath>
#include<string>
#include<stack>
using namespace std;
int n,cnt;
int ans,nxt[100005];
bool vis[100005];
void ini(){
 for(int i=1;i<=n;i++)
 vis[i]=0;
}
void dfs(int id,int t,int s){
 int tmp=nxt[id];
 if(vis[tmp]){
  cout<<t<<endl;
  return ;
 }
 vis[tmp]=1;
 dfs(tmp,t+1,s);
 return;
}
int main(){
 cin>>n;
 for(int i=1;i<=n;i++){
  cin >> nxt[i];
 }
 for(int i=1;i<=n;i++){
  ini();
  vis[i]=1;
  dfs(i,1,i);
 }
 return 0;
}

正解,与上一题有相通之处,强

#include <cstdio>
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm> 
#include<iomanip>
#include<cmath>
#include<string>
#include<stack>
using namespace std;
const int maxn=100005;
int n,nxt[maxn],dfn[maxn],cnt;
//vis记录是否访问过,num记录如果成环,环的大小
//time是加入环时走过的步数 
int vis[maxn],num[maxn],t[maxn];
int main(){
 cin >> n;
 for(int i=1;i<=n;i++) cin>>nxt[i];
 for(int i=1;i<=n;i++){
  for(int j=i,cnt=0;;j=nxt[j],cnt++){
   //如果没记录过 
   if(!vis[j]){
    vis[j]=i;
    dfn[j]=cnt;
   }
   //如果正好在环里成环了 
   else if(vis[j]==i){
    num[i]=cnt-dfn[j];
    //时间戳 
    t[i]=dfn[j];
    cout << cnt << endl;
    break;
   }
   //这种情况就是预见到未来的节点会成环
   //但是这个节点之前已经算过了,答案是固定的,so直接计算了
   //节省时间 ,为什么不能记录下到每一个节点的答案呢?
   //一开始我是这样想的,后来发现,就算记录答案,每一次也不确定
   //因为你不知道那头牛在到达这里前,经历了什么...... 
   else{
    //环的大小就是那个点的大小 
    num[i]=num[vis[j]];
    t[i]=cnt+max(0,t[vis[j]]-dfn[j]);
    cout<<t[i]+num[i]<<endl;
    break;
   }
  }
 }
 return 0;
}

6.洛谷P1341无序字母对
欧拉回路:
统计每一个点的度,若都是偶数,则有欧拉回路(有进有出)
若只有两个点的度为奇数,那么以这两个点为起始点有一条欧拉路径
感觉像是一笔画,以前听大佬讲过
代码还比较清楚

#include <cstdio>
#include<iostream>
#include<vector>
#include<queue>
#include<algorithm> 
#include<iomanip>
#include<cmath>
#include<string>
#include<stack>
using namespace std;
int n,fa[300],mp[300][300],du[300],cnt,head,sum;
bool f=0;
char ans[300];
//用并查集判断联通 
int a[300]; 
int find(int x){
 if(fa[x]==x) return x;
 else return fa[x]=find(fa[x]);
}
void dfs(int x){
 for(int i=64;i<=125;i++){
  if(mp[x][i]){
   mp[x][i]=mp[i][x]=0;
   dfs(i);
  }
 }
 ans[++sum]=x;
}
int main(){
 cin >> n;
 for(int i=64;i<=125;i++)fa[i]=i;
 char x[2];
 for(int i=1;i<=n;i++){
  cin >> x;
  //建图加边 
  mp[x[0]][x[1]]=1;
  mp[x[1]][x[0]]=1;
  //欧拉回路 统计度数 
  du[x[0]]++;
  du[x[1]]++;
  fa[find(x[1])]=find(x[0]);
 }
 for(int i=64;i<=125;i++){
  //根 du是为了确定出现过 
  if(fa[i]==i && du[i]) cnt++;
 } 
 if(cnt!=1){
  f=1;
 } 
 cnt=0;
 head=0;
 for(int i=64;i<=125;i++){
  if(du[i]%2){
   cnt++;
   if(head==0) head=i;
  }
 }
 if(cnt&&cnt!=2) f=1;
 if(head==0){
  for(int i=64;i<=125;i++){
   if(du[i]){
    head=i;
    break;
   }
  }
 }
 dfs(head);
 if(f){
  cout<<"No Solution"<<endl;
  return 0;
 }
 else{
  for(int i=n+1;i>=1;i--){
   cout<<ans[i];
  }
  cout<<endl;
 }
 return 0;
}

RMQ:利用了2^k的特点,维护区间值。
例题:洛谷
P2880 [USACO07JAN]Balanced Lineup G
维护区间最大值&最小值
RMQ利用了dp的思想,需要预处理,维护区间值。

维护:

以区间最大值为例,设数组dp[i][j]:表示区间 [ i , i + 2 j − 1 [i,i+2^j-1 [i,i+2j1],即为以i为左端点,长度为 2 j 2^j 2j的区间。
那么就有状态转移方程
d p [ i ] [ j ] = m a x ( d p [ i ] [ j − 1 ] , d p [ i + 2 j − 1 ) ] [ j − 1 ] ) dp[i][j]=max(dp[i][j-1],dp[i+2^{j-1})][j-1]) dp[i][j]=max(dp[i][j1],dp[i+2j1)][j1])
含义:区间 [ i , i + 2 j − 1 ] [i,i+2^j-1] [i,i+2j1]的max = 区间 [ i , i + 2 j − 1 − 1 ] , [ i + 2 j − 1 , i + 2 j − 1 + 2 j − 1 − 1 ] ( 即 为 [ i + 2 j − 1 , i + 2 j − 1 ] ) [i,i+2^{j-1}-1], [i+2^{j-1},i+2^{j-1}+2^{j-1}-1](即为[i+2^{j-1},i+2^{j}-1]) [i,i+2j11],[i+2j1,i+2j1+2j11]([i+2j1,i+2j1])的max
初始化: d p [ i ] [ 0 ] = a [ 0 ] , 代 表 长 度 为 1 的 区 间 为 初 始 值 dp[i][0]=a[0],代表长度为1的区间为初始值 dp[i][0]=a[0],1

询问:

假设我们需要询问区间[l,r]中的最大值,那么我们需要找到这样一个数字k,使得 2 k < = r − l + 1 2^k <= r-l+1 2k<=rl+1
因此我们用一个while循环来寻找值,直到 2 k + 1 > r − l + 1 2^{k+1}>r-l+1 2k+1>rl+1跳出,此时的k就是满足 2 k < = r − l + 1 2^k<=r-l+1 2k<=rl+1的最大的k了
那么我们的ans即为:
q u e r y a n s = m a x ( d p [ l ] [ k ] , d p [ r − 2 k + 1 ] [ k ] ) query_{ans} = max(dp[l][k],dp[r-2^k+1][k]) queryans=max(dp[l][k],dp[r2k+1][k])
注意此处的2^k的实现都使用1<<K移位运算完成,记得加括号(
代码:

#include <iostream>
#include <string>
#include <cstdlib>
#include <queue>
#include<cstdio>
#include<string.h>
#include <stack>
#include<cmath>
#include <iomanip>
#include<map>
#include<set>
#include <algorithm>
#define ll long long
using namespace std;
const int maxn = 5e5+5;
int n, q;
int h[maxn],mi[maxn][20],ma[maxn][20];
void ini() {
	for (int i = 1;i <= n;i++) {
		mi[i][0] = ma[i][0] = h[i];
	}
	for (int j = 1;(1 << j) <= n;j++) {
		for (int i = 1;i + (1 << j) - 1 <= n;i++) {
			ma[i][j] = max(ma[i][j - 1], ma[i + (1 << (j - 1))][j - 1]);
		}
	}
	for (int j = 1;(1 << j) <= n;j++) {
		for (int i = 1;i + (1 << j) - 1 <= n;i++) {
			mi[i][j] = min(mi[i][j - 1], mi[i + (1 << (j - 1))][j - 1]);
		}
	}
}
int RMQmi(int l,int r) {
	//cout << "Min: " << endl;
	int k = 0;
	while ((1 << (k + 1)) <= r - l + 1) k++;
	return min(mi[l][k], mi[r-(1 << k) + 1][k]);
}
int RMQma(int l,int r) {
	int k = 0;
	//区间长度为r-l+1,k是最大的满足2^k <= r-l+1的数字
	while ((1 << (k + 1)) <= r - l + 1) k++;
	return max(ma[l][k], ma[r-(1 << k)+1][k]);
}
int main() {
	cin >> n >> q;
	for (int i = 1;i <= n;i++) {
		cin >> h[i];
	}
	ini();
	for (int i = 1;i <= q;i++) {
		int l, r;
		cin >> l >> r;
		//cout << RMQma(l, r) << " " << RMQmi(l, r) << endl;
		cout << RMQma(l, r) - RMQmi(l, r) << endl;
	}
	return 0;
}```

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值