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+2j−1],即为以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][j−1],dp[i+2j−1)][j−1])
含义:区间
[
i
,
i
+
2
j
−
1
]
[i,i+2^j-1]
[i,i+2j−1]的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+2j−1−1],[i+2j−1,i+2j−1+2j−1−1](即为[i+2j−1,i+2j−1])的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<=r−l+1
因此我们用一个while循环来寻找值,直到
2
k
+
1
>
r
−
l
+
1
2^{k+1}>r-l+1
2k+1>r−l+1跳出,此时的k就是满足
2
k
<
=
r
−
l
+
1
2^k<=r-l+1
2k<=r−l+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[r−2k+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;
}```