最短路
普通
对于一般的最短路,较为常用的有spfa算法和dijstra算法。大多数情况下,spfa更常用,但对于稠密图,spfa可能会被卡爆。不多说,上代码
spfa(使用前向星):
void spfa(){
queue<int>q;
q.push(S);
memset(dis,0x3f,sizeof(dis));
dis[S]=0;
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(dis[v]>dis[u]+e[i].w){
dis[v]=dis[u]+e[i].w;
if(!vis[v]){//判负环时标记每个点进队次数,cnt[i]>n-1就有负环
vis[v]=1;
q.push(v);
}
}
}
}
}
dijkstra(堆优化):
oid dijkstra(){
memset(dis,0x3f,sizeof(dis));
dis[S]=0;
priority_queue<Node>q;//需重载运算符
q.push((Node){S,0});
while(!q.empty()){
int u=q.top().now;
q.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(!vis[v]&&dis[v]>dis[u]+e[i].w){
dis[v]=dis[u]+e[i].w;
q.push((Node){v,dis[v]});
}
}
}
}
差分约束
题目会给出一些不等关系,结论: 对于每个不等式 x[i] - x[j] <= a[k],对结点 j 和 i 建立一条 j -> i的有向边,边权为a[k], 求x[n-1] - x[0] 的最大值就是求 0 到n-1的最 短路。
例:奶牛的站位Layout(usaco2005dec)
#include<bits/stdc++.h>
using namespace std;
const int N=1005;
const int M=10005;
int n,F,E,cnt,head[N],dis[N],vis[N],ti[N];
int flag=0;
struct Node{
int u,v,nxt,w;
}e[M<<1];
inline void add(int u,int v,int w){
e[++cnt].u=u;
e[cnt].v=v;
e[cnt].w=w;
e[cnt].nxt=head[u];
head[u]=cnt;
}
inline void spfa(int s){
memset(dis,0x3f,sizeof(dis));
dis[s]=0;
queue<int>q;
q.push(s);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(dis[v]>dis[u]+e[i].w){
dis[v]=dis[u]+e[i].w;
if(!vis[v]){
vis[v]=1;
ti[v]++;
if(ti[v]==n-1){
flag=1;
return;
}
q.push(v);
}
}
}
}
}
int main(){
scanf("%d%d%d",&n,&F,&E);
for(int i=1;i<=F;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
if(u>v)
swap(u,v);
add(u,v,w);
}
for(int i=1;i<=E;i++){
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
if(u<v)
swap(u,v);
add(u,v,-w);
}
spfa(1);
if(!flag&&dis[n]==0x3f3f3f3f)
printf("-1");
else if(flag)
printf("-2");
else
printf("%d",dis[n]);
return 0;
}
次短路
从起点和终点分别向对面跑最短路,得到dis1数组和dis2数组,枚举每个点,取dis1[i]+dis2[i]>最短路中最小的。
分层图
如果在跑图时有状态变化,比如拿到钥匙,获得buff之类的,可以考虑建分层图。
例:走迷宫(usaco2015dec)
/*
f数组将地图分为两层,第三维1是臭气层,0是正常层
*/
#include<bits/stdc++.h>
using namespace std;
int g[1200][1200]={},f[1005][1005][3]={};
int n,m,inf=0x3f3f3f3f;
int dx[5]={0,0,0,1,-1};
int dy[5]={0,1,-1,0,0};
struct Node{
int x,y,d,se;//x,y是位置,d是步数,se是层数,也就是有没有味道
};
queue<Node>q;
int check(int xx,int yy,int s){
if(!g[xx][yy])
return 0;
if(g[xx][yy]==3)//判臭气
return s;
return 1;
}
int bfs(){//广搜,用Node来做队列,直接从里面取
q.push((Node){1,1,0,0});
f[1][1][0]=0;
while(!q.empty()){
Node t=q.front();
q.pop();
for(int i=1;i<=4;i++){
int nx=t.x+dx[i],ny=t.y+dy[i];
int nd=t.d+1;//移动步数
int nse=t.se;
if(!check(nx,ny,t.se))
continue;
while(g[nx][ny]==4){//紫色滑到底
int tx=nx+dx[i],ty=ny+dy[i];
if(!check(tx,ty,t.se))
break;
nx+=dx[i];
ny+=dy[i];
nd++;
nse=0;
}
if(g[nx][ny]==2)//有鳄鱼,nse变成1,去f[][][1]获得臭气
nse=1;
if(f[nx][ny][nse]<=nd)// ↘
continue;//→→→→→→尝试更新最小值
f[nx][ny][nse]=nd;//↗
q.push((Node){nx,ny,nd,nse});
}
}
}
int main(){
memset(f,0x3f,sizeof(f));
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&g[i][j]);
bfs();
int ans=inf;
ans=min(f[n][m][0],f[n][m][1]);//从两层的终点找最小值
if(ans==inf)
printf("-1");
else
printf("%d",ans);
return 0;
}
线段树优化建图
这种题题目会说由点指向区间,或区间指向点,而线段树可以很好的解决这个问题而不是一个一个连线。建图时要看清题目条件,而考虑建一棵树还是两棵树。
例:Legacy(From Codeforces)(好题啊)
#include<bits/stdc++.h>
using namespace std;
#define re read()
#define ri register int
#define ll long long
#define mid ((T1[q].l+T1[q].r)>>1)
#define med ((T2[q].l+T2[q].r)>>1)
#define lc (q<<1)
#define rc (q<<1|1)
inline char nc(){
return getchar();
static char buf[10000000],*p1=buf,*p2=buf;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,10000000,stdin),p1==p2)?EOF:*p1++;
}
inline int read(){
char ch=nc();
int res=0;
while(ch<'0'||ch>'9')
ch=nc();
while(ch<='9'&&ch>='0'){
res=(res<<3)+(res<<1)+ch-'0';
ch=nc();
}
return res;
}
const int N=200005;
int head[N<<1],cnt,n,q,T,S;
int tot,pos1[N],pos2[N];
int U,V,L,R;
ll W,dis[N<<3],vis[N<<3];
struct Node{
int u,v,nxt;
ll w;
}e[N<<3];
struct Tree{
int l,r,num;
}T1[N<<2],T2[N<<2];
inline void add(int u,int v,ll w){
e[++cnt].v=v;
e[cnt].u=u;
e[cnt].w=w;
e[cnt].nxt=head[u];
head[u]=cnt;
}
inline void build1(int q,int l,int r){//构造下线段树
T1[q].num=++tot;
T1[q].l=l;
T1[q].r=r;
if(l==r){
pos1[l]=T1[q].num;
return;
}
build1(lc,l,mid);
build1(rc,mid+1,r);
add(T1[lc].num,T1[q].num,0);
add(T1[rc].num,T1[q].num,0);
}
inline void build2(int q,int l,int r){
T2[q].num=++tot;
T2[q].l=l;
T2[q].r=r;
if(l==r){
pos2[l]=T2[q].num;
add(pos2[l],pos1[l],0);
return;
}
build2(lc,l,med);
build2(rc,med+1,r);
add(T2[q].num,T2[lc].num,0);
add(T2[q].num,T2[rc].num,0);
}
inline void update_P_to_S(int q,int ql,int qr,ll w){
if(ql<=T2[q].l&&T2[q].r<=qr){
add(pos1[U],T2[q].num,w);
return;
}
if(ql<=med)
update_P_to_S(lc,ql,qr,w);
if(qr>med)
update_P_to_S(rc,ql,qr,w);
}
inline void update_S_to_P(int q,int ql,int qr,ll w){
if(ql<=T1[q].l&&T1[q].r<=qr){
add(T1[q].num,pos2[V],w);
return;
}
if(ql<=mid)
update_S_to_P(lc,ql,qr,w);
if(qr>mid)
update_S_to_P(rc,ql,qr,w);
}
inline void spfa(){
queue<int>q;
q.push(pos1[S]);
for(ri i=1;i<=n<<3;++i)
dis[i]=1e18;
dis[pos1[S]]=0;
while(!q.empty()){
int u=q.front();
q.pop();
for(ri i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(dis[u]+e[i].w<dis[v]){
dis[v]=dis[u]+e[i].w;
if(!vis[v]){
q.push(v);
vis[v]=1;
}
}
}
vis[u]=0;
}
}
int main(){
n=re;q=re;S=re;
build1(1,1,n);
build2(1,1,n);
for(ri i=1;i<=q;++i){
int op=re;
if(op==1){
U=re,V=re,W=re;
add(pos1[U],pos2[V],W);
}else if(op==2){//点到区间
U=re,L=re,R=re,W=re;
update_P_to_S(1,L,R,W);
}else{//区间到点
V=re,L=re,R=re,W=re;
update_S_to_P(1,L,R,W);
}
}
spfa();
for(ri i=1;i<=n;++i)
printf("%lld ",dis[pos1[i]]<1e18?dis[pos1[i]]:-1);
return 0;
}
最小生成树
常见算法有prim和kruskal,而我个人因为更好打而更喜欢kruskal。两个算法本质都是贪心。
代码:
kruskal:
bool cmp(const Node& a,const Node& b){
return a.w<b.w;
}
int getfa(int x){
return x==fa[x]?x:fa[x]=getfa(fa[x]);
}
int merge(int x,int y){
int dx=getfa(x);
int dy=getfa(y);
if(dx!=dy){
fa[dx]=dy;
size[dy]+=size[dx];
return 1;
}
return 0;
}
void kruskal(){
sort(e+1,e+m+1,cmp);
for(ri i=1;i<=m;++i){
int u=e[i].u,v=e[i].w;
if(merge(u,v)){
ans+=e[i].w;
}
}
}
prim(其实可以堆优化,但这里没打):
int prime(int start=1){
int k=0,ans=0;
for(int i=1;i<=n;i++){
mincost[i]=g[start][i];
}
mincost[start]=0;
for(int i=1;i<n;i++){
int mi=inf;
for(int j=1;j<=n;j++){
if(mincost[j]!=0&&mi>mincost[j]){
k=j;
mi=mincost[j];
}
}
ans+=mi;
mincost[k]=0;
for(int j=1;j<=n;j++){
if(mincost[j]!=0&&g[k][j]<mincost[j]){
mincost[j]=g[k][j];
}
}
}
return ans;
}
拓扑排序
记录每个点的入度,每次有其他访问到时将其减一,为零时可入队
代码:
void topsort(){
queue<int>q;
for(ri i=1;i<=n;++i)
if(!du[i])
q.push(i);
while(!q.empty()){
int u=q.front();
q.pop();
x[++tot]=u;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
--du[v];
if(!du[v])
q.push(v);
}
}
}
图的连通性
tarjan算法,记录dfs序和low数组,low数组表示该点能回到的最小的dfs序编号
无向图求割边,割点:
例:网络(CEOI1996)
void tarjan(int u,int fa){
int son=0;//儿子,特判
dft[u]=low[u]=++tot;
for(int i=f[u];i;i=e[i].nxt){
int v=e[i].v;
if(!dft[v]){
son++;
tarjan(v,u);
low[u]=min(low[u],low[v]);
if(low[v]>=dft[u]){
flag[u]=1;
}
}
else if(v!=fa){
low[u]=min(low[u],dft[v]);
}
if(u==root&&son==1){//特判,如果是根且只有一个儿子
flag[u]=0;
}
}
}
有向图求强连通分量
例:[HAOI2006]受欢迎的牛
void dfs_tarjan(int u){
low[u]=dfn[u]=++num;
sta.push(u);
vis[u]=1;
for(int i=f[u];i;i=e[i].nxt){
int v=e[i].v;
if(!dfn[v]){
dfs_tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v]){
low[u]=min(low[u],dfn[v]);
}
}
if(dfn[u]==low[u]){
int t;
cnt_color++;
while(1){
t=sta.top();
sta.pop();
color[t]=cnt_color;
size[cnt_color]++;
if(t==u){
break;
}
}
}
}
网络流及费用流
只会dinic算法。。。
有一道题叫网络扩容,既用到了网络流,有用到了费用流。先跑网络流,在此基础上每条边再加上一条有费用的边再跑费用流。
例:网络扩容(zjoi2010)
#include<bits/stdc++.h>
using namespace std;
const int SS=0;
const int N=1005;
const int M=5005;
int S,T,vis[N<<1],dis[N<<1];
int cur[N<<1];
int ans;
int cnt=1,n,m,k,head[N<<1];
int U[M<<2],V[M<<2],W[M<<2];
struct Node{
int v,w,c,nxt;
}e[M<<3];
inline void add(int u,int v,int c,int w){
e[++cnt].v=v;
e[cnt].w=w;
e[cnt].c=c;
e[cnt].nxt=head[u];
head[u]=cnt;
}
inline int bfs(){
memset(dis,-1,sizeof(dis));
dis[S]=0;
queue<int>q;
q.push(S);
while(!q.empty()){
int u=q.front();
q.pop();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(dis[v]==-1&&e[i].c>0){
dis[v]=dis[u]+1;
q.push(v);
if(v==T)
return 1;
}
}
}
return 0;
}
inline int dfs(int u,int f){
if(u==T||f==0)
return f;
int used=0;
for(int &i=cur[u];i;i=e[i].nxt){
int v=e[i].v;
if(dis[v]==dis[u]+1&&e[i].c>0){
int w=dfs(v,min(f,e[i].c));
if(!w)
continue;
f-=w;
e[i].c-=w;
e[i^1].c+=w;
used+=w;
if(f==0)
break;
}
}
if(used==0)
dis[u]=-1;
return used;
}
inline int dinic(){
int mflow=0;
while(bfs()){
memcpy(cur,head,sizeof(head));
mflow+=dfs(S,0x3f3f3f3f);
}
return mflow;
}
inline int spfa(){
queue<int>q;
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
dis[SS]=0;
q.push(SS);
while(!q.empty()){
int u=q.front();
q.pop();
vis[u]=0;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(dis[v]>dis[u]+e[i].w&&e[i].c>0){
dis[v]=dis[u]+e[i].w;
if(!vis[v]){
vis[v]=1;
q.push(v);
}
}
}
}
return dis[T]<dis[2005];
}
inline int dfs2(int u,int f){
if(u==T||f==0)
return f;
int used=0;
vis[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].v;
if(!vis[v]&&dis[u]+e[i].w==dis[v]&&e[i].c>0){
int w=dfs2(v,min(f,e[i].c));
if(w){
ans+=e[i].w*w;
e[i].c-=w;
e[i^1].c+=w;
used+=w;
}
if(f==used)
break;
}
}
return used;
}
inline void dinic_fee(){
while(spfa()){
memset(vis,0,sizeof(vis));
dfs2(SS,0x3f3f3f3f);
}
}
int main(){
scanf("%d%d%d",&n,&m,&k);
S=1,T=n;
for(int i=1;i<=m;i++){
int u,v,c,w;
scanf("%d%d%d%d",&u,&v,&c,&w);
U[i]=u,V[i]=v,W[i]=w;
add(u,v,c,0);
add(v,u,0,0);
}
printf("%d ",dinic());
add(SS,S,k,0);
add(S,SS,0,0);
for(int i=1;i<=m;i++){
add(U[i],V[i],k,W[i]);
add(V[i],U[i],0,-W[i]);
}
dinic_fee();
printf("%d",ans);
return 0;
}
总结那就先这样吧