【无标题】

打印代码:

二分图匹配:

bool find(int x){
	for(int i = h[x];~i;i=ne[i]){
		int y = e[i];
		if(!st[y]){
			st[y] = true;
			if(!match[y] || find(match[y]))return 1,match[y] = x;
		}
	}
	return false;
}

完美匹配:

typedef long long type;
const type inf = 1e18;
type w[N][N], slack[N], val1[N], val2[N];
int match[N], pre[N];
bool vis2[N];
int n, m;
void bfs(int u) {
	int x, y = 0, m_y = 0;
	memset(pre, 0, sizeof pre);
	for (int i = 1; i <= n; i++)slack[i] = inf;
	match[0] = u;
	do {
		type d = inf;
		x = match[y];
		vis2[y] = true;
		for (int i = 1; i <= n; i++) {
			if (vis2[i])continue;
			if (slack[i] > val1[x] + val2[i] - w[x][i])
				slack[i] = val1[x] + val2[i] - w[x][i], pre[i] = y;
			if (slack[i] < d)
				d = slack[i], m_y = i;
		}
		for (int i = 0; i <= n; i++) {
			if (vis2[i])
				val1[match[i]] -= d, val2[i] += d;
			else
				slack[i] -= d;
		}
		y = m_y;
	} while (match[y]);
	while (y) {
		match[y] = match[pre[y]];
		y = pre[y];
	}
}
type EK() {
	memset(match, 0, sizeof match);
	memset(val1, 0, sizeof val1);
	memset(val2, 0, sizeof val2);
	for (int i = 1; i <= n; i++) {
		memset(vis2, 0, sizeof vis2);
		bfs(i);
	}
	type ans = 0;
	for (int i = 1; i <= n; i++)
		if(match[i])ans += w[match[i]][i];
	return ans;
}

二分图的最小 顶 点 顶点 覆盖

最小覆盖:假如选了一个点就相当于覆盖了以它为端点的所有边,你需要选择最少的点来覆盖所有的边。
二 分 图 的 ∣ 最 小 点 集 ∣ = ∣ 最 大 匹 配 ∣ 二分图的|最小点集|=|最大匹配| =

二分图最小 权 点 权点 覆盖集

点覆盖集(这个点集中包含了所有边的至少一个端点,这个点集就覆盖了所有边)中,总权值和最小的一个就是所说的最小权点覆盖集。

当所有点权都为 1 1 1时, 最 大 匹 配 数 = 最 小 权 点 覆 盖 集 = 最 小 点 覆 盖 最大匹配数 = 最小权点覆盖集 = 最小点覆盖 ==
一般做法:我们将所有二分图的点看成两个集合 X X X Y Y Y,从 s s s向所有 X X X集合的点连一条容量为点权的边,从所有 Y Y Y集合的点向 t t t连一条容量为点权的边(只对于点权为正的点,因为网络流的容量必须是正的), X X X集合和 Y Y Y集合之间建原图存在的边,容量为正无穷。然后求 s s s t t t的最小割就是最小权值和。

二分图最小 路 径 路径 覆盖

通俗点将,就是在一个有向图中,找出最少的路径,使得这些路径经过了所有的点。
最 小 路 径 覆 盖 = 总 顶 点 数 − 最 大 匹 配 最小路径覆盖 =总顶点数 - 最大匹配 =

二分图的最大 独 立 集 独立集

就是在二分图中选尽量多的点,但得保证选出的点中任意两点之间没有边。
∣ 最 大 独 立 集 ∣ = ∣ V ∣ − ∣ 最 大 匹 配 数 ∣ |最大独立集| = |V|-|最大匹配数| =V

二分图最大 权 独 立 集 权独立集

所有独立集(独立集是指一个点集,点集中任意两点之间是不存在边的,也就是点和点之间相独立,并不相连)中,总点权最大的一个就是最大权独立集。
最 大 权 独 立 集 = 所 有 点 的 权 值 之 和 − 最 小 权 点 覆 盖 集 最大权独立集 = 所有点的权值之和 - 最小权点覆盖集 =

覆盖集,独立集是相反的概念

最大团

G ′ G′ G 是图 G G G 的子图,且 G ′ G′ G 是关于 G G G 的完全图时,子图 G ′ G' G 为图 G G G 的团.(任意两点之间都存在边。)

原 图 的 最 大 独 立 集 大 小 = 补 图 的 最 大 团 大 小 原图的最大独立集大小=补图的最大团大小 =
证明:原图的最大团必然两两有边,对应到补图上面就是两两无边,也就是独立集。最大团也就是最大独立集。证毕。


至少添加几个边使每个点能互相到达(m a x ( 入 度 为 0 的 点 , 出 度 为 0 的 点 ) max(入度为0的点,出度为0的点)max(入度为0的点,出度为0的点))
特殊情况 :给的图是强联通的答案为( 1 , 0 ) (1,0)(1,0);


强连通分量:

//求最大环.
void tarjan(int u){
	dfn[u] = low[u] = ++tim;
	stk[ ++ top] = u;
	st[u] = true;
	
	for(auto y : v[u]){
		if(!dfn[y])tarjan(y),low[u] = min(low[u],low[y]);
		else if(st[y])low[u] = min(low[u],dfn[y]);
	}

	if(dfn[u] == low[u]){
		int y;
		++cnt;
		do{
			y = stk[top--];
			st[y] = false;
			scc[y] = cnt;
		}while(u != y);
	}
}

割点

//割点的判断
void tarjan(int x){
	dfn[x] = low[x] = ++cnt;
	int son = 0;
	for(int i  = h[u];~i;i=ne[i]{
		int y = e[i];
		//这里的if else 用到上面(tarjan)low的更新条件(返祖边,搜素边)
		if(!dfn[y]){
			tarjan(y);
			son ++;//有多个儿子的根节点的判断。
			low[x] = min(low[x],low[y]);
			if(low[y] >= dfn[x]){//割点判断条件
				if(x != root || son > 1)cut[x] = 1;
			}
		}else low[x] = min(low[x],dfn[y]);
	}
}

割边

//对于求割边去掉等号,标记改一下

void tarjan(int u,int from){    
    dfn[u] = low[u] = ++tim;
    stk[++top] = u;
    for(int i = h[u];~i;i=ne[i]){
        int y = e[i];
        if(!dfn[y]){
            tarjan(y,i);
            low[u] = min(low[u],low[y]);
            //桥
            if(dfn[u] < low[y])
               is_bridge[i] = is_bridge[i^1] = true;
        }
        //i不是返回u的那条反向边
        else if(i != (from ^ 1)) low[u] = min(dfn[y],low[u]);
    }
}

点双

void Tarjan(int u) {
  low[u] = dfn[u] = ++tim;                // low 初始化为当前节点 dfn
  stk[++top] = u;                          // 加入栈中
  for (int i = h[u];~i;i=ne[i]) {                    // 遍历 u 的相邻节点
    int v = e[i];
    if (!dfn[v]) {                        // 如果未访问过
      Tarjan(v);                          // 递归
      low[u] = min(low[u], low[v]);  // 未访问的和 low 取 min
      if (low[v] == dfn[u]) {  // 标志着找到一个以 u 为根的点双连通分量
        ++cnt; 
        int y;
        do{
			y = stk[top--];
			dcc[cnt].push_back(y);        
		} while(y != v); 
        dcc[cnt].push_back(u);
      }
    } else
      low[u] = min(low[u], dfn[v]);  // 已访问的和 dfn 取 min
  }
}

边双

//from:到u的这条边
void tarjan(int u,int from){    
    dfn[u] = low[u] = ++tim;
    stk[++top] = u;
    for(int i = h[u];~i;i=ne[i]){
        int y = e[i];
        if(!dfn[y]){
            tarjan(y,i);
            low[u] = min(low[u],low[y]);
            //桥
            if(dfn[u] < low[y])
               is_bridge[i] = is_bridge[i^1] = true;
        }
        //i不是返回u的那条反向边
        else if(i != (from ^ 1)) low[u] = min(dfn[y],low[u]);
    }
    //边联通分量 缩点
    if(low[u] == dfn[u]){
        int y;
        cnt++;
        do{
            y = stk[top--];
            dcc[y] = cnt;
        }while(y!=u); 
    }
}

求树的k级祖先
例题
预处理复杂度: O(nlogn) 查询复杂度: 最坏O(logn)。

//[tops]:还能用重链剖分和长链剖分求。

//预处理
void dfs(int u,int fa){
	dep[u] = dep[fa] + 1;
	f[u][0] = fa;
	for(int i = 1;i<=20;i++)f[u][i] = f[f[u][i-1]][i-1];
	for(int i = h[u];~i;i=ne[i]){
		int  y = e[i];
		if(y == fa)continue;
		dfs(y,u);
	}
}

//x的k级祖先
int get(int x,int k){
	if(!k)return x;
	int index = log2(k);//取k的2的最高位 
	return get(f[x][index],k - (1<<index)); 
}

RMQ

void init(){
	for(int i = 1;i<=n;i++)f[i][0] = gcd[i][0] = a[i];
	for(int i = 1;(1<<i) <= n;i++)
		for(int j = 1;j + (1<<i) - 1 <= n ;j++)
			f[j][i] = min(f[j][i-1],f[j+(1<<(i-1))][i-1]),
			gcd[j][i] = __gcd(gcd[j][i-1],gcd[j+(1<<(i-1))][i-1]);
}
int _min(int l,int r){
	int k = (int)log2((double)(r-l+1));
	return min(f[l][k],f[r-(1<<k)+1][k]);
}

重构树

void krus(){
	int cnt = n;
	sort(edge+1,edge+1+m);
	for(int i = 1;i<=m;i++){
		int u = find(edge[i].u),v = find(edge[i].v);
		if(u != v){
			f[u] = f[v] = ++cnt;
			add(cnt,u);
			add(cnt,v);
			w[cnt] = edge[i].w;
		}
	}
}

最小割树
最小割树
求解将任意两点割开(分到两个集合)的最小权值.
参考链接

第一行两个数n,mn,m

接下来mm行,每行33个数u,v,wu,v,w,表示有一条连接uu与vv的无向边,割断它的代价为ww

接下来这一行有一个整数QQ,表示询问次数

接下来QQ行,每行两个数u,vu,v,你需要求出uu与vv之间的最小割

注意:因为数据有误,给定图的真实点数应该是n+1n+1个,编号为00到nn

核心代码(分治):

void slove(int L,int R){
	if(L == R)return ;
	int l = L ,r = R,tep[N];
	int t = id[L],s = id[R];
	long long tt = dinic(s,t);
	for(int i = 1;i<=n;i++)
		if(d[i])
			for(int j = 1;j<=n;j++)
				if(!d[j])
					mp[i][j] = mp[j][i] = min(mp[i][j],tt);
	for(int i = L;i<=R;i++)
		tep[d[id[i]] ? l++ : r--] = id[i];
	for(int i = L;i<=R;i++)
		id[i] = tep[i];
	slove(L,r),slove(l,R);	
}

例题 平面图的判定

题目链接

条件1 : 对于一个平面图边数 m m m,点数 n n n .有 : m < = 3 ∗ n − 6 : m <= 3*n-6 :m<=3n6;
条件2:对于一个哈密顿回路(所有顶点组成一个环),其他的边(不包含在回路上的边)要不在环内要不在环外(2-sat)。

#include<iostream>
#include<cstring>
using namespace std;
const int N = 2e6+10;
int h[N],e[N],ne[N],idx;
void add(int a,int b){
	ne[idx] = h[a],e[idx] = b,h[a] = idx++;
} 
struct node{
	int u,v;
}edge[N];
int cir[210],point[210];
int cut[210][210];
int t,n,m;
int dfn[N],low[N],tim;
bool st[N];
int stk[N],top;
int scc[N],cnt;
void tarjan(int u){
	dfn[u] = low[u] = ++tim;
	stk[++top] = u;
	st[u] = true;
	for(int i = h[u];~i;i=ne[i]){
		int y = e[i];
		if(!dfn[y]){
			tarjan(y);
			low[u] = min(low[u],low[y]);
		}
		else if(st[y])low[u] = min(low[u],dfn[y]);
	}
	if(dfn[u] == low[u])
	{
		int y;
		++cnt;
		do{
			y = stk[top--];
			st[y] = false;
			scc[y] = cnt;
		}while(y!=u);
	}
}
void init(){
	memset(h,-1,sizeof h);
	top = idx = cnt = tim = 0;
	memset(dfn,0,sizeof dfn);
	memset(cut,0,sizeof cut);
}
int main(){
	cin >> t;
	while(t--){
		init();
		cin >> n >> m;
		for(int i = 1;i<=m;i++)
			cin >> edge[i].u >> edge[i].v;
		
		
		for(int i = 1,a;i<=n;i++){
			cin >> point[i];
			cir[point[i]] = i;
		}
		
		if(m > 3*n-6){
			cout << "NO" << endl;
			continue;
		}
		for(int i = 1;i<n;i++){
			cut[point[i]][point[i+1]] = cut[point[i+1]][point[i]] = true;
		}
		cut[point[1]][point[n]] = cut[point[n]][point[1]] = true;
		
		for(int i = 1;i <= m;i++){
			int u = edge[i].u , v = edge[i].v;
			if(cut[u][v])continue;
			if(cir[u] > cir[v])swap(u,v);
			u = cir[u],v = cir[v];
			for(int j = i+1;j<=m;j++){
				int uu = edge[j].u , vv = edge[j].v;
				if(cut[uu][vv])continue;
				if(cir[uu] > cir[vv])swap(uu,vv);
				uu = cir[uu],vv = cir[vv];
				if((u < uu && uu < v && v < vv) || (uu < u && u < vv && vv < v)){
					add(i,j+m),add(i+m,j);
					add(j+m,i),add(j,i+m);	
				}
			}
		}
		
		for(int i = 1;i<=m*2;i++)if(!dfn[i])tarjan(i);
		
		bool flag = true;
		for(int i = 1;i<=m;i++){
			if(scc[i]==scc[i+m]){flag = false;break;}
		}
		if(flag)cout << "YES" << endl;
		else cout << "NO" << endl;
	}
	return 0;
}

欧拉路径和欧拉回路

前提:图是联通
无向图
[1] . 欧拉路径判断:

度数为奇数的点只有0个或2个。

[2].欧拉回路判断

度数为奇数的点只有0个。

有向图
[1] . 欧拉路径判断:

所有点的出度等于入读,或除了两个点,所有点的出度等于入读,其中一个点的入读比出度大一,另一个点的出度比入读大一。

[2].欧拉回路判断

所有点的出度等于入读

例题 欧拉回路

题目链接

#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int N = 1e5+10,M = 4e5+10;
int h[N],e[M],ne[M],idx;
void add(int a,int b){
    ne[idx] = h[a] , e[idx] = b,h[a] = idx++;
}
int n,m,type;
int in[N],out[N];
int vis[M];
vector<int > ans;
void dfs(int u){
    for(int &i = h[u];~i;){
        if(vis[i]){i = ne[i];continue;}
        
        vis[i] = 1;
        if(type == 1)vis[i^1] = 1;
        
        int t;
        if(type == 1){
            t = i / 2 + 1;
            if(i & 1)t = -t;
        }
        else t = i + 1;
        
        int y = e[i];
        i = ne[i];
        dfs(y);
        
        ans.push_back(t);
    }
}
int main(){
    cin >> type >> n >> m;
    memset(h,-1,sizeof h);
    for(int i = 1;i<=m;i++){
        int a,b;
        cin >> a >> b;
        add(a,b);
        if(type == 1)add(b,a);
        in[b] ++ ,out[a] ++;
    }
    if(type == 1)
    {
        for(int i = 1;i<=n;i++)
            if( (in[i] + out[i]) & 1)return puts("NO"),0;
    }
    else 
    {
        for(int i = 1;i<=n;i++)
            if(in[i] != out[i])return puts("NO"),0;
    }
    for(int i = 1;i<=n;i++)if(h[i]!=-1){dfs(i);break;}
    if(ans.size()!=m)return puts("NO"),0;
    puts("YES");
    for(int i = ans.size()-1;i>=0;i--){
        printf("%d ",ans[i]);
    }
    return 0;
}


一个32进制无符号整数的二进制中1的个数 : _ _ b u i l t i n _ p o p c o u n t ( ) \_\_builtin\_popcount() __builtin_popcount().

求解奇偶最短路。

可以拆点对于边 ( u , v ) (u,v) (u,v) u u u差成 u u u0 , u ,u ,u1 .

u u u1:表示到达 u u u需要奇数步
u u u0:表示到达 u u u需要偶数步
v v v同理。

连边时:
( u , v ) (u,v) (u,v) – > ( u , v + n ) , ( v , u + n ) (u,v+n),(v,u+n) (u,v+n),(v,u+n)

dsu

例题3 D. Tree Requests

D. Tree Requests

题意:

给你一颗树,顶点的权值为 a a a ~ z z z的字母,对于顶点 u u u找深度为 d d d的顶点能不能组成回文。 (这个深度为从根开始的.);

思路:

暴力维护 c n t [ d e p ] [ c h a r ] cnt[dep][char] cnt[dep][char] 深度为 d e p dep dep c h a r char char的个数.
对于是否能够组成回文:要么字母全为偶数,要么只有一个为奇数。(亦或运算)。

#include<iostream>
#include<cstring>
#include<vector>
using namespace std;
const int N = 5e5+10,M = N*2;
int h[N],e[M],ne[M],idx;
void add(int a,int b){
    ne[idx] = h[a],e[idx] = b,h[a] = idx++;
}
int n,m;
struct node{
	int id,dp;
};
vector<node> q[N];
int dep[N];
char c[N];
int col[N][30];
long long ans[N];
int L[N],R[N],dfn,Index[N];
int top[N],son[N],siz[N],fa[N];

void dfs1(int u,int f){
    L[u] = ++dfn;
    Index[dfn] = u;
    fa[u] = f;
    siz[u] = 1;
    dep[u] = dep[f] + 1;
	for(int i = h[u];~i;i=ne[i]){
        int y = e[i];
        if(y == f)continue;
        dfs1(y,u);
        siz[u] += siz[y];
        if(siz[son[u]] < siz[y])son[u] = y;
    }
    R[u] = dfn;
}
void dfs2(int v,int u){
    top[v] = u;
    if(!son[v])return ;
    dfs2(son[v],u);
    for(int i = h[v];~i;i=ne[i]){
        int y = e[i];
        if(y != fa[v] && y != son[v])dfs2(y,y);
    }
}
int skp,tot;
void add(int x){
	
	for(int i = L[x]+1;i<=R[x];i++){
		if(Index[i] == skp){
			col[dep[skp]][c[skp] - 'a'] ^= 1;
			i = R[Index[i]];
			continue;
		}
		//col(x,y) 深度为x,字母为y的个数 
		col[dep[Index[i]]][c[Index[i]] - 'a'] ^= 1;
	}
	
}

void sub(int x){
	for(int i = L[x];i<=R[x];i++){
		col[dep[Index[i]]][c[Index[i]] - 'a'] = 0;
	}
}

void dsu(int x){
 
    for(int i = h[x];~i;i=ne[i]){
        int y = e[i];
        if(y == son[x] || y == fa[x])continue;
        dsu(y);
    }
    
    if(son[x]){
    	dsu(son[x]);
        skp = son[x];
    }
    
    add(x);
   
   	for(auto s : q[x]){
   		int d = s.dp,id = s.id;
   		for(int i = 0;i<26;i++){
   		//	cout << char(i+'a') << col[d][i] << endl;
   			ans[id] += col[d][i];
		}
	} 
	
    if(x == top[x]){
		skp = 0;
    	sub(x);    
	}
    
}
int main(){
    cin >> n >> m;
   	memset(h,-1,sizeof h);
	for(int i = 2,a;i<=n;i++){
   		scanf("%d",&a);
   		add(i,a),add(a,i);
   	}
   	getchar();
    for(int i = 1;i<=n;i++){
        scanf("%c",&c[i]);
    }
    
   	for(int i = 1;i<=m;i++){
  		int u,d;
		scanf("%d%d",&u,&d);
		q[u].push_back({i,d});	
	} 

    dfs1(1,0);
    dep[0] = -1;
	dfs2(1,0);
    dsu(1);
	
	for(int i = 1;i<=m;i++){
		if(ans[i] <= 1)printf("Yes\n");
		else printf("No\n");
	}

    return 0;
}


EK
思路:

最小覆盖和二分图最大匹配等价的 问题转换为求二分图的最大匹配。
网络流求二分图的最大匹配

将源点连上左边所有点,右边所有点连上汇点,容量皆为 1 。原来的每条边从左往右连边,容量也皆为1 ,最大流即最大匹配。
最小路径覆盖=总顶点数−最大匹配

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N = 350,M = 15100,inf = 0x3f;
int h[N],e[M],ne[M],f[M],idx;
void add(int a,int b,int c){
    ne[idx] = h[a],e[idx] = b,f[idx] = c,h[a] = idx++;
    ne[idx] = h[b],e[idx] = a,f[idx] = 0,h[b] = idx++;
}
int pre[M],a[N];
bool st[M],vis[N];
int n,m;
int S = 0,T = N-10;
void EK(int s,int t){
    int flow = 0;
    while(1){
        memset(a,0,sizeof a);
        queue<int > q;
        q.push(s);
        a[s] = inf;
        while(!q.empty()){
            int u = q.front();q.pop();
            for(int i = h[u];~i;i=ne[i]){
                int y = e[i];
                if(f[i] > 0 && !a[y]){
                    a[y] = min(f[i],a[u]);
                    pre[y] = i;
                    q.push(y);
                }
            }
            if(a[t])break;
        }
        if(!a[t])break;
        for(int i = t;i!=s;i=e[pre[i]^1]){
            f[pre[i]] -= a[t];
            f[pre[i]^1] += a[t];
        }
    }
}
void dfs(int u){
    vis[u] = true;
    cout << u << " ";
    for(int i = h[u];~i;i=ne[i]){
        if(st[i]){
            int y = e[i];
            dfs(y-n);
            // 1对1匹配不用找后面的了
            return ;
        }
    }
}
int main(){
    cin >> n >> m;
    int a,b;
    memset(h,-1,sizeof h);
    for(int i = 1;i<=m;i++){
        cin >> a >> b;
        add(a,b+n,1);
    }
    for(int i = 1;i<=n;i++){
        add(S,i,1);
        add(i+n,T,1);
    }
    EK(S,T);
    //标记匹配边 和 匹配数量
    int cnt = 0;
     for(int i = 0;i<idx;i+=2){
		if(e[i]==S || e[i^1] == S)continue;
		if(e[i]==T || e[i^1] == T)continue;
		if(f[i]==0){//流量被用 
			st[i] = true;
            cnt ++;
		}
    }
    for(int i = 1;i<=n;i++){
        if(!vis[i])dfs(i),cout << endl;
    }
    cout << n - cnt << endl;
    return 0;
}

dinic

## Fox And Dinner
[题目链接](https://codeforces.com/contest/512/problem/C)

 <font color=#bd97b6 size=3 face="微软雅黑">分析:</font>
>题目说一个环至少$3$个数,表明每个数都要有$两$个相邻的数。
>和为质数,有$a>2$,所以任意两个数的和一定大于$2$,表明一定是一奇一偶的和。 
>建图 
>1. $s$向偶数建立一条容量为$2$的边,奇数向t建立一条容量为$2$的边。
>2. 和为质数的两个数连容量为$1$的一条边

$代码$
```cpp
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
//526526
using namespace std;
const int N = 300,M = 4e5+10,inf = 0x3f3f3f3f;
int h[N],ne[M],e[M],f[M],idx;
void add(int a,int b,int c){
	ne[idx] = h[a],e[idx] = b,f[idx] = c,h[a] = idx++;
	ne[idx] = h[b],e[idx] = a,f[idx] = 0,h[b] = idx++;
} 
vector<int> prime;
bool st[M],is_prime[M];
void getprime(int n = M){
	for(int i = 2;i<=n;i++){
		if(!st[i])prime.push_back(i),is_prime[i] = true;
		for(int j = 0;j<prime.size() && prime[j] * i< n;j++){
			st[i*prime[j]] = true;
			if(i % prime[j] == 0)break;
		}
	}
}
int dist[N],cur[N];
bool bfs(int s,int t){
	memset(dist,inf,sizeof dist);
	memset(st,0,sizeof st);
	queue<int> q;
	q.push(s);
	dist[s] = 0;
	st[s] = true;
	while(!q.empty()){
		int u = q.front();q.pop();
		st[u] = false;
		for(int i = h[u];~i;i=ne[i]){
			int y = e[i];
			if(f[i] > 0 && dist[y] > dist[u] + 1){
				dist[y] = dist[u] + 1;
				if(!st[y])q.push(y);
			} 
		}
	}
	return dist[t] != inf;
}
int dfs(int u,int mv,int t){
	if(u == t)return mv;
	for(int &i = cur[u];~i;i=ne[i]){
		int y = e[i];
		if(f[i] <= 0 || dist[y] != dist[u] + 1)continue;
		int cw = dfs(y,min(mv,f[i]),t);
		if(cw <= 0)continue;
		f[i] -= cw;
		f[i^1] += cw;
		return cw;
	}
	return 0;
}
int dinic(int s,int t){
	int flow=0;
	while(bfs(s,t)){
		memcpy(cur,h,sizeof h);
		while(int d = dfs(s,inf,t))flow += d;
	}
	return flow;
}
int n;
int a[N];
int ans = 1;
vector<int> res[N];
int s,t;
void find(int u){
	res[ans].push_back(u);
	st[u] = true;
	for(int i = h[u];~i;i=ne[i]){
		if(e[i] == t || e[i^1] == t)continue;
		int j;
		if(a[u] & 1)j = i ^ 1;
		else j = i;  
		if(f[j] == 0 && !st[e[i]]){
			find(e[i]);
		} 
	}
}
int main(){
	memset(h,-1,sizeof h);
	getprime();
	cin >> n;
	s=0,t=n+1;
	int even=0,odd=0;
	for(int i = 1;i<=n;i++){
		cin >> a[i];
		
		if(a[i]&1)add(i,t,2),odd++;
		else add(s,i,2),even++;
		
		for(int j = 1;j<i;j++){
			if(is_prime[a[i]+a[j]]){
				if(a[i]&1)add(j,i,1);
				else add(i,j,1);
			}
		}
	}
	if(even != odd || dinic(s,t) != n)printf("Impossible");
	else{	
		for(int i = 1;i<=n;i++){
			if(!st[i])find(i),ans++;
		}
		cout << ans-1 << endl;
		for(int i = 1;i<ans;i++){
			cout << res[i].size();
 			for(int j = 0;j<res[i].size();j++)cout << " " << res[i][j] ;
			cout << endl;
		}
	}
	return 0;
}

[网络流24题]最长递增子序列问题

题目链接
思路:

  1. 求最长上升子序列, l e [ i ] le[i] le[i]表示 i i i位置序列最长长度。
  2. 建立边 : i − j ( i > j ) i - j (i>j) ij(i>j)建立边条件 a r r [ i ] > = a r r [ j ] arr[i] >= arr[j] arr[i]>=arr[j] && l e [ i ] = l e [ j ] + 1 le[i] = le[j] + 1 le[i]=le[j]+1
  3. 对与问题2 以 源点 ( S ) (S) (S)向 长度为 1 1 1的点设置流量 1 1 1. 长度为最长子序列长度的点向汇点 ( T ) (T) (T)流量 1 1 1.
  4. 对于问题3 以 源点 ( S ) (S) (S)向 长度为 1 1 1的点设置流量 i n f inf inf. 长度为最长子序列长度的点向汇点 ( T ) (T) (T)流量 i n f inf inf(注意最长子序列长度为 1 1 1时流量为 1 1 1.).

[SCOI2007]蜥蜴

题目链接

思路:

  • 对于每个点拆点(入点和出点),容量为高度。
  • 对于有高度的点向附近距离小于 d d d的连边,出点连入店,容量为 i n f inf inf
  • 对于有壁虎的点,源点 s s s向该点入点连边,容量为 i n f inf inf
  • 对于能跑出棋盘的点(出点)向汇点连边,容量为 i n f inf inf

PIGS

题目链接

建图:

对于第 i i i个人如果想买第 j j j头猪圈的猪

  1. 如果 j j j猪圈没被合并过,则从源点 s s s连向 i i i一条容量为猪圈大小的边。
  2. 如过 j j j猪圈和并过,这从最后一次合并的那个人流向 i i i一条容量为 i n f inf inf的边。

最后 i i i向汇点 t t t连一个容量为购买猪数的边。

网络流24题]餐巾计划问题

题目链接
参考链接

#include<iostream>
#include<cstring>
#include<queue>
using namespace std;
const int N = 4100,M = 4e6+10,inf = 0x3f3f3f3f;
const long long inf64 = 23333333333333;
int h[M],e[M],ne[M],idx;
long long f[M],w[M];
void add(int a,int b,long long c,long long d){
    ne[idx] = h[a],e[idx] = b,f[idx] = c,w[idx] = d,h[a] = idx++;
    ne[idx] = h[b],e[idx] = a,f[idx] = 0,w[idx] = -d;h[b] = idx++;
}
int cur[M];
long long dist[M];
bool st[M];
long long cost;
bool spfa(int s,int t){
    for(int i = 0;i<N;i++)dist[i] = inf64,st[i] = 0;
    dist[s] = 0;
    st[s] = true;
    queue<int> q;
    q.push(s);
    while(!q.empty()){
        int u = q.front();q.pop();
        st[u] = false;
        for(int i = h[u];~i;i=ne[i]){
            int y = e[i];
            if(dist[y] > dist[u] + w[i] && f[i]){
                dist[y] = dist[u] + w[i];
                if(!st[y])st[y] = true,q.push(y);
            }
        }
        //cout <<u << endl;
    }
    return dist[t] != inf64;
}
long long dfs(int u,int t,long long mw){
    if(u == t)return mw;
    st[u] = true;
    for(int &i = cur[u];~i;i=ne[i]){
        int y = e[i];
        if(f[i] <= 0 || dist[y] != dist[u] + w[i] || st[y])continue;
        long long cw = dfs(y,t,min(f[i],mw));
        if(cw <= 0)continue;
        f[i] -= cw;
        f[i^1] += cw;
        cost += (long long)w[i] * cw;
        return cw;
    }
    st[u] = false;
    return 0;
}
long long dinic(int s,int t){
   while(spfa(s,t)){
   		memcpy(cur,h,sizeof cur);
   		while(dfs(s,t,inf64));
	}
   return cost; 
}
long long N_N,p,m,f_f,n,s_s;
int main(){
    cin >> N_N >> p >> m >> f_f >> n >> s_s;
    int s = 0,t = N_N * 2 + 1,tp;
    memset(h,-1,sizeof h);
    for(int i = 1;i<=N_N;i++){
        cin >> tp;
        add(s,i,inf,p);    // s
        add(i,t,tp,0); // t;
        add(s,i+N_N,tp,0);
        //慢洗;
        if(N_N >= i + n)add(i+N_N,i+n,inf64,s_s);
        //快洗
        if(N_N >= i + m)add(i+N_N,i+m,inf64,f_f);
        //不洗
        if(N_N >= i + 1)add(i+N_N,i+N_N+1,inf64,0);
    }
    cout << dinic(s,t) << endl;
    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值