图论总结

DFS

套路1:

​ 构造的时候选一颗DFS树忽略其他边进行构造,

​ (对于点标和度数奇偶性的问题,假如所有的点的度数和为偶数的话,则成立)

套路2:

​ 对于一个无向图求出一棵DFS树,然后每条非树边对应了一个环(每条非树边必然连向祖先)

​ 例题:$$Xor$$和仙人掌判定(改点到祖先在树的路径)。

//dfs树+树形DP
#include<iostream> 
#include<cstdio>
#include<cmath>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
#define ll long long
#define N 500010
#define M 1000010
#define P 998244353
char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;}
int gcd(int n,int m){return m==0?n:gcd(m,n%m);}
int read()
{
    int x=0,f=1;char c=getchar();
    while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();}
    while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
    return x*f;
}
int T,n,m,p[N],f[N],size[N],fac[N],inv[N],inv2[N],fa[N],from[N],deep[N],t;
bool flag[N];
struct data{int to,nxt,op;
}edge[M<<1];
void addedge(int x,int y){t++;edge[t].to=y,edge[t].nxt=p[x],edge[t].op=0,p[x]=t;}
void inc(int &x,int y){x+=y;if (x>=P) x-=P;}
int C(int n,int m){return 1ll*fac[n]*inv[m]%P*inv[n-m]%P;}
int calc(int n,int m){return 1ll*C(n,2*m)*C(2*m,m)%P*fac[m]%P*inv2[m]%P;}
void dfs(int k)
{
    flag[k]=1;
    for (int i=p[k];i;i=edge[i].nxt) 
    if (!flag[edge[i].to]&&!edge[i].op)
    {
        size[k]++;
        dfs(edge[i].to);
        int s=0;
        for (int j=0;j<=(size[edge[i].to]>>1);j++)
        inc(s,1ll*calc(size[edge[i].to],j)*(size[edge[i].to]-(j<<1)+1)%P);
        f[k]=1ll*f[k]*s%P*f[edge[i].to]%P;
    }
}
bool check(int i){edge[i].op++;if (i&1) edge[i+1].op++;else edge[i-1].op++;return edge[i].op<=1;}
bool maketag(int x,int y,int i)
{
    if (!check(i)) return 0;
    while (x!=y) {if (!check(from[x])) return 0;x=fa[x];}
    return 1;
}
bool iscactus(int k)
{
    flag[k]=1;
    for (int i=p[k];i;i=edge[i].nxt)
    if (edge[i].to!=fa[k])
        if (flag[edge[i].to]) {if (deep[edge[i].to]<deep[k]&&!maketag(k,edge[i].to,i)) return 0;}
        else
        {
            fa[edge[i].to]=k;
            deep[edge[i].to]=deep[k]+1;
            from[edge[i].to]=i;
            if (!iscactus(edge[i].to)) return 0;
        }
    return 1;
}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("bzoj4784.in","r",stdin);
    freopen("bzoj4784.out","w",stdout);
    const char LL[]="%I64d\n";
#else
    const char LL[]="%lld\n";
#endif
    T=read();
    while (T--)
    {
        n=read(),m=read();
        t=0;for (int i=1;i<=n;i++) p[i]=0,size[i]=0,f[i]=1,flag[i]=0,deep[i]=0;
        fac[0]=1;for (int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%P;
        inv[0]=inv[1]=1;for (int i=2;i<=n;i++) inv[i]=P-1ll*(P/i)*inv[P%i]%P;
        inv2[0]=1;for (int i=1;i<=n;i++) inv2[i]=1ll*inv2[i-1]*inv[2]%P;
        for (int i=2;i<=n;i++) inv[i]=1ll*inv[i]*inv[i-1]%P;
        for (int i=1;i<=m;i++)
        {
            int x=read(),y=read();
            addedge(x,y),addedge(y,x);
        }
        if (!iscactus(1)) {printf("0\n");continue;}
        for (int i=1;i<=n;i++) flag[i]=0;
        int ans=1;
        for (int k=1;k<=n;k++)
        if (!flag[k])
        {
            dfs(k);
            int s=0;
            for (int i=0;i<=size[k]/2;i++) inc(s,calc(size[k],i));
            ans=1ll*ans*s%P*f[k]%P;
        }
        printf("%d\n",ans);
    }
    return 0;
}

BFS

在BFS图(无向图)中 。边只可能在相邻的两层或同一层之间连(BFS最优解的特性),并且每个点至少和上一层的一个点相连(按照距离分层)

强连通/双连通分量($$Tarjan$$)

远古套路0:

强连通缩点,在DAG上DP

//缩点
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
struct Edge
{
	int from;
	int to;
	int next;
}e[200005],E[200005];
int n,m,edgenum,head[10005],w[10005],W[10005],low[10005],dfn[10005],where[10005];
int scc,stack[10005],f[10005],in[10005],top,ans;
queue<int>q;
vector<int>SCC[10005];
bool flag[10005];
void add(int u,int v)
{
	e[++edgenum].to=v;
	e[edgenum].from=u;
	e[edgenum].next=head[u];
	head[u]=edgenum;
}
int ind=0;
void Tarjan(int node,int fa)
{
	if(dfn[node])return;
	dfn[node]=++ind;
	low[node]=dfn[node];
	stack[++top]=node;
	flag[node]=1;
	for(int hd=head[node];hd;hd=e[hd].next)
	{
		if(!dfn[e[hd].to])
		{
			Tarjan(e[hd].to,node);
			low[node]=min(low[node],low[e[hd].to]);
		}
		else if(flag[e[hd].to])
		{
			low[node]=min(low[node],dfn[e[hd].to]);
		}
	}
	if(low[node]==dfn[node])
	{
		int v=stack[top--];
		SCC[++scc].push_back(v);
		where[v]=scc;
		flag[v]=0;
		W[scc]+=w[v];
		while(v!=node)
		{
			v=stack[top--];
			SCC[scc].push_back(v);
			where[v]=scc;
			flag[v]=0;
			W[scc]+=w[v];
		}
	}
}
void add2(int hd)
{
	E[++edgenum].from=where[e[hd].from];
	E[edgenum].to=where[e[hd].to];
	E[edgenum].next=head[E[edgenum].from];
	head[E[edgenum].from]=edgenum;
	in[E[edgenum].to]++;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
	  scanf("%d",&w[i]);
	for(int i=1;i<=m;i++)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		add(u,v);
	}
	for(int i=1;i<=n;i++)
	  Tarjan(i,-1);
	edgenum=0;
	memset(head,0,sizeof(head));
	for(int i=1;i<=m;i++)
	{
		if(where[e[i].from]!=where[e[i].to])
		  add2(i);
	}
	for(int i=1;i<=scc;i++)
	  if(in[i]==0)
	  {
	    q.push(i);
	    f[i]=W[i];
	    ans=max(ans,f[i]);
	  }
	while(!q.empty())
	{
		int node=q.front();
		q.pop();
		ans=max(ans,f[node]);
		for(int hd=head[node];hd;hd=E[hd].next)
		{
			f[E[hd].to]=max(f[E[hd].to],f[node]+W[E[hd].to]);
			in[E[hd].to]--;
			if(in[E[hd].to]==0)
			{
				q.push(E[hd].to);
			}
		}
	}
	printf("%d\n",ans);
	return 0;
}

套路1:

边(点)双联通分量缩起来形成一棵树,然后在树上搞事情。(比如加最少边让这个图变成双连通)

若一个无向连通图不存在割点,则称它为“点双连通图”。若一个无向连通图不存在割边,则称它为“边双连通图”。
无向图的极大点双连通子图称为“点双连通分量”,简记为“v-DCC”。无向连通图的极大边双连通子图被称为“边双连通分量”,简记为“e-DCC”。二者统称为“双连通分量”,简记为“DCC”。

点双连通分量处理起来比较麻烦

例题:加最少边让图变成双连通(做法:先缩边双联通分量,然后答案就是$$ \lceil 叶子节点/2 \rceil $$

#include<iostream>
#include<cstdio>
#include<cmath>
#include<queue>
#include<cstring>
#include<algorithm>
#define lson x<<1
#define rson x<<1|1
#define ll long long
#define rint register int
#define mid ((st[x].l + st[x].r) >> 1)
using namespace std;
template <typename xxx> inline void read(xxx &x) {
    char c = getchar(),f = 1;x = 0;
    for(;c ^ '-' && !isdigit(c);c = getchar());
    if(c == '-') c = getchar(),f = -1;
    for(;isdigit(c);c = getchar()) x = (x<<1) + (x<<3) + (c ^ '0');
    x *= f;
}
template<typename xxx> inline void print(xxx x)
{
    if(x<0){putchar('-');x=-x;}
    if(x>9) print(x/10);
    putchar(x%10+'0');
}
const int maxn = 200010;
const int inf = 0x7fffffff;
const int mod = 1e9 + 7;
struct edge{
    int to,last,fg,from;    
}e[maxn];
int head[maxn],tot;
inline void add(int from,int to) {
    ++tot;
    e[tot].to = to;
    e[tot].from = from;
    e[tot].last = head[from];
    head[from] = tot;
}
int n,m;
int dfn[maxn],low[maxn],cnt;
inline void tarjan(int x,int in_edge) {
    dfn[x] = low[x] = ++cnt;
    for(rint i = head[x];i; i = e[i].last) {
        if(!dfn[e[i].to]) {
            tarjan(e[i].to,i);
            if(low[e[i].to] < low[x]) low[x] = low[e[i].to];
            if(low[e[i].to] > dfn[x]) {
                e[i].fg = e[i^1].fg = 1;
            }
        }
        else if(i ^ (in_edge ^ 1) && dfn[e[i].to] < low[x]) low[x] = dfn[e[i].to];
    }
}
int col[maxn],num,in[maxn];
inline void ddfs(int x) {
    col[x] = num;
    for(rint i = head[x];i;i = e[i].last) {
        if(col[e[i].to] || e[i].fg) continue;
        ddfs(e[i].to);
    }
}
int main()
{
    read(n);read(m);tot = 1;
    for(rint i = 1;i <= m; ++i) {
        int x,y;
        read(x);read(y);
        add(x,y);add(y,x);
    }   
    for(rint i = 1;i <= n; ++i) {
        if(!dfn[i]) {
            tarjan(i,0);
        }
    }
    for(rint i = 1;i <= n; ++i) {
        if(!col[i]) {
            ++num;
            ddfs(i);
        }
    }
    for(rint i = 2;i <= tot; ++i) {
        if(col[e[i].from] == col[e[i].to]) continue;
        ++in[col[e[i].from]];
        ++in[col[e[i].to]];
    }
    int ans = 0;
    for(rint i = 1;i <= num; ++i) 
        if(in[i] == 2) ++ans;
    print((ans + 1) / 2);
    return 0;
}

套路2:

​ 动态维护边双连通分量树

离线:先建树后处理。在线:$$lct$$/虚树

//BZOJ2959 长跑
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cmath>
#include<vector>
#include<bitset>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define pr pair<int,int>
#define ll long long
using namespace std;
int g[150010];
int fa[150010];
int f[150010];
int s[150010][2];
int v[150010];
int sum[150010];
int size[150010];
int st[150010];
int r[150010];
int n,m;
int opt;
int x,y;
int find(int x)
{
    if(fa[x]==x)
    {
        return x;
    }
    return fa[x]=find(fa[x]);
}
int judge(int x)
{
    if(g[x]==x)
    {
        return x;
    }
    return g[x]=judge(g[x]);
}
int is_root(int rt)
{
    return rt!=s[find(f[rt])][0]&&rt!=s[find(f[rt])][1];
}
int get(int rt)
{
    return rt==s[find(f[rt])][1];
}
void pushup(int rt)
{
    sum[rt]=sum[s[rt][0]]+sum[s[rt][1]]+size[rt];
}
void pushdown(int rt)
{
    if(r[rt])
    {
        swap(s[rt][0],s[rt][1]);
        r[s[rt][0]]^=1;
        r[s[rt][1]]^=1;
        r[rt]^=1;
    }
}
void rotate(int rt)
{
    int fa=find(f[rt]);
    int anc=find(f[fa]);
    int k=get(rt);
    if(!is_root(fa))
    {
        s[anc][get(fa)]=rt;
    }
    s[fa][k]=s[rt][k^1];
    f[s[fa][k]]=fa;
    s[rt][k^1]=fa;
    f[fa]=rt;
    f[rt]=anc;
    pushup(fa);
    pushup(rt);
}
void splay(int rt)
{
    int top=0;
    st[++top]=rt;
    for(int i=rt;!is_root(i);i=find(f[i]))
    {
        st[++top]=find(f[i]);
    }
    for(int i=top;i>=1;i--)
    {
        pushdown(st[i]);
    }
    for(int fa;!is_root(rt);rotate(rt))
    {
        if(!is_root(fa=find(f[rt])))
        {
            rotate(get(fa)==get(rt)?fa:rt);
        }
    }
}
void access(int rt)
{
    for(int x=0;rt;x=rt,rt=find(f[rt]))
    {
        splay(rt);
        s[rt][1]=x;
        pushup(rt);
    }
}
void reverse(int rt)
{
    access(rt);
    splay(rt);
    r[rt]^=1;
}
void link(int x,int y)
{
    reverse(x);
    f[x]=y;
}
void dfs(int x,int rt)
{
    fa[x]=rt;
    if(s[x][0])
    {
        dfs(s[x][0],rt);
    }
    if(s[x][1])
    {
        dfs(s[x][1],rt);
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&v[i]);
        size[i]=v[i];
        sum[i]=v[i];
        fa[i]=i;
        g[i]=i;
    }
    while(m--)
    {
        scanf("%d%d%d",&opt,&x,&y);
        int fx=find(x);
        int fy=find(y);
        if(opt==1)
        {  
            if(fx!=fy)
            {
                if(judge(fx)!=judge(fy))
                {
                    link(fx,fy);
                    g[g[fx]]=g[fy];
                }
                else
                {
                    reverse(fx);
                    access(fy);
                    splay(fy);
                    size[fy]=sum[fy];
                    dfs(fy,fy);
                    s[fy][0]=0;
                }
            }
        }
        else if(opt==2)
        {
            splay(fx);
            size[fx]+=y-v[x];
            sum[fx]+=y-v[x];
            v[x]=y;
        }
        else
        {
            if(judge(fx)!=judge(fy))
            {
                printf("-1\n");
            }
            else
            {
                reverse(fx);
                access(fy);
                splay(fy);
                printf("%d\n",sum[fy]);
            }
        }
    }
}

在OI比赛中结合数据结构考(图论模型一般比较明显)。

//强连通分量
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define inf 0x3f3f3f3f

vector<int> g[10010];
int color[10010],dfn[20020],low[20020],stack[20020],vis[10010],cnt[10010];
int deep,top,n,m,sum,ans;

void tarjan(int u)
{
    dfn[u]=++deep;
    low[u]=deep;
    vis[u]=1;
    stack[++top]=u;
    int sz=g[u].size();
    for(int i=0;i<sz;i++)
    {
        int v=g[u][i];
        if(!dfn[v])
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else
        {
            if(vis[v])
            {
                low[u]=min(low[u],low[v]);
            }
        }
    }
    if(dfn[u]==low[u])
    {
        color[u]=++sum;
        vis[u]=0;
        while(stack[top]!=u)
        {
            color[stack[top]]=sum;
            vis[stack[top--]]=0;
        }
        top--;
    }
}

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int from,to;
        scanf("%d%d",&from,&to);
        g[from].push_back(to);
    }
    for(int i=1;i<=n;i++)
    {
        if(!dfn[i])
        {
            tarjan(i);
        }
    }
    for(int i=1;i<=n;i++)
    {
        cnt[color[i]]++;
    }
    for(int i=1;i<=sum;i++)
    {
        if(cnt[i]>1)
        {
            ans++;
        }
    }
    printf("%d\n",ans);
}
//割点
#include<cstdio> 
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define hi printf("hi!");
using namespace std;

vector<int> g[10010];
int dfn[10010],low[10010],iscut[10010],son[10010];
int deep,root,n,m,ans;

int tarjan(int u,int fa)
{
    int child=0,lowu;
    lowu=dfn[u]=++deep;
    int sz=g[u].size();
    for(int i=0;i<sz;i++)
    {
        int v=g[u][i];
        if(!dfn[v])
        {
            child++;
            int lowv=tarjan(v,u);
            lowu=min(lowu,lowv);
            if(lowv>dfn[u])
            {
                iscut[u]=1;
            }
        }
        else
        {
            if(v!=fa&&dfn[v]<dfn[u])
            {
                lowu=min(lowu,dfn[v]);
            }
        } 
    }
    if(fa<0&&child==1)
    {
        iscut[u]=false;
    }
    low[u]=lowu;
    return lowu;
} 

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int from,to;
        scanf("%d%d",&from,&to);
        g[from].push_back(to);
        g[to].push_back(from);
    }
    for(int i=1;i<=n;i++)
    {
        if(!dfn[i])
        {
            root=i;
            tarjan(i,-1);
        }
    }
    for(int i=1;i<=n;i++)
    {
        if(iscut[i])
        {
            ans++;
        }
    }
    printf("%d\n",ans);
    for(int i=1;i<=n;i++)
    {
        if(iscut[i])
        {
            printf("%d ",i);
        }
    }
}
//割边/桥
#include<cstdio> 
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
#define hi printf("hi!");
using namespace std;

vector<pair<int,int> >bridge;
vector<int> g[10010];
int dfn[10010],low[10010];
int deep,root,n,m,ans;

int tarjan(int u,int fa)
{
    int lowu;
    lowu=dfn[u]=++deep;
    int sz=g[u].size();
    for(int i=0;i<sz;i++)
    {
        int v=g[u][i];
        if(!dfn[v])
        {
            int lowv=tarjan(v,u);
            lowu=min(lowu,lowv);
            if(lowv>dfn[u])
            {
                int from,to;
                from=u;
                to=v;
                if(from>to)
                {
                    swap(from,to);
                }
                bridge.push_back(make_pair(from,to));
            }
        }
        else
        {
            if(v!=fa&&dfn[v]<dfn[u])
            {
                lowu=min(lowu,dfn[v]);
            }
        } 
    }
    low[u]=lowu;
    return lowu;
} 

int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
    {
        int from,to;
        scanf("%d%d",&from,&to);
        g[from].push_back(to);
        g[to].push_back(from);
    }
    for(int i=1;i<=n;i++)
    {
        if(!dfn[i])
        {
            root=i;
            tarjan(i,-1);
        }
    }
    for(int i=0;i<bridge.size();i++)
    {
        printf("%d %d\n",bridge[i].first,bridge[i].second);
    }
}

欧拉回路(它很妙

需要掌握正确的有/无向图的欧拉回路/路径的求法

#include "bits/stdc++.h"

using namespace std;
const int maxn = 1e3;
int vis[maxn][maxn];
int n, m, x, y;
vector<int> e[maxn];
deque<int> q;

void addEdge(int a, int b) {
    e[a].push_back(b);
    e[b].push_back(a);
}

bool dfs(int now, int sum) {
    if (sum == m) {
        while (!q.empty()) {
            cout << q.back() << " ";
            q.pop_back();
        }
        cout << endl;
        return 1;
    }
    for (int i = 0; i < e[now].size(); i++) {
        if (!vis[now][e[now][i]]) {
            vis[now][e[now][i]] = 1;
            vis[e[now][i]][now] = 1;
            q.push_front(e[now][i]);
            if (dfs(e[now][i], sum + 1))
                return 1;
            q.pop_front();
            vis[now][e[now][i]] = 0;
            vis[e[now][i]][now] = 0;
        }
    }
    return 0;
}

int main() {、
    cin >> n >> m;
    for (int i = 0; i < m; i++) {
        cin >> x >> y;
        addEdge(x, y);
    }
    q.push_front(1);
    if (!dfs(1, 0))
        cout << "NO answer" << endl;
    return 0;
}

套路1:

构造(构造一个序列或者能得一个无向图定向使得入度和出度差不超过1)

平面上有n个点$$(x,y)$$,将这些点红蓝染色实点每行每列的红蓝点个数的差不超过1(解法x,y建边,然后定向,染色真TM奇特)。

生成$$2^n$$ 的01串(串首尾相连),使得所有长度为n的01串都出现过

套路2:

根据充要条件处理度数(有向图联通其入度=出度;无向图只要联通且度数是偶数)

无向图加最少的边使得整个图成为欧拉图(做法为原理)。

混合图欧拉回路(无向图定向,性质)

//POJ 1637 
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <string>
using namespace std;
const int MAXN = 1010;
const int MAXM = 50010;
const int INF = 0x3f3f3f3f;
struct Edge
{
	int v, f;
	int next;
}edge[MAXM];
int n, m;
int cnt;
int s, t;
int first[MAXN], level[MAXN];
int q[MAXN];
int ind[MAXN], outd[MAXN];
int totFlow;
void init()
{
	cnt = 0;
	totFlow = 0;
	memset(first, -1, sizeof(first));
	memset(ind, 0, sizeof(ind));
	memset(outd, 0, sizeof(outd));
}
void read(int u, int v, int f)
{
	edge[cnt].v = v, edge[cnt].f = f;
	edge[cnt].next = first[u], first[u] = cnt++;
}
void read_graph(int u, int v, int f)
{
	read(u, v, f);
	read(v, u, 0);
}
int bfs(int s, int t)
{
	memset(level, 0, sizeof(level));
	level[s] = 1;
	int front = 0, rear = 1;
	q[front] = s;
	while(front < rear)
	{
		int x = q[front++];
		if(x == t) return 1;
		for(int e  = first[x]; e != -1; e = edge[e].next)
		{
			int v = edge[e].v, f = edge[e].f;
			if(!level[v] && f)
			{
				level[v] = level[x] + 1;
				q[rear++] = v;
			}
		}
	}
	return 0;
}
int dfs(int u, int maxf, int t)
{
	if(u == t) return maxf;
	int ret = 0;
	for(int e = first[u]; e != -1; e = edge[e].next)
	{
		int v = edge[e].v, f = edge[e].f;
		if(level[v] == level[u] + 1 && f)
		{
			int Min = min(maxf-ret, f);
			f = dfs(v, Min, t);
			edge[e].f -= f;
			edge[e^1].f += f;
			ret += f;
			if(ret == maxf) return ret;
		}
	}
	return ret;
}
int Dinic(int s, int t)
{
	int ans = 0;
	while(bfs(s, t)) ans += dfs(s, INF, t);
	return ans;
}
void read_case()
{
	init();
	scanf("%d%d", &n, &m);
	while(m--)
	{
		int u, v, flag;
		scanf("%d%d%d", &u, &v, &flag);
		outd[u]++, ind[v]++;
		if(u != v)
		{
			if(!flag) read_graph(u, v, 1);
		}
	}
}
int build()
{
	int flag = 1;
	s = 0, t = n+1;
	for(int i = 1; i <= n; i++)
	{
		if((ind[i]+outd[i]) & 1) //出度加入度是奇数 
		{
			return 0;
		}
		else if(outd[i] > ind[i]) //出度大于入度 
		{
			int dif = outd[i]-ind[i];
			read_graph(s, i, dif/2);
			totFlow += dif/2;
			
		} //可能有入度等于出度的情况,连不连无所谓 
		else
		{
			int dif = ind[i]-outd[i];
			read_graph(i, t, dif/2);
		}
	}
	return 1;
}
void solve()
{
	read_case();
	int flag = build();
	int ans = Dinic(s, t);
	if(!flag) printf("impossible\n");
	else if(ans >= totFlow) printf("possible\n");
	else printf("impossible\n"); 
}
int main()
{
	int T;
	scanf("%d", &T);
	while(T--)
	{
		solve();
	}
	return 0;
}

2-SAT

掌握做法和输出方案

//2-sat
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
struct Edge
{
	int from;
	int to;
	int next;
}e[4000005];
int n,m,edgenum,head[2000005],p[2000005];
int dfn[2000005],low[2000005],st[2000005],scc,top;
bool flag[2000005];
queue<int>q;
void add(int u,int v)
{
	e[++edgenum].from=u;
	e[edgenum].to=v;
	e[edgenum].next=head[u];
	head[u]=edgenum;
}
int ind;
void Tarjan(int node)
{
	st[++top]=node;
	dfn[node]=low[node]=++ind;
	flag[node]=1;
	for(int hd=head[node];hd;hd=e[hd].next)
	{
		int to=e[hd].to;
		if(!dfn[to])
		{
			Tarjan(to);
			low[node]=min(low[node],low[to]);
		}
		else if(flag[to])low[node]=min(low[node],dfn[to]);
	}
	if(low[node]==dfn[node])
	{
		int v=st[top--];
		scc++;
		flag[v]=0;
		p[v]=scc;
		while(v!=node)
		{
			v=st[top--];
			flag[v]=0;
			p[v]=scc;
		}
	}
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int x1,v1,x2,v2;
		scanf("%d%d%d%d",&x1,&v1,&x2,&v2);
		add(x1+(!v1)*n,x2+v2*n);
		add(x2+(!v2)*n,x1+v1*n);
	}
	for(int i=1;i<=2*n;i++)
	  if(!dfn[i])Tarjan(i);
	for(int i=1;i<=n;i++)
	{
		if(p[i]==p[n+i])
		{
			printf("IMPOSSIBLE\n");
			return 0;
		}
	}
	printf("POSSIBLE\n");
	int num=edgenum;
	edgenum=0;
	memset(head,0,sizeof(head));
	memset(low,0,sizeof(low));
	for(int i=1;i<=edgenum;i++)
	{
		int u=e[i].from,v=e[i].to;
		if(p[u]!=p[v])
		{
			add(p[u],p[v]);
			low[p[v]]++;
		}
	}
	ind=0;
	for(int i=1;i<=scc;i++)
	  if(low[i]==0)
		q.push(i);
	while(!q.empty())
	{
		int node=q.front();
		q.pop();
		dfn[node]=++ind;
		for(int hd=head[node];hd;hd=e[hd].next)
		{
			int to=e[hd].to;
			low[to]--;
			if(low[to]==0)q.push(to);
		}
	}
	for(int i=1;i<=n;i++)
	{
		if(dfn[p[i]]>dfn[p[i+n]])putchar('1');
		else putchar('0');
		putchar(' ');
	}
	putchar('\n');
	return 0;
}

套路 1:

建立辅助变量优化建图

会结合一些数据结构的常见手段(线段树 ST表 前缀 后缀)

比如一些变量至多只有一个可以为1的建图方法(不会

套路2:

对于变量取值,拆除一堆$$x\leqslant1,x\leqslant2,x\leqslant3,……, $$这样的变量记作$$p_1,p_2,p_3,……$$

然后连$$p_2 \to p_1,p_1^, \to p_2^,,…… $$这样保证前面的取的是1,后面的取的是0

然后再根据不等式之类的限制连边。

套路3:

额外加一个限制,判断可不可行之类的。

相当于在这个图中加了两条边,判断$$x与x^,$$可不可达,转化到可达性问题。

Dominator Tree(支配树)

会吗并不会,考吗并不考。

支配树(dominator tree) 学习笔记

支配树详解

//luogu-P5180
# include <iostream>
# include <cstdio>
# include <cstring>
# include <queue>
# define sz 20
# define FOR(i,st,ed) for(int i=st;i<=ed;++i)
# define _FOR(tu,u) for(int v,i=tu.hd[u];i;i=tu.e[i].nt)
# define _(tu) v=tu.e[i].to;
using namespace std;
int re(){
    int s=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){s=(s<<1)+(s<<3)+ch-'0';ch=getchar();}
    return s*f;
}//快读就不解释了

const int N=2e5+7;
struct MAP{
    struct edge{
        int to,nt;
    }e[N<<1];int cnt,hd[N];
    void link(int x,int y){e[++cnt]=(edge){y,hd[x]};hd[x]=cnt;}
}xx,a,ra,t,rt,z;//排除xx,依次表示原图,原图的反图,dfs树
//dfs树的反图,和最后的支配树
int n,m;

int dfn[N],dji,id[N],ff[N];//ff表示dfs树上的父节点
void dfs(int u){
    id[dfn[u]=++dji]=u;
    _FOR(a,u){
        _(a);
        if(!dfn[v]) dfs(v),ff[v]=u,t.link(u,v); 
    }
}//扣出一个dfs树

int anc[N],semi[N],mn[N];//这个mn[v],表示v点的dfs树上的祖先
//的semi最小的那个祖先,所以semi[mn[x]]=semi[x];
void init(){
    FOR(i,1,n) anc[i]=semi[i]=mn[i]=i;
}
int find(int u){
    if(u!=anc[u]){
        int t=anc[u];anc[u]=find(anc[u]);
        if(dfn[semi[mn[u]]]>dfn[semi[mn[t]]]) mn[u]=mn[t];
    }
    return anc[u];
}//以上是带权并查集(路径压缩)
void len_tarjan(){
    init();
    for(int j=n;j>=2;--j){
        int u=id[j],res=j;
        if(!u) continue;
        _FOR(ra,u){
            _(ra);
            if(!dfn[v]) continue;
            if(dfn[v]<dfn[u]) res=min(res,dfn[v]);
            else find(v),res=min(res,dfn[semi[mn[v]]]);
        }
        semi[u]=id[res];anc[u]=ff[u];t.link(semi[u],u); 
    }
}//我们的tarjan大佬的算法

int ru[N],dep[N],fa[N][25];
int lca(int x,int y){
    if(dep[x]<dep[y]) swap(x,y);
    int del=dep[x]-dep[y];
    FOR(i,0,sz)
        if((1<<i)&del) x=fa[x][i];
    if(x==y) return x;
    for(int i=sz;i>=0;--i)
        if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}/lca
void work(int u){
    int t=rt.e[rt.hd[u]].to;
    _FOR(rt,u){
        _(rt);t=lca(t,v);
    }
    dep[u]=dep[t]+1;
    fa[u][0]=t;
    z.link(t,u);
    FOR(i,1,sz)
        fa[u][i]=fa[fa[u][i-1]][i-1]; 
}//建立支配树
void tpsort(){
    FOR(j,1,n)
        _FOR(t,j){
            _(t);
            ru[v]++;rt.link(v,j); 
        }   
    FOR(i,1,n) if(!ru[i]) t.link(0,i),rt.link(i,0);
    queue <int> q;q.push(0);
    while(q.size()){
        int u=q.front();q.pop();
        _FOR(t,u){
            _(t);
            if((--ru[v])<=0) q.push(v),work(v);
        }
    } 
}

int siz[N];
void adfs(int u){
    siz[u]=1;
    _FOR(z,u){
        _(z);adfs(v);siz[u]+=siz[v];
    }
}//最后adfs用来搜答案
int main(){
    n=re();m=re();int x,y;
    FOR(i,1,m){
        x=re();y=re();
        a.link(x,y);ra.link(y,x);  
    }
    dfs(1);
    len_tarjan();
    tpsort();
    adfs(0);
    FOR(i,1,n) cout<<siz[i]<<' ';
    return 0;
}

最短路

套路:

割点/边问最短路:

删边最短路一般来说都是考虑最短路径树,然后考虑每条非树边当成替换边之类的。

删点可以使用分治解决。

($$dijkstra$$)边权小的话可以把堆换成桶解决。

变种:

比如边权,距离,价格有关之类的,可以用$$dijkstr$$每次确定最小值解决。

还有一些题的某些参数不知道,告诉你某些点之间的最短路的范围,然后求边权或者之类的

有些题目是可以通过一些精妙的分析解决。(一般可以写成线性规划,之间使用$$simplex$$ $$overkill$$一下就行了)

//dijkstra
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
struct Edge
{
	int to;
	int nxt;
	int len;
}e[200005];
int n,m,s,edgenum,head[100005],dis[100005];
priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
void add(int u,int v,int l)
{
	e[++edgenum].len=l;
	e[edgenum].to=v;
	e[edgenum].nxt=head[u];
	head[u]=edgenum;
}
void Dijkstra()
{
	memset(dis,0x7f,sizeof(dis));
	dis[s]=0;
	q.push(make_pair(0,s));
	while(!q.empty())
	{
		int d=q.top().first;
		int node=q.top().second;
		q.pop();
		if(dis[node]!=d)continue;
		for(int hd=head[node];hd;hd=e[hd].nxt)
		{
			int to=e[hd].to;
			if(dis[to]>dis[node]+e[hd].len)
			{
				dis[to]=dis[node]+e[hd].len;
				q.push(make_pair(dis[to],to));
			}
		}
	}
}
int main()
{
	scanf("%d%d%d",&n,&m,&s);
	for(int i=1;i<=m;i++)
	{
		int u,v,l;
		scanf("%d%d%d",&u,&v,&l);
		add(u,v,l);
	}
	Dijkstra();
	for(int i=1;i<=n;i++)
	  printf("%d ",dis[i]);
	printf("\n");
	return 0;
}

//SPFA
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
struct Edge
{
	int to;
	int len;
	int next;
}e[100005];
int n,m,edgenum,head[10005],dis[10005],num[10005];
bool flag[10005];
queue<int>q;
void add(int u,int v,int l)
{
	e[++edgenum].len=l;
	e[edgenum].to=v;
	e[edgenum].next=head[u];
	head[u]=edgenum;
}
bool SPFA(int i)
{
	memset(dis,0x7f,sizeof(dis));
	dis[i]=0;
	q.push(i);
	flag[i]=1;
	while(!q.empty())
	{
		int node=q.front();
		q.pop();
		flag[node]=0;
		for(int hd=head[node];hd;hd=e[hd].next)
		{
			int to=e[hd].to;
			if(dis[to]>dis[node]+e[hd].len)
			{
				dis[to]=dis[node]+e[hd].len;
				if(!flag[to])
				{
					q.push(to);
					flag[to]=1;
					num[to]++;
					if(num[to]>m)return 0;
				}
			}
		}
	}
	return 1;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int u,v,l;
		scanf("%d%d%d",&u,&v,&l);
		add(u,v,l);
	}
	if(!SPFA(1))
	{
		printf("No solution\n");
		return 0;
	}
	for(int i=1;i<=n;i++)
	  printf("%d ",dis[i]);
	printf("\n");
	return 0;
}

K短路

求k优解,或者之间问k短路。

做法:A*或者可并堆(反正都不会)。

//A*
#include <bits/stdc++.h>
using namespace std;
typedef double db;
db dis[5005],f[5005];
int t[5005],vis[5005];
struct dat
{
    int u;
    db w;
    bool operator < (const dat &rhs) const {
        return w>rhs.w;
    }
};
vector<dat> G[5005],g[5005];
void addedge(int i,int j,db w)
{
    G[i].push_back((dat){j,w});
    g[j].push_back((dat){i,w});
}
signed main()
{
    int n,m,ans=0;
    db e;
    cin>>n>>m>>e;
    for (int i=1;i<=m;i++)
    {
        int u,v;
        db w;
        cin>>u>>v>>w;
        addedge(u,v,w);
    }
    memset(f,127,sizeof(f));
    f[n]=0;
    priority_queue<dat> q,Q;
    Q.push((dat){n,0});
    while (!Q.empty())
    {
        dat p=Q.top();Q.pop();
        int u=p.u;
        if (vis[u]) continue;
        vis[u]=1;
        for (int i=0;i<g[u].size();i++)
        {
            dat v=g[u][i];
            if (f[u]+v.w<f[v.u])
            {
                Q.push((dat){v.u,f[u]+v.w});
                f[v.u]=f[u]+v.w;
            }
        }
    }
    q.push((dat){1,f[1]});
    while (!q.empty())
    {
        dat p=q.top();q.pop();
        int u=p.u;
        db w=p.w-f[u];
        if (u==n)
        {
            e-=w;
            if (e>=1e-6) ans++;
            else break;
            continue;
        }
        for (int i=0;i<G[u].size();i++)
        {
            dat v=G[u][i];
            q.push((dat){v.u,w+v.w+f[v.u]});
        }
    }
    cout<<ans<<endl;
    return 0;
}
//可并堆
# include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template <class Num> inline void Cmax(Num &x, const Num y) {
    x = y > x ? y : x;
}
template <class Num> inline void Cmin(Num &x, const Num y) {
    x = y < x ? y : x;
}
const int maxn(5005);
const int maxm(2e5 + 5);
const double eps(1e-8);
int n, m, first[maxn], cnt, vis[maxn], rt[maxn], tot, cov[maxm << 1], ans, fa[maxn];
double se, e, dis[maxn];
priority_queue < pair <double, int> > q;
struct Heap {
    int ls, rs, dis, ed;
    double w;
} tr[maxm * 20];
struct Edge {
    int to, next;
    double w;
} edge[maxm << 1];
inline void Add(int u, int v, double w) {
    edge[cnt] = (Edge){v, first[u], w}, first[u] = cnt++;
    edge[cnt] = (Edge){u, first[v], w}, first[v] = cnt++;
}
inline int NewNode(double w, int ed) {
    int x = ++tot;
    tr[x].w = w, tr[x].dis = 1, tr[x].ed = ed;
    return x;
}
int Merge(int x, int y) {
    if (!x || !y) return x + y;
    if (tr[x].w - tr[y].w >= eps) swap(x, y);
    int p = ++tot;
    tr[p] = tr[x], tr[p].rs = Merge(tr[p].rs, y);
    if (tr[tr[p].ls].dis < tr[tr[p].rs].dis) swap(tr[p].ls, tr[p].rs);
    tr[p].dis = tr[tr[x].rs].dis + 1;
    return p;
}
void Dfs(int u) {
    vis[u] = 1;
    for (int e = first[u], v; e != -1; e = edge[e].next)
        if (e & 1) {
            double w = edge[e].w;
            if (fabs(dis[u] + w - dis[v = edge[e].to]) < eps && !vis[v])
                fa[v] = u, cov[e ^ 1] = 1, Dfs(v);
        }
}
int main() {
    memset(first, -1, sizeof(first));
    memset(dis, 127, sizeof(dis));
    scanf("%d%d%lf", &n, &m, &se);
    for (int i = 1, u, v; i <= m; ++i) scanf("%d%d%lf", &u, &v, &e), Add(u, v, e);
    dis[n] = 0, q.push(make_pair(0, n));
    while (!q.empty()) {
        int u = q.top().second;
        q.pop();
        if (vis[u]) continue;
        vis[u] = 1;
        for (int e = first[u]; ~e; e = edge[e].next)
            if (e & 1) {
                int v = edge[e].to;
                if (dis[v] - (dis[u] + edge[e].w) >= eps)
                    q.push(make_pair(-(dis[v] = dis[u] + edge[e].w), v));
            }
    }
    for (int i = 1; i <= n; ++i) vis[i] = 0;
    Dfs(n);
    for (int e = 0, u, v; e < cnt; e += 2)
        if (!cov[e]) {
            u = edge[e ^ 1].to, v = edge[e].to;
            if (dis[u] == dis[0] || dis[v] == dis[0]) continue;
            rt[u] = Merge(rt[u], NewNode(dis[v] + edge[e].w - dis[u], v));
        }
    for (int i = 1; i <= n; ++i) q.push(make_pair(-dis[i], i));
    for (int i = 1, u; i <= n; ++i) {
        u = q.top().second, q.pop();
        if (fa[u]) rt[u] = Merge(rt[u], rt[fa[u]]);
    }
    if (dis[1] - se < eps) se -= dis[1], ++ans;
    if (rt[1]) q.push(make_pair(-tr[rt[1]].w, rt[1]));
    while (!q.empty()) {
        int ed = q.top().second;
        double cur = q.top().first, w = dis[1] - cur;
        if (w - se >= eps) break;
        q.pop(), se -= w, ++ans;
        for (int i = 0; i < 2; ++i) {
            int nxt = i ? tr[ed].rs : tr[ed].ls;
            if (nxt) q.push(make_pair(cur + tr[ed].w - tr[nxt].w, nxt));
        }
        if (rt[tr[ed].ed]) q.push(make_pair(cur - tr[rt[tr[ed].ed]].w, rt[tr[ed].ed]));
    }
    printf("%d\n", ans);
    return 0;
}

差分约束

对于$$s(v)\leqslant s(u)+w(u,v)$$的限制建图,然后求解。

一般就是根据题意把变量和限制写出来,如果每个限制都只和某两项的差有关,可以使用差分约束解决。

考虑前缀和也是常见手段。

//luogu-p1993
#include<bits/stdc++.h>
#define il inline
#define For(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
using namespace std;
const int N=50005,inf=23333333;
int n,m,to[N],net[N],w[N],dis[N],cnt,h[N],tot[N];
bool vis[N];

il int gi(){
    int a=0;char x=getchar();
    while(x<'0'||x>'9')x=getchar();
    while(x>='0'&&x<='9')a=(a<<3)+(a<<1)+x-48,x=getchar();
    return a;
}

il void add(int u,int v,int c){to[++cnt]=v,net[cnt]=h[u],h[u]=cnt,w[cnt]=c;}

il bool spfa(int u){
    vis[u]=1;
    for(int i=h[u];i;i=net[i])
        if(dis[to[i]]<dis[u]+w[i]){
            dis[to[i]]=dis[u]+w[i];
            if(vis[to[i]])return 0;
            if(!spfa(to[i]))return 0;
        }
    vis[u]=0;
    return 1;
}

int main(){
    n=gi(),m=gi();
    int f,a,b,c;
    while(m--){
        f=gi(),a=gi(),b=gi();
        if(f==1)c=gi(),add(b,a,c);
        else if(f==2)c=gi(),add(a,b,-c);
        else if(f==3)add(a,b,0),add(b,a,0);
    }
    For(i,1,n)add(0,i,0),dis[i]=-inf;
    if(!spfa(0))cout<<"No";
    else cout<<"Yes";
    return 0;
}

网络流

最好能掌握多种费用流算法($$SPFA$$增广,$$dkw$$费用流),以及掌握有负环/上下界的费用流。最大流($$dinic$$);

//spfa增广
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn=705,maxe=144005,INF=0x3f3f3f3f;
int N,M,S,T,n,tim[65][15],id[65][15],ans=0;
int tot=1,son[maxe],nxt[maxe],lnk[maxn],flw[maxe],cap[maxe],w[maxe];
void add(int x,int y,int z){
    son[++tot]=y;nxt[tot]=lnk[x];lnk[x]=tot;flw[tot]=0;cap[tot]=1;w[tot]=z;
    son[++tot]=x;nxt[tot]=lnk[y];lnk[y]=tot;flw[tot]=0;cap[tot]=0;w[tot]=-z;
}
#define nc getchar
inline int red(){
    int tot=0,f=1;char ch=nc();
    while (ch<'0'||'9'<ch) {if (ch=='-') f=-f;ch=nc();}
    while ('0'<=ch&&ch<='9') tot=tot*10+ch-48,ch=nc();
    return tot*f;
}
int que[maxn],dst[maxn],fa[maxn],ed[maxn];
bool vis[maxn];
bool spfa(){
    memset(dst,63,sizeof(dst));
    memset(vis,0,sizeof(vis));
    int hed=0,til=1;
    que[1]=S;dst[S]=0;fa[S]=0;
    while (hed!=til){
        int x=que[hed=(hed+1)%maxn];
        vis[x]=0;
        for (int j=lnk[x];j;j=nxt[j])
         if (cap[j]>flw[j]&&dst[son[j]]>dst[x]+w[j]){
            dst[son[j]]=dst[x]+w[j];
            fa[son[j]]=x;ed[son[j]]=j;
            if (!vis[son[j]])
             vis[son[j]]=1,
             que[til=(til+1)%maxn]=son[j];
         }
    }
    if (dst[T]==INF) return 0;
    return 1;
}
int main(){
    //do something...
    while (spfa()){
        int Min=INF;
        for (int j=T;j!=S;j=fa[j]) Min=min(Min,cap[ed[j]]-flw[ed[j]]);
        for (int j=T;j!=S;j=fa[j]) flw[ed[j]]+=Min,flw[ed[j]^1]-=Min;
        ans+=dst[T]*Min;
    }
    printf("%.2lf",(double)ans/N);
    return 0;
}
//dinic
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int INF=2147483647;
struct Edge
{
	int from;
	int to;
	int next;
	int flow;
}e[200005];
int n,m,s,t,edgenum=1,dep[50005],head[50005];
queue<int>q;
void add(int u,int v,int f)
{
	e[++edgenum].flow=f;
	e[edgenum].from=u;
	e[edgenum].to=v;
	e[edgenum].next=head[u];
	head[u]=edgenum;
}
bool bfs()
{
	while(!q.empty())q.pop();
	memset(dep,0,sizeof(dep));
	dep[s]=1;
	q.push(s);
	while(!q.empty())
	{
		int node=q.front();
		q.pop();
		for(int hd=head[node];hd;hd=e[hd].next)
		{
			int to=e[hd].to;
			if(e[hd].flow&&dep[to]==0)
			{
				dep[to]=dep[node]+1;
				q.push(to);
			}
		}
	}
	return dep[t];
}
int dfs(int node,int nowf)
{
	if(node==t)return nowf;
	for(int hd=head[node];hd;hd=e[hd].next)
	{
		int to=e[hd].to;
		if(dep[to]==dep[node]+1&&e[hd].flow)
		{
			int d=dfs(to,min(nowf,e[hd].flow));
			if(d>0)
			{
				e[hd].flow-=d;
				e[hd^1].flow+=d;
				return d;
			}
		}
	}
	return 0;
}
int Dinic()
{
	int ans=0,d;
	while(bfs())
	{
		while(d=dfs(s,INF))
		  ans+=d;
	}
	return ans;
}
int main()
{
	scanf("%d%d%d%d",&n,&m,&s,&t);
	for(int i=1;i<=m;i++)
	{
		int u,v,f;
		scanf("%d%d%d",&u,&v,&f);
		add(u,v,f);
		add(v,u,0);
	}
	printf("%d\n",Dinic());
	return 0;
}

套路0:

有负环/上下界的最大/最小/费用/循环流(无源点和汇点)。

套路1:

建立线性规划的式子然后对偶成为费用流。($$ simplex $$不会比对偶费用流慢多少,之间背有初始解的模板)

套路2:

建立一堆辅助变量变成等式,然后通过一些高明的技巧使得每个东西一正一反出现在这些等式里,然后根据流量平衡见图。

一般来说这些$$LP-based$$的网络流题都是费用流。因为最大流的对偶是最小割。处理的方法比较套路。而费用流的运行速度和$$simplex$$也差不了多少。无脑$$simplex$$。

//simplex
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<cstdio>
#include<ctime>
#include<cmath>
#include<map>
#define Redge(u) for (int k = h[u],to; k; k = ed[k].nxt)
#define REP(i,n) for (int i = 1; i <= (n); i++)
#define mp(a,b) make_pair<int,int>(a,b)
#define cls(s) memset(s,0,sizeof(s))
#define cp pair<int,int>
#define LL long long int
using namespace std;
const int maxn = 25,maxm = 100005;
const double eps = 1e-8,INF = 1e15;
inline int read(){
    int out = 0,flag = 1; char c = getchar();
    while (c < 48 || c > 57){if (c == '-') flag = -1; c = getchar();}
    while (c >= 48 && c <= 57){out = (out << 3) + (out << 1) + c - 48; c = getchar();}
    return out * flag;
}
int n,m,id[maxn << 1];
double a[maxn][maxn],ans[maxn];
void Pivot(int l,int e){
    swap(id[n + l],id[e]);
    double t = a[l][e]; a[l][e] = 1;
    for (int j = 0; j <= n; j++) a[l][j] /= t;
    for (int i = 0; i <= m; i++) if (i != l && abs(a[i][e]) > eps){
        t = a[i][e]; a[i][e] = 0;
        for (int j = 0; j <= n; j++) a[i][j] -= a[l][j] * t;
    }
}
bool init(){
    while (true){
        int e = 0,l = 0;
        for (int i = 1; i <= m; i++) if (a[i][0] < -eps && (!l|| (rand() & 1))) l = i;
        if (!l) break;
        for (int j = 1; j <= n; j++) if (a[l][j] < -eps && (!e || (rand() & 1))) e = j;
        if (!e){puts("Infeasible"); return false;}
        Pivot(l,e);
    }
    return true;
}
bool simplex(){
    while (true){
        int l = 0,e = 0; double mn = INF;
        for (int j = 1; j <= n; j++) if (a[0][j] > eps){e = j; break;}
        if (!e) break;
        for (int i = 1; i <= m; i++) if (a[i][e] > eps && a[i][0] / a[i][e] < mn)
            mn = a[i][0] / a[i][e],l = i;
        if (!l){puts("Unbounded"); return false;}
        Pivot(l,e);
    }
    return true;
}
int main(){
    srand(time(NULL));
    n = read(); m = read(); int t = read();
    REP(i,n) a[0][i] = read();
    REP(i,m){
        REP(j,n) a[i][j] = read();
        a[i][0] = read();
    }
    REP(i,n) id[i] = i;
    if (init() && simplex()){
        printf("%.8lf\n",-a[0][0]);
        if (t){
            REP(i,m) ans[id[n + i]] = a[i][0];
            REP(i,n) printf("%.8lf ",ans[i]);
        }
    }
    return 0;
}

最小生成树

掌握$$Prim/Kruskal/Boruvka$$算法。

//Prim
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<stack>
#include<map>
#include<sstream>
using namespace std;
typedef long long ll;
const int maxn = 2e3 + 10;
const int INF = 1 << 30;
int dir[4][2] = {1,0,0,1,-1,0,0,-1};
int T, n, m, x;
int Map[maxn][maxn];//存图
int lowcost[maxn], mst[maxn];
void prim(int u)//最小生成树起点
{
    int sum_mst = 0;//最小生成树权值
    for(int i = 1; i <= n; i++)//初始化两个数组
    {
        lowcost[i] = Map[u][i];
        mst[i] = u;
    }
    mst[u] = -1;//设置成-1表示已经加入mst
    for(int i = 1; i < n; i++)//此处只需要迭代n-1次即可
    {
        int minn = INF;
        int v = -1;
        //在lowcost数组中寻找未加入mst的最小值
        for(int j = 1; j <= n; j++)
        {
            if(mst[j] != -1 && lowcost[j] < minn)
            {
                v = j;
                minn = lowcost[j];
            }
        }
        if(v != -1)//v=-1表示未找到最小的边,
        {//v表示当前距离mst最短的点
            printf("%d %d %d\n", mst[v], v, lowcost[v]);//输出路径
            mst[v] = -1;
            sum_mst += lowcost[v];
            for(int j = 1; j <= n; j++)//更新最短边
            {
                if(mst[j] != -1 && lowcost[j] > Map[v][j])
                {
                    lowcost[j] = Map[v][j];
                    mst[j] = v;
                }
            }
        }
    }
    printf("weight of mst is %d\n", sum_mst);
}
int main()
{
    cin >> n >> m;
    memset(Map, 0, sizeof(Map));
    for(int i = 1; i <= m; i++)
    {
        int u, v, w;
        cin >> u >> v >> w;
        Map[u][v] = Map[v][u] = w;
    }
    for(int i = 1; i <= n; i++)
    {
        for(int j = 1; j <= n; j++)
        {
            if(i == j)Map[i][j] = 0;
            else if(!Map[i][j])Map[i][j] = INF;
        }
    }
    prim(1);
    return 0;
}
//kruskal
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int n,m,tot=0,k=0;//n端点总数,m边数,tot记录最终答案,k已经连接了多少边 
int fat[200010];//记录集体老大 
struct node
{
	int from,to,dis;//结构体储存边 
}edge[200010];
bool cmp(const node &a,const node &b)//sort排序(当然你也可以快排) 
{
	return a.dis<b.dis;
}
int father(int x)//找集体老大,并查集的一部分 
{
	if(fat[x]!=x)
	return father(fat[x]);
	else return x;
}
void unionn(int x,int y)//加入团体,并查集的一部分 
{
	fat[father(y)]=father(x);
}
int main()
{
	scanf("%d%d",&n,&m);//输入点数,边数 
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&edge[i].from,&edge[i].to,&edge[i].dis);//输入边的信息 
	}
	for(int i=1;i<=n;i++) fat[i]=i;//自己最开始就是自己的老大 (初始化) 
	sort(edge+1,edge+1+m,cmp);//按权值排序(kruskal的体现) 
	for(int i=1;i<=m;i++)//从小到大遍历 
	{
		if(k==n-1) break;//n个点需要n-1条边连接 
		if(father(edge[i].from)!=father(edge[i].to))//假如不在一个团体 
		{
			unionn(edge[i].from,edge[i].to);//加入 
			tot+=edge[i].dis;//记录边权 
			k++;//已连接边数+1 
		}
	}
	printf("%d",tot);
	return 0;
}
//broucvka解曼哈顿距离最大生成树
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<set>
#include<vector>
using namespace std;
typedef long long ll;
typedef pair<ll,int> abcd;
inline char nc(){
  static char buf[100000],*p1=buf,*p2=buf;
  return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline void read(int &x){
  char c=nc(),b=1;
  for (;!(c>='0' && c<='9');c=nc()) if (c=='-') b=-1;
  for (x=0;c>='0' && c<='9';x=x*10+c-'0',c=nc()); x*=b;
}
const int N=100005;
const int dx[]={1,-1,1,-1};
const int dy[]={-1,-1,1,1};
set<abcd> Set[4];
int n; ll Ans=0;
int x[N],y[N];
ll a[N][4];
int fat[N];
#define pb push_back
vector<int> g[N];
inline int Fat(int u){
  return fat[u]==u?u:fat[u]=Fat(fat[u]);
}
inline bool Merge(int x,int y){
  x=Fat(x); y=Fat(y); if (x==y) return 0;
  for (int i=0;i<(int)g[x].size();i++) g[y].pb(g[x][i]);
  g[x].clear();
  fat[x]=y; return 1; 
}
struct edge{
  int u,v,w;
  edge(int u=0,int v=0,int w=0):u(u),v(v),w(w) { }
}ed[N];
int pnt=0;
int main(){
  freopen("mst.in","r",stdin);
  freopen("mst.out","w",stdout);
  read(n);
  for (int i=1;i<=n;i++){
    read(x[i]); read(y[i]);
    for (int j=0;j<4;j++){
      a[i][j]=x[i]*dx[j]+y[i]*dy[j];
      Set[j].insert(abcd(a[i][j],i));
    }
  }
  for (int i=1;i<=n;i++) fat[i]=i,g[i].pb(i);
  Ans=0;
  while (1){
    int flag=0; pnt=0;
    for (int i=1;i<=n;i++){
      if (fat[i]!=i) continue;
      if (g[i].size()==n){
    flag=1; break;
      }
      for (int j=0;j<(int)g[i].size();j++)
    for (int k=0;k<4;k++)
      Set[k].erase(abcd(a[g[i][j]][k],g[i][j]));
      ll Max=-1LL<<40,v;
      for (int k=0;k<4;k++){
    ll maxu=-1LL<<40,maxv;
    for (int j=0;j<(int)g[i].size();j++)
      if (a[g[i][j]][k]>=maxu)
        maxu=a[g[i][j]][k];
    set<abcd>::iterator it=Set[k^3].end(); it--;
    maxv=it->first;
    if (maxu+maxv>=Max)
      Max=maxu+maxv,v=it->second;
      }
      ed[++pnt]=edge(i,v,Max);
      for (int j=0;j<(int)g[i].size();j++)
    for (int k=0;k<4;k++)
      Set[k].insert(abcd(a[g[i][j]][k],g[i][j]));
    }
    if (flag) break;
    for (int j=1;j<=pnt;j++)
      if (Merge(ed[j].u,ed[j].v))
    Ans+=ed[j].w;
  }
  printf("%lld\n",Ans);
  return 0;
}

套路1:

两点之间路径的边/点权的最大值最小(最小值最大)。

套路2:

动态维护最小生成树(加边/删边)

动态图中可以把每条边的删除时间当成边权维护最大生成树。

//魔法森林
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
const int MAXN = 50010;
const int MAXM = 100010;
const int MAXS = MAXN + MAXM;
const int INF = 1061109567;
inline int Max(const int a, const int b){ return (a > b) ? a : b; }
inline int Min(const int a, const int b){ return (a < b) ? a : b; }
inline int read(){
    int x = 0; int w = 1; register char c = getchar();
    for(; c ^ '-' && (c < '0' || c > '9'); c = getchar());
    if(c == '-') w = -1, c = getchar();
    for(; c >= '0' && c <= '9'; c = getchar()) x = (x<<3) + (x<<1) + c - '0'; return x * w;
}
struct Edge{
    int x,y,a,b;
}e[MAXM];
int N,M,ans(INF),maxa;
int val[MAXS],mx[MAXS];
struct LinkCutTree{
    int ch[MAXS][2],fa[MAXS];
    bool tag[MAXS];
    inline bool rson(int f, int x){
        return ch[f][1] == x;
    }
    inline bool isroot(int x){
        return ch[fa[x]][0]!=x && ch[fa[x]][1]!=x;
    }
    inline void pushup(int x){
        mx[x] = x;
        if(val[mx[ch[x][0]]] > val[mx[x]]) mx[x] = mx[ch[x][0]];
        if(val[mx[ch[x][1]]] > val[mx[x]]) mx[x] = mx[ch[x][1]];
    }
    inline void rotate(int x){
        int f = fa[x], gf = fa[f];
        bool p = rson(f,x), q = !p;
        if(!isroot(f)) ch[gf][rson(gf,f)] = x; fa[x] = gf;
        ch[f][p] = ch[x][q], fa[ch[x][q]] = f;
        ch[x][q] = f, fa[f] = x;
        pushup(f), pushup(x);
    }
    void reverse(int x){
        if(!isroot(x)) reverse(fa[x]);
        if(!tag[x]) return;
        tag[x] = 0;
        swap(ch[x][0], ch[x][1]);
        tag[ch[x][0]] ^= 1, tag[ch[x][1]] ^= 1;
    }
    inline void splay(int x){
        for(reverse(x); !isroot(x); rotate(x)){
            if(isroot(fa[x])){ rotate(x); break; }
            if(rson(fa[fa[x]],fa[x]) ^ rson(fa[x],x)) rotate(x); else rotate(fa[x]);
        }
    }
    inline void access(int x){
        for(int y = 0; x; y=x, x = fa[x]) splay(x), ch[x][1] = y, pushup(x);
    }
    inline void mroot(int x){
        access(x), splay(x), tag[x] ^= 1;
    }
    inline int findroot(int x){
        access(x), splay(x);
        while(ch[x][0]) x = ch[x][0];
        return x;
    }
    inline void split(int x, int y){
        mroot(x);
        access(y);
        splay(y);
    }
    inline void link(int x, int y){
        mroot(x);
        fa[x] = y;
    }
    inline void cut(int x, int y){
        split(x,y);
        if(ch[y][0] != x || ch[x][1]) return;
        ch[y][0] = fa[x] = 0;
    }
}qxz;
inline bool cmp(const Edge& a, const Edge& b){
    return a.a < b.a;
}
int main(){
//  freopen(".in","r",stdin);
    N = read(), M = read();
    for(int i = 1; i <= M; ++i){
        e[i].x = read(), e[i].y = read(), e[i].a = read(), e[i].b = read();
    }
    sort(e+1, e+M+1, cmp);
    for(int i = 1; i <= M; ++i){
        val[N+i] = e[i].b;
        if(qxz.findroot(e[i].x) != qxz.findroot(e[i].y)){
            qxz.link(e[i].x, N+i);
            qxz.link(e[i].y, N+i);
            maxa = e[i].a;
        }
        else{
            qxz.split(e[i].x, e[i].y);
            if(val[mx[e[i].y]] > e[i].b){
                int temp = mx[e[i].y]-N;
                qxz.cut(e[temp].x, temp+N);
                qxz.cut(e[temp].y, temp+N);
                qxz.link(e[i].x, N+i);
                qxz.link(e[i].y, N+i);
                maxa = e[i].a;
            }
        }
        if(qxz.findroot(1) == qxz.findroot(N)){
            qxz.split(1, N);
            ans = Min(ans, maxa + val[mx[N]]);
        }
    }
    if(ans == INF) printf("-1");
    else printf("%d", ans);
    return 0;
}

套路3:

完全图,每条边的边权为$$w(u)$$ $$op$$ $$w(v)$$。

一般来说可以用$$prim或者Boruvka$$。

最小树形图

掌握朱刘算法及输出方案。

//hdu 2121 Ice_cream’s world II
#include<iostream>
using namespace std;
#include<cstdio>
#include<cstring>
#define MAXN 1005
#define INF 0x7f7f7f7f
typedef __int64 type;
struct node//边的权和顶点
{
    int u, v;
    type w;
}edge[MAXN * MAXN];
int pre[MAXN], id[MAXN], vis[MAXN], n, m, pos;
type in[MAXN];//存最小入边权,pre[v]为该边的起点
type Directed_MST(int root, int V, int E)
{
    type ret = 0;//存最小树形图总权值
    while(true)
    {
        int i;
        //1.找每个节点的最小入边
        for( i = 0; i < V; i++)
            in[i] = INF;//初始化为无穷大
        for( i = 0; i < E; i++)//遍历每条边
        {
            int u = edge[i].u;
            int v = edge[i].v;
            if(edge[i].w < in[v] && u != v)//说明顶点v有条权值较小的入边  记录之
            {
                pre[v] = u;//节点u指向v
                in[v] = edge[i].w;//最小入边
                if(u == root)//这个点就是实际的起点
                    pos = i;
            }
        }
        for( i = 0; i < V; i++)//判断是否存在最小树形图
        {
            if(i == root)
                continue;
            if(in[i] == INF)
                return -1;//除了根以外有点没有入边,则根无法到达它  说明它是独立的点 一定不能构成树形图
        }
        //2.找环
        int cnt = 0;//记录环数
        memset(id, -1, sizeof(id));
        memset(vis, -1, sizeof(vis));
        in[root] = 0;
        for( i = 0; i < V; i++) //标记每个环
        {
            ret += in[i];//记录权值
            int v = i;
            while(vis[v] != i && id[v] == -1 && v != root)
            {
                vis[v] = i;
                v = pre[v];
            }
            if(v != root && id[v] == -1)
            {
                for(int u = pre[v]; u != v; u = pre[u])
                    id[u] = cnt;//标记节点u为第几个环
                id[v] = cnt++;
            }
        }
        if(cnt == 0)
            break; //无环   则break
        for( i = 0; i < V; i++)
            if(id[i] == -1)
                id[i] = cnt++;
            //3.建立新图   缩点,重新标记
            for( i = 0; i < E; i++)
            {
                int u = edge[i].u;
                int v = edge[i].v;
                edge[i].u = id[u];
                edge[i].v = id[v];
                if(id[u] != id[v])
                    edge[i].w -= in[v];
            }
            V = cnt;
            root = id[root];
    }
    return ret;
}
int main()
{
    int i;
    while(scanf("%d%d", &n, &m) != EOF)
    {
        type sum = 0;
        for( i = 0; i < m; i++)
        {
            scanf("%d%d%I64d", &edge[i].u, &edge[i].v, &edge[i].w);
            edge[i].u++; edge[i].v++;
            sum += edge[i].w;
        }
      sum ++;
        for( i = m; i < m + n; i++)//增加超级节点0,节点0到其余各个节点的边权相同(此题中 边权要大于原图的总边权值)
        {
            edge[i].u = 0;
            edge[i].v = i - m + 1;
            edge[i].w = sum;
        }
        type ans = Directed_MST(0, n + 1, m + n);
        //n+1为总结点数,m+n为总边数
        //ans代表以超级节点0为根的最小树形图的总权值,
        //将ans减去sum,如果差值小于sum,说明节点0的出度只有1,说明原图是连通图
        //如果差值>=sum,那么说明节点0的出度不止为1,说明原图不是连通图
        if(ans == -1 || ans - sum >= sum)
            puts("impossible");
        else
            printf("%I64d %d\n",ans - sum, pos - m);
        puts("");
    }
    return 0;
}

$$Steiner Tree$$

掌握$$O(3{kn}+2{kmlog(m)} )$$的方法。

浅析SteinerTree(斯坦纳树)

//POJ3123
#include <iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
    int INF = 99999999, N, K, d[30][30], i, j, k, x, y, z;
	int dp[256][30], e[8], v[30], c, b;
	string s, t;
	while (cin >> N >> K && N)
	{
		map <string, int> cityMap;
		for (i = 0; i < N; i++)
		for (j = 0; j < N; j++)
		d[i][j] = i == j ? 0 : INF;
		for (i = 0; i < N; i++)
		{
			cin >> s;
			cityMap[s] = i;
		}
		if (K)
		while (cin >> s >> t >> z, x = cityMap[s],
			y = cityMap[t],
			d[x][y] = d[y][x] = min(d[y][x], z), --K);
		for (k = 0; k < N; k++)
			for (i = 0; i < N; i++)
				for (j = 0; j < N; j++)
					d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
		for (i = 0; i < 8; i++)
		{
			cin >> s;
			e[i] = cityMap[s];
			for (j = 0; j < N; j++)
				dp[1 << i][j] = d[j][e[i]];
		}
		for (i = 1; i < 256; i++)
		{
			if (!(i & (i - 1)))
			continue;	
			// step1
			for (k = 0; k < N; k++)
			{
				dp[i][k] = INF;
				v[k] = 0;
				/*for (j = 1; j < i; j++)
				if ((i | j) == i)
				dp[i][k] = min(dp[i][k], dp[j][k] + dp[i-j][k]);*/
				for (int sub = i; sub; sub = (sub - 1) & i)
				{
					dp[i][k] = min(dp[i][k], dp[sub][k] + dp[i - sub][k]);
				}
			}
			// step2
			for (j = 0; b = INF, j < N; j++)
			{
				for (k = 0; k < N; k++)
					if (dp[i][k] <= b && !v[k])
						b = dp[i][c = k];
				for (k = 0, v[c] = 1; k < N; k++)
					dp[i][c] = min(dp[i][c], dp[i][k] + d[k][c]);
			}
		}
		// step3
		for (i = 0, b = INF; z = 0, i < 256; b = min(b, z), i++)
		for (j = 0; y = 0, j < 4; z += !!y * dp[y][x], j++)
		for (k = 0; k < 8; k += 2)
		if ((i >> k & 3) == j)
		y += 3 << k, x = e[k];
		cout << b << endl;
	}
	return 0;
}

二分图匹配

一般结果只与$$x,n-x,m-x,n+m-x $$有关。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cctype>
#define maxn 20000+200
using namespace std;
inline int read(){
	register int x=0,y=0;register char ch=getchar();
	for(;!isdigit(ch);ch=getchar())if(ch=='-')y=1;
	for(;isdigit(ch);ch=getchar())x=(x<<3)+(x<<1)+(ch^48);
	return y?-x:x; 
}
struct edge{
	int v,n;
}e[maxn];
int first[maxn],ask[maxn],matched[maxn];
int n,m,er,ans=0,num_edge=0;
inline void add(int u,int v){
	int k=++num_edge;
	e[k].n=first[u],first[u]=k,e[k].v=v;
} 
int found(int x){
	int ee,v;
	for(ee=first[x];v=e[ee].v,ee;ee=e[ee].n)
		if(!ask[v]){
			ask[v]=1;
			if(!matched[v]||found(matched[v]))
				matched[v]=x;return 1;
		}
	return 0;
}
inline void match(){
	int cnt=0;
	memset(matched,0,sizeof(matched));
	for(int i=1;i<=n;++i){
		memset(ask,0,sizeof(ask));
		if(found(i))++cnt; 
	}
	ans=cnt;
}
signed main(){
	n=read(),m=read(),er=read();
	int x,y;
	for(int i=1;i<=er;++i)
	{
		if(x>n||y>m)continue;
		x=read(),y=read(),
		add(x,y);
	}
	match();
	printf("%d\n",ans);
	return 0;
}

仙人掌

任意一条边至多只出现在一条简单回路的无向连通图称为仙人掌。

//luogu-P5236
#include<bits/stdc++.h>
#define N 10005
#define M 80005
using namespace std;
inline void rd(int &X){
    X=0;char ch=0;
    while(!isdigit(ch))ch=getchar();
    while( isdigit(ch))X=(X<<3)+(X<<1)+(ch^48),ch=getchar();
}
int n,m,q,cnt=1,tot,num;
int head[N],dis[N],v[N];
struct nd{int nxt,to,v;}e[M];
int d[N],Dis[N],vis[M],pre[N],sum[M],c[N],f[N][15];
#define For(x) for(int y,i=head[x];(y=e[i].to);i=e[i].nxt)
inline void add(int x,int y,int v){
    e[++cnt]={head[x],y,v};head[x]=cnt;
    e[++cnt]={head[y],x,v};head[y]=cnt;
}
void SPFA(){
    queue<int> q;q.push(1);
    memset(dis,0x3f,sizeof dis);dis[1]=dis[0]=0;
    while(q.size()){
        int x=q.front();q.pop();v[x]=0;
        For(x) if(dis[y]>dis[x]+e[i].v){
            dis[y]=dis[x]+e[i].v;
            if(!v[y]) v[y]=1,q.push(y);
        }
    }
}
void work(int x,int rt){
    if(x==rt) return ;
    add(rt,x,0);vis[pre[x]]=vis[pre[x]^1]=1;
    sum[num]+=e[pre[x]].v;c[x]=num;work(e[pre[x]^1].to,rt);
}
void dfs(int x){
    v[x]=++tot;
    For(x) if(i^pre[x]^1)
        if(!v[y]) pre[y]=i,Dis[y]=Dis[x]+e[i].v,dfs(y);
        else if(v[y]<v[x]) {
            sum[++num]=e[i].v;
            vis[i]=vis[i^1]=1; work(x,y);
        }
}
void dfs2(int x){
    for(int i=1;i<=14;i++)
        f[x][i]=f[f[x][i-1]][i-1];
    For(x) if(!vis[i] and y!=f[x][0])
        d[y]=d[x]+1,f[y][0]=x,dfs2(y);
}
int ask(int x=0,int y=0){
    rd(x);rd(y);
    if(d[x]<d[y]) swap(x,y);
    int X=x,Y=y;
    for(int i=14;~i;i--)
        if(d[f[x][i]]>=d[y])
            x=f[x][i];
    if(x==y) return dis[X]-dis[Y];
    for(int i=14;~i;i--)
        if(f[x][i]!=f[y][i])
            x=f[x][i],y=f[y][i];
    if(c[x] and c[x]==c[y]){
        int now=sum[c[x]],L=abs(Dis[x]-Dis[y]);
        return min(L,now-L)+dis[X]-dis[x]+dis[Y]-dis[y];
    }return dis[X]+dis[Y]-2*dis[f[x][0]];
}
signed main(){
    rd(n);rd(m);rd(q);
    for(int x,y,v,i=1;i<=m;i++)
        rd(x),rd(y),rd(v),add(x,y,v);
    SPFA();dfs(1);dfs2(1);
    while(q--) printf("%d\n",ask());
}

平面图

对于一个图G=< V,E >,如果能把G画在一个平面上,且画出的图的任意两条边除了V中的节点没有其他交点,则图G为平面图.

//HNOI2010 Planar平面图判定
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int MAXE=10010;
const int MAXV=300;
struct node
{
       int x,y;
}e2[MAXE];
int test,n,m,sum;
int h[MAXV],rank[MAXV],c[MAXV];
bool map[MAXV][MAXV],flag;
vector<int> e[MAXE],dag[MAXE];
vector<int>::iterator it;
bool Fright(int x1,int y1,int x2,int y2)
{
     if (x1==x2 || x1==y2 || y1==x2 || y1==y2) return 0;
     x1=rank[x1],y1=rank[y1];
     x2=rank[x2],y2=rank[y2];
     if (x1>y1) swap(x1,y1);
     return (x1<x2 && x2<y1)!=(x1<y2 && y2<y1);
}
void Colour(int v,int x)
{
 c[v]=x;
     int size=dag[v].size();
     for (int i=0;i<size;++i)
     {
 if (!c[dag[v][i]]) Colour(dag[v][i],-x); else
         if (c[dag[v][i]]==x) flag=0;
         if (!flag) return;
     }
}
int main()
{
    freopen("in.txt","r",stdin);
    freopen("out.txt","w",stdout);
    scanf("%d",&test);
    while (test--)
    {
          scanf("%d%d",&n,&m);
          flag=1;
          for (int i=1;i<=n;++i) e[i].clear();
          for (int i=1;i<=m;++i)
          {
              int x,y;
              scanf("%d%d",&x,&y);
              e[x].push_back(y);
          }
          for (int i=1;i<=n;++i)
              scanf("%d",&h[i]),rank[h[i]]=i;
          memset(map,0,sizeof(map));
          for (int i=2;i<=n;++i)
              map[h[i-1]][h[i]]=map[h[i]][h[i-1]]=1;
          map[h[n]][h[1]]=map[h[1]][h[n]]=1;
          if (m>n*3-6)
          {
                      printf("NO\n");
                      continue;
          }
          sum=0;
          for (int i=1;i<=n;++i)
              for (it=e[i].begin();it<e[i].end();++it)
                  if (!map[i][*it]) e2[++sum].x=i,e2[sum].y=*it;
 for (int i=1;i<=sum;++i) dag[i].clear();
          for (int i=1;i<=sum;++i)
              for (int j=i+1;j<=sum;++j)
                  if (Fright(e2[i].x,e2[i].y,e2[j].x,e2[j].y)) dag[i].push_back(j),dag[j].push_back(i);
 memset(c,0,sizeof(c));
          for (int i=1;i<=sum;++i)
              if (!c[i])
              {
                        Colour(i,1);
 if (!flag) break;
              }
          if (flag) printf("YES\n"); else printf("NO\n");
    }
    return 0;
}