打印代码:
二分图匹配:
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<=3∗n−6;
条件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
题意:
给你一颗树,顶点的权值为 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题]最长递增子序列问题
题目链接
思路:
- 求最长上升子序列, l e [ i ] le[i] le[i]表示 i i i位置序列最长长度。
- 建立边 : i − j ( i > j ) i - j (i>j) i−j(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
- 对与问题2 以 源点 ( S ) (S) (S)向 长度为 1 1 1的点设置流量 1 1 1. 长度为最长子序列长度的点向汇点 ( T ) (T) (T)流量 1 1 1.
- 对于问题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头猪圈的猪
- 如果 j j j猪圈没被合并过,则从源点 s s s连向 i i i一条容量为猪圈大小的边。
- 如过 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;
}