dicnic https://www.luogu.com.cn/problem/P2740
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#include<iostream>
#define R register int
using namespace std;
const int N=1500010;
inline void in(R &x){
R f=1;x=0;char s=getchar();
while(!isdigit(s)){if(s=='-')f=-1;s=getchar();}
while(isdigit(s)){x=x*10+s-'0';s=getchar();}
x*=f;
}
int first[N],dep[N],len=1,n,m,cur[N],st,ed;
struct cod{int y,c,gg;}b[N<<1];
inline void ins(R x,R y,R c){
b[++len].gg=first[x];
b[len].y=y;b[len].c=c;
first[x]=len;
}
queue<int> q;
inline bool bfs(){
for(int i=1;i<=n;i++) dep[i]=0;
dep[st]=1;q.push(st);
while(!q.empty()){
int x=q.front(); q.pop();
for(R i=first[x];i>0;i=b[i].gg){
int y=b[i].y;
if(!dep[y] && b[i].c)
dep[y]=dep[x]+1,q.push(y);
}
}return dep[ed];
}
inline int dfs(R x,R dis)
{
if(x==ed || !dis) return dis;
R my=0,f;
for(R i=first[x];i>0;i=b[i].gg){
int y=b[i].y;
if(dep[y]==dep[x]+1 && (f=dfs(y,min(b[i].c,dis)))){
my+=f;dis-=f;
b[i].c-=f;b[i^1].c+=f;
if(dis==0) return my;
}
}return my;
}
int main()
{
in(m),in(n);st=1,ed=n;
for(R x,y,c,i=1;i<=m;i++){
in(x),in(y),in(c);
ins(x,y,c);ins(y,x,0);
}
R ans=0;
while(bfs()) ans+=dfs(st,214748364);
printf("%d\n",ans);
return 0;
}
最小生成树 prim算法 O(n^2) 题意:花费di标记点 或 mij连边 使得所有点被标记
题解:prim满足贪心,第一个必须标记点,此后 边权点权取min即为代价
https://loj.ac/problem/10066
#include<cstdio>
bool v[310];
int ma[310][310],d[310];
int n,ans;
void Prim(){
int minn,k;
for(int i=1;i<=n;i++){
minn=2147483647;
for(int j=1;j<=n;j++)
if(!v[j] && minn>d[j]) minn=d[j],k=j;
v[k]=true;ans+=d[k];
for(int j=1;j<=n;j++)
if(!v[j] && d[j]>ma[j][k]) d[j]=ma[j][k];
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&d[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&ma[i][j]);
Prim();
printf("%d",ans);
return 0;
}
最小生成树: kruskal算法
给出树T,要求构造完全图G,使得图中唯一的一棵最小生成树是T,求完全图G的最小边权和
题解: 在kruskal的基础上,每次连当前最小的边b,显然它所连接的2个并查集中,点两两之间都必须连
一条边(完全图) 即 tot[tx]+tot[ty]-1条边 , 而新连的边不能代替b ,所以新连的边要 >b[i].c
贡献即为 (tot[tx]+tot[ty]-1)*(b[i].c+1) + b[i].c
https://loj.ac/problem/10067
#include<cstdio>
#include<string>
#include<algorithm>
#define LL long long
using namespace std;
inline int in(){
int x=0,f=1;char ch=getchar();
for(;!isdigit(ch);ch=getchar()) if(ch=='-') f=-1;
for(;isdigit(ch);ch=getchar()) x=x*10+ch-'0';
return x*f;
}
const int N=1e5+10;
struct bian{int x,y;LL c;}b[N];;
LL tot[N],ans=0;
int f[N];
bool cmp(bian x,bian y) {return x.c<y.c;}
int find(int x){
if (f[x]==x) return x;
else return f[x]=find(f[x]);
}
int main() {
int n=in();
for(int i=1;i<n;i++) b[i].x=in(),b[i].y=in(),b[i].c=(LL)in();
sort(b+1,b+n,cmp);
for(int i=1;i<=n;i++) f[i]=i,tot[i]=1;
for(int i=1;i<n;i++){
int fx=find(b[i].x),fy=find(b[i].y);
if(fx!=fy){
ans+=(tot[fx]*tot[fy]-1)*(b[i].c+1)+b[i].c;
f[fx]=fy;
tot[fy]+=tot[fx];
}
}
printf("%lld",ans);
return 0;
}
最短路 floyd 求最小环上点的编号 special judge
枚举中转点k,d[i][j]表示以1~k-1为中转点的最短路长度(用floyd维护) 即求d[i][j]+a[i][k]+a[k][j]最小
路径的话,在floyd时,记录可行的中转点。更新答案就不停的递归找 我与中转点的中转点的中转点......
https://loj.ac/problem/10072
#include<cstdio>
#include<cstring>
int a[110][110],d[110][110],pos[110][110];
int ans[110],len=0;
int minn(int x,int y){ return x<y?x:y; }
void change(int x,int y)
{
if(pos[x][y]==0) return ;
change(x,pos[x][y]);
ans[++len]=pos[x][y];
change(pos[x][y],y);
}
int main()
{
int n,m,x,y,c;
long long tot=999999999;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)for(int j=1;j<=n;j++) a[i][j]=999999999;
for(int i=1;i<=m;i++)
{
scanf("%d %d %d",&x,&y,&c);
a[x][y]=a[y][x]=minn(a[x][y],c);
}
memcpy(d,a,sizeof(d));
for(int k=1;k<=n;k++)
{
for(int i=1;i<=n;i++) if(i!=k)
for(int j=1;j<=n;j++) if(j!=k && i!=j)
{
if(tot>(long long)a[i][k]+a[k][j]+d[i][j])
{
tot=(long long)a[i][k]+a[k][j]+d[i][j];
len=0;
ans[++len]=i; change(i,j);
ans[++len]=j; ans[++len]=k;
}
}
for(int i=1;i<=n;i++) if(i!=k)
for(int j=1;j<=n;j++)
if(j!=k && i!=j)
{
if(d[i][j]>d[i][k]+d[k][j])
{
d[i][j]=d[i][k]+d[k][j];
pos[i][j]=k;
}
}
}if(tot==999999999) printf("No solution.");
else for(int i=1;i<=len;i++) printf("%d ",ans[i]);
return 0;
}
dijkstra 单源最短路
用堆维护,性质:第一次从堆中取出为最短路,第k次为第k短路(特定题目里有用,一般不拿出第二次)
重点:一定要用vis[x] 因为可能会有非最优解压在堆底,这些不能拿来更新
https://www.luogu.com.cn/problem/P5304 这题能水90
题意:图G,多标记点,求任意有标记的2点路径权值和的最小值
90分水法:对于一个被标记的点,它通过dij找到的第一个被标记就是它到 所有被标记点的距离最小值
#include<cstdio>
#include<cstring>
#include<queue>
#define LL long long
using namespace std;
struct bian{int y,gg;LL c;}b[500100];
int first[100100],len=0;
LL maxl=1ll<<60;
void ins(int x,int y,LL c){
b[++len].y=y;
b[len].gg=first[x];b[len].c=c;
first[x]=len;
}
int read(){
int x=0;char c=getchar();
while(c<48) c=getchar();
while(c>47) x=x*10+c-'0',c=getchar();
return x;
}
struct node{
int id;LL c;
friend bool operator < (const node &x, const node &y)
{ return x.c>y.c; }
};
priority_queue<node> q;
bool r[100100];
int p[100100],n;
LL dis[100100];
bool B[100100],v[100100];
LL dijkstra(int st)
{
while(!q.empty()) q.pop();
node cur;
for(int i=1;i<=n;i++) dis[i]=maxl,v[i]=false;
cur.id=st,cur.c=0;dis[st]=0;
q.push(cur);
while(!q.empty())
{
int x=q.top().id;q.pop();
if(x!=st && r[x]) return dis[x];
if(!v[x])
{
v[x]=true;
for(int i=first[x];i>0;i=b[i].gg)
{
int y=b[i].y;
cur.id=y,cur.c=b[i].c+dis[x];
if(cur.c<dis[y]){
dis[y]=cur.c;
q.push(cur);
}
}
}
}return maxl;
}
LL minn(LL x,LL y) { return x<y?x:y; }
int main()
{
int T=read();
while(T--)
{
len=0;LL ans=maxl;
memset(first,0,sizeof(first));
memset(r,false,sizeof(r));
n=read();
int m=read(),k=read(),x;
for(int i=1;i<=m;i++){
int x=read(),y=read(),c=read();
ins(x,y,(LL)c);
}
for(int i=1;i<=k;i++) p[i]=read(),r[p[i]]=true;
for(int i=1;i<=k;i++) ans=minn(ans,dijkstra(p[i]));
printf("%lld\n",ans);
}
return 0;
}
dijkstra 多源最短路
将所有特殊点都扔进堆里!
将所有特殊点都扔进堆里!
将所有特殊点都扔进堆里!
靠染色配合跑多源最短路 , 跑某个点 被所有特殊点到达的路 中的最短路 记录来源被标记点col
再反过来建边 即某个点到任意一个特殊点的最短路 记录来源相当于记录去向被标记点
来源与去向一定都是被标记点,对于每一条有向边,若来源与去向不同,则可更新答案ans
还是这道题的正解之一 https://www.luogu.com.cn/problem/P5304
代码来自 https://www.luogu.com.cn/blog/Owencodeisking/solution-p5304
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=100000+10;
const int maxm=500000+10;
const ll inf=0x3f3f3f3f3f3f3f3f;
int n,m,k,a[maxn],X[maxm],Y[maxm],W[maxm],col[2][maxn],head[maxn],tot;
ll dis[2][maxn],ans;bool vis[maxn],iscity[maxn];
struct Edge{int to,next,val;}e[maxm];
struct node{
ll dis;int id;
node(ll _dis=0,int _id=0):dis(_dis),id(_id){}
};
inline bool operator < (const node &a,const node &b){return a.dis>b.dis;}
inline int read(){
register int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
return (f==1)?x:-x;
}
inline void addedge(int x,int y,int w){
e[++tot].to=ye[tot].val=w;
e[tot].next=head[x];head[x]=tot;
}
inline void Dijkstra(ll *dis,int *col){
for(int i=1;i<=n;i++) dis[i]=inf,vis[i]=0;
priority_queue<node> pq;
for(int i=1;i<=k;i++) dis[a[i]]=0 , col[a[i]]=a[i] , pq.push(node(0,a[i]));
while(!pq.empty()){
int u=pq.top().id;pq.pop();
if(vis[u]) continue;
vis[u]=1;
for(int i=head[u];i;i=e[i].next)
{
int v=e[i].to;
if(dis[v]>dis[u]+e[i].val)
{
dis[v]=dis[u]+e[i].val;
col[v]=col[u];
pq.push(node(dis[v],v));
}
}
}
}
inline void solve()
{
n=read(),m=read(),k=read();
int x,y,w;
for(int i=1;i<=m;i++)
{
x=read(),y=read(),w=read();
if(x!=y) addedge(x,y,w);
X[i]=x;Y[i]=y;W[i]=w;
}
for(int i=1;i<=k;i++) a[i]=read(),iscity[a[i]]=1;
Dijkstra(dis[0],col[0]);
for(int i=1;i<=n;i++) head[i]=0;
tot=0;
for(int i=1;i<=m;i++)
if(X[i]!=Y[i]) addedge(Y[i],X[i],W[i]);
Dijkstra(dis[1],col[1]);
ans=inf;
for(int i=1;i<=m;i++){
x=X[i];y=Y[i];w=W[i];
if(col[0][x]&&col[1][y]&&col[0][x]!=col[1][y]) ans=min(ans,dis[0][x]+dis[1][y]+w);
}
printf("%lld\n",ans);
for(int i=1;i<=n;i++) head[i]=iscity[i]=0;
tot=0;
}
int main()
{
int T=read();
while(T--) solve();
return 0;
}
spfa 求来回最短路
重点:v[x]判断i是否在队列中
0表示从st出发的最短路
1表示到st的最短路,将边反过来建,2次spfa
https://loj.ac/problem/10075
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;
bool v[21100];
int d[2][21100],first[21100],len=0,st,n,m,ans=0;
struct bian{int x,y,c,gg;}b[21100];
void Clear(int num){
len=0;
for(int i=1;i<=n;i++){
first[i]=0;
v[i]=false;
d[num][i]=999999999;
}
}
void ins(int x,int y,int c){
b[++len].y=y; b[len].c=c;
b[len].gg=first[x]; first[x]=len;
}
queue<int> q;
void spfa(int num)
{
v[st]=true; d[num][st]=0;
q.push(st);
while(!q.empty()){
int x=q.front();q.pop(); v[x]=false;
for(int i=first[x];i>0;i=b[i].gg){
int y=b[i].y;
if(d[num][y]>d[num][x]+b[i].c){
d[num][y]=d[num][x]+b[i].c;
if(!v[y]){
v[y]=true;
q.push(y);
}
}
}
}
}
int main()
{
scanf("%d %d %d",&n,&m,&st);
int x[100100],y[100100],c[100100];
Clear(0);
for(int i=1;i<=m;i++){
scanf("%d %d %d",&x[i],&y[i],&c[i]);
ins(x[i],y[i],c[i]);
}spfa(0);
Clear(1);
for(int i=1;i<=m;i++) ins(y[i],x[i],c[i]);
spfa(1);
for(int i=1;i<=n;i++){
if(ans<d[1][i]+d[0][i]) ans=d[1][i]+d[0][i];
}printf("%d",ans);
return 0;
}
Tarjan 割点
判定法则:dfn[x]<=low[y] 且 root没有父亲树,所以要有两棵子树不连通
https://www.luogu.com.cn/problem/P3388#submit
#include<cstdio>
#include<cstring>
struct bian{int y,gg;}b[200100];
int first[20100],len=0;
void ins(int x,int y){
len++;b[len].y=y;
b[len].gg=first[x];
first[x]=len;
}
int dfn[20100],low[20100],num=0,root,tot=0;
bool v[20100];
int mymin(int x,int y) { return x<y?x:y; }
void tarjan(int x){
dfn[x]=low[x]=++num;int flag=0;
for(int i=first[x];i>0;i=b[i].gg){
int y=b[i].y;
if(!dfn[y]){
tarjan(y);
low[x]=mymin(low[x],low[y]);
if(dfn[x]<=low[y]){
flag++;
if(x!=root || flag>1) v[x]=true;
}
}
else low[x]=mymin(low[x],dfn[y]);
}
}
int main()
{
int n,m,x,y;
memset(dfn,0,sizeof(dfn));
memset(first,0,sizeof(first));
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++){
scanf("%d %d",&x,&y);
if(x==y) continue;
ins(x,y);ins(y,x);
}
for(int i=1;i<=n;i++)
if(!dfn[i]) root=i,tarjan(i);
for(int i=1;i<=n;i++) if(v[i]) tot++;
printf("%d\n",tot);
for(int i=1;i<=n;i++) if(v[i])printf("%d ",i);
return 0;
}
Tarjan 割边
判定法则:dfn[x]<low[y] 考虑重边if(i!=(inedge^1)) low[x]=mymin(low[x],dfn[y]);
void tarjan(int x,int inedge){
dfn[x]=low[x]=++num;int flag=0;
for(int i=first[x];i>0;i=b[i].gg){
int y=b[i].y;
if(!dfn[y]){
tarjan(y,i);
low[x]=mymin(low[x],low[y]);
if(dfn[x]<low[y]) bridge[i]=bridge[i^1]=true;
}
else if(i!=(inedge^1)) low[x]=mymin(low[x],dfn[y]);
}
}
int main()
{
for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i,0);
}
Tarjan 强连通分量 判定法则:深搜结束后low[x]==dfn[x] 把栈内x之上的弹出
if(!id[y]) low[x]=minn(low[x],dfn[y]);
https://www.luogu.com.cn/problem/P2341
思路:强连通之后拓扑求是否有出度
连通两个强连通分量的边一定是桥,也就是说只有可能一整个环喜欢另一个环,而无法做到互相喜欢
森林一定是错的,要所有嘛
1.过桥后封闭
2.过桥后又有桥连向别的世界,直到封闭,之前走过的存在栈里
8字形的环属于同一个环(自己模拟)
#include<cstdio>
using namespace std;
int first[10010];
struct bian{int gg,y;}b[50010];
int num=0,dfn[10010],low[10010],du[10010],dcc[10010],dl=0;
int len=0,id[10010],n,m;
int top,sta[10010];
void ins(int x,int y)
{
b[++len].y=y;
b[len].gg=first[x];
first[x]=len;
}
int minn(int x,int y){ return x<y?x:y; }
void Tarjan(int x)
{
dfn[x]=low[x]=++num;
sta[++top]=x;id[x]=0;
for(int i=first[x];i!=0;i=b[i].gg){
int y=b[i].y;
if(!dfn[y]){
Tarjan(y);
low[x]=minn(low[x],low[y]);
}
else if(!id[y]) low[x]=minn(low[x],dfn[y]);
}
if(low[x]==dfn[x])
{
dl++;
do
{
id[sta[top]]=dl;
++dcc[dl];
}while(sta[top--]!=x);
}
}
int main()
{
int x,y;
scanf("%d %d",&n,&m);
for(int i=1;i<=m;i++)
scanf("%d %d",&x,&y),ins(x,y);
for(int i=1;i<=n;i++)
if(!dfn[i]) Tarjan(i);
for(int i=1;i<=n;i++)
for(int j=first[i];j!=0;j=b[j].gg){
if(id[i] != id[b[j].y])
du[id[i]]++;
}
int ans=0,tot=0;
for(int i=1;i<=dl;i++) if(du[i]==0) ans=dcc[i],tot++;
if(tot==1)printf("%d\n",ans);
else printf("0\n");
return 0;
}
匈牙利算法 最小覆盖点集、最大二分图匹配
bool dfs(int x){
for(int i=first[x];i;i=b[i].gg){
int y=b[i].y;
if(!v[y]){
v[y]=true;
if(!bf[y] || dfs(bf[y])){
bf[y]=x;return true;
}
}
}return false;
}
int main(){
for(int i=1;i<=n;i++){
memset(v,false,sizeof(v));
if(dfs(i)) ans++;
}
}
欧拉回路
2-SAT
费用流
最小割