from https://blog.csdn.net/weixin_40191952/article/details/89160419
打了这么久acm竞赛,也不过这些篇总结和一堆铜牌而已..
得到金牌的同学很优秀,可我们生活过的也是同样的时间,只不过我(我们)投入的少一些,走了一些弯路,做了更多其他的事。虽然写不进履历,但都如实构成了现在的我(我们)。
所以我觉得,那些不那么厉害的acmer也不必懊悔,感叹算法的神奇,经历过现场ac了以为超出自我水平的题目时的狂喜,有过全情投入的日子,即使不那么长,就足够了。
大学中我也接触过许多竞赛,也得过一些听起来比铜牌厉害的奖项。但只有acm,是我可以自豪的说,我打过acm,虽然成绩平平,但我喜欢它。如果本科再来一次,无论什么专业什么学校,我都希望还能在操场路邂逅acm海报,再次选择acm竞赛。
作为菜鸡把他们分享出来,不是想表示自己做了什么,而是将来如果有和我脑回路相似的初学者,能从我的经验里得到一些体会,少走一些弯路,就很棒了..
最后说一些模板本身的东西..最好是有这方面知识再去用模板,而不是像字典一样比赛带着就行。
模板本身都是自己写的,大半贴的不是一个类或函数,而是一整个代码文件,这是我的习惯..代码都测试过,但不保证100%正确(99%正确吧)。依稀记得并查集和C语言判断大数的好像有错过,但未必是模板错。
当然这个模板也是非常不全的..都是比较基础的..比我开始计划的要写的少很多..但也算是有些内容吧..
数论
O(nloglogn)的筛法:
for (i = 2; i*i <= mm; i++)
if (p[i] == 1)
for (j=i*i; j < mm; j += i)
p[j] = 0;
线性欧拉筛法 h[i]=0 where i is prime.p[i] is (i+1)th prime.z is number of prime under maxn。
for ( i = 2; i<maxn; i++)
{
if (!h[i])
p[z++] = i;
for (int j = 0; j<z; j++)
{
if (i*p[j]>maxn) break;
h[i*p[j]] = true;
if (i%p[j] == 0) break;
}
}
O(nlogn)欧拉函数
for (i = 1; i<maxnn; i++) a[i] = i;
for (i = 2; i<maxnn; i += 2) a[i] /= 2;
for (i = 3; i < maxnn; i += 2)
if (a[i] == i)
for (j = i; j < ; j += i)
a[j] = a[j] - a[j] / i;
米勒罗宾非确定算法判质数: O(slog³n)(1s判断万个longlong数,或用java自带函数:x=cin.nextBigInteger();if(x.isProbablePrime(1))...)
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int S = 8;
LL mult_mod(LL a, LL b, LL c)
{
a %= c, b %= c;
LL ret = 0, tmp = a;
while (b)
{
if (b & 1)
{
ret += tmp;
if (ret > c)
ret -= c;
}
tmp <<= 1;
if (tmp > c)
tmp -= c;
b >>= 1;
}
return ret;
}
LL pow_mod(LL a, LL n, LL mod)
{
LL ret = 1, temp = a % mod;
while (n)
{
if (n & 1)
ret = mult_mod(ret, temp, mod);
temp = mult_mod(temp, temp, mod);
n >>= 1;
}
return ret;
}
bool check(LL a, LL n, LL x, LL t)
{
LL ret = pow_mod(a, x, n), last = ret;
for (int i = 1; i <= t; i++)
{
ret = mult_mod(ret, ret, n);
if (ret == 1 && last != 1 && last != n - 1)
return 1;
last = ret;
}
if (ret != 1)
return 1;
return 0;
}
bool mill(LL n)
{
if (n < 2)
return 0;
if (n == 2)
return 1;
if ((n & 1) == 0)
return 0;
LL x = n - 1, t = 0;
while ((x & 1) == 0)
x >>= 1, t++;
srand(time(NULL));
for (int i = 0; i < S; i++)
{
LL a = rand() % (n - 1) + 1;
if (check(a, n, x, t))
return 0;
}
return 1;
}
int main()
{
LL n;
while (~scanf("%lld", &n))
puts(mill(n) ? "Yes" : "No");
}
java大数类可以用随机算法判断质数以及找下一个质数,判断质数的参数S是确定性,表示这个结果错误的概率为(1/2)^S,S与算法执行时间成正比(下一个质数函数默认参数S为100)
import java.math.*;
public class Main
{
public static void main(String[] args)
{
BigInteger bi1, bi2;
Boolean b1;
bi1 = new BigInteger("10633823966279326983230456482242756607");
bi2 = bi1.nextProbablePrime();
b1 = bi1.isProbablePrime(100);
String str1 = bi1+ " is prime with certainity is " +b1;
System.out.println(str1);
System.out.println(bi2);
}
}
二分幂:
LL po(LL a, LL b)
{
LL ans = 1;
while (b)
{
if (b & 1)
ans = ans * a % mm;
a = a * a % mm;
b = b >> 1;
}
return ans;
}
逆元,求(a/b)%p时(p为质数),b^-1%p=po(b,p-2),即:
(a/b)%p=(a*po(b,p-2))%p
求逆元,除法时记得特判分母为0(比如等比数列公比为1)。
矩阵快速幂:
对于可以logn的求出递推式的第n项,将f(n)=xxx右边的所有项竖列,乘以传递矩阵得到f(n+1)=xxx右边的所有项竖列(一般右边有几项就是几阶矩阵)。比如:
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define mm 1000000007
#define nn 6
LL g[22];
struct mat { LL a[nn][nn]; };
mat mat_mul(mat x, mat y)
{
mat res;
memset(res.a, 0, sizeof(res.a));
for (int i = 0; i < nn; i++)
for (int j = 0; j < nn; j++)
for (int k = 0; k < nn; k++)
res.a[i][j] = (x.a[i][k] * y.a[k][j] + res.a[i][j]) % mm;
return res;
}
mat pow(mat a, LL n)
{
mat res;
memset(res.a, 0, sizeof(res.a));
for (int i = 0; i < nn; i++)
res.a[i][i] = 1;//单位矩阵
while (n)
{
if (n & 1)
res = mat_mul(res, a);
a = mat_mul(a, a);
n >>= 1;
}
return res;
}
int main()
{
//g[0] = 0, g[1] = 1;
//for (i = 2; i < 22; i++)
//g[i] = g[i - 2] + g[i - 1] + i * i*i + i * i + i + 1;
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
mat qq;
LL n, te[6][6] = {
1,1,1,1,1,1,
1,0,0,0,0,0,
0,0,1,3,3,1,
0,0,0,1,2,1,
0,0,0,0,1,1,
0,0,0,0,0,1 };
memcpy(qq.a, te, sizeof(te));
scanf("%*d");
while (~scanf("%lld", &n))
{
mat ans = pow(qq, n - 1);
LL viia = (ans.a[0][0] + 8 * ans.a[0][2] + 4 * ans.a[0][3] + 2 * ans.a[0][4] + ans.a[0][5]) % mm;
printf("%lld\n", viia);
}
return 0;
}
图论
并查集:
void init(int size)
{
for (i = 0; i < size; i++) u[i] = -1;
}
int find(int x)
{
if (u[x] < 0) return x;
u[x] = find(u[x]);
return u[x];
}
void mix(int x, int y)
{
if ((x = find(x)) == (y = find(y))) return;
if (u[x] < u[y])
u[x] += u[y], u[y] = x;
else
u[y] += u[x], u[x] = y;
}
拓扑排序(删除图中度小于2的点直到无点可删):
void top()
{
queue<int>q;
int i, te, v;
for (i = 1; i <= n; i++)
if (nu[i] < 2) vi[i] = 1, q.push(i);
while (!q.empty())
{
te = q.front(), q.pop();
for (i = h[te]; i; i = a[i].ne)
{
v = a[i].v;
if (vi[v] == 1) continue;
nu[v]--;
if (nu[v] < 2)
q.push(v), vi[v] = 1;
}
}
}
最小生成树:
void add(int u, int v, int w)//t初始为1
{
a[t].u = u, a[t].v = v, a[t++].w = w;
}
int cmp(viia x, viia y)
{
return x.w < y.w;
}
int find(int x)
{
if (!f[x]) return x;
f[x] = find(f[x]);
return f[x];
}
int kru()
{
sort(a + 1, a + t, cmp);
int i, ans = 0, w, u, v, t1, t2, cnt = 0;
for (i = 1; i < t; i++)
{
u = a[i].u, v = a[i].v, w = a[i].w;
t1 = find(u), t2 = find(v);
if (t1 != t2)
f[t1] = t2, ans += w, cnt++;
if (cnt == t - 1) return ans;
}
return -1;
}
最短路系列 都要链表存边(注意int溢出 初始化最大值足够大):
void add(int u, int v, int w)//初始k=1
{
a[k].v = v, a[k].w = w, a[k].ne = h[u], h[u] = k++;
}
队列优化spfa(适合稀疏图 总体比dij慢):
int spfa(int str, int end)
{
int i, u, v, w;
for (i = 1; i <= n; i++) d[i] = 1 << 28;
d[str] = 0;
memset(vi, 0, sizeof(vi));
queue<int>q;
q.push(str);
while (!q.empty())
{
u = q.front(), q.pop(), vi[u] = 0;
for (i = h[u]; i; i = a[i].ne)
{
v = a[i].v, w = a[i].w;
if (d[v] > d[u] + w)
{
d[v] = d[u] + w;
if (!vi[v])
vi[v] = 1, q.push(v);
}
}
}
return d[end];
}
栈优化spfa(玄学速度 有时最快有时最慢):
int spfa(int sta,int end)
{
int i, u, v, w, top = 0;
for (i = 1; i <= n; i++) d[i] = 1 << 28;
d[sta] = 0, st[top++] = vi[sta] = 1;
while (top)
{
u = st[--top], vi[u] = 0;
for (i = h[u]; i; i = a[i].ne)
{
w = a[i].w, v = a[i].v;
if (d[v] > d[u] + w)
{
d[v] = d[u] + w;
if (!vi[v])
st[top++] = v, vi[v] = 1;
}
}
}
return d[end];
}
优先队列优化dij(适合稠密图 稳定速度 没见过卡这个tle的):
struct node
{
int v, c;
node(int _v = 0, int _c = 0) :v(_v), c(_c) {}
bool operator <(const node &r)const
{
return c > r.c;
}
};
int dij(int sta,int end)
{
int i, v, u, w, top = 0;
for (i = 1; i <= n; i++)
vi[i] = 0, d[i] = 1 << 28;
d[sta] = 0;
priority_queue<node>q;
q.push(node(sta, 0));
node tmp;
while (!q.empty())
{
tmp = q.top(), q.pop(), u = tmp.v;
if (vi[u])continue;
vi[u] = 1;
for (i = h[u]; i; i = a[i].ne)
{
v = a[i].v, w = a[i].w;
if (!vi[v] && d[v]>d[u] + w)
d[v] = d[u] + w, q.push(node(v, d[v]));
}
}
return d[end];
}
Lca最近公共祖先(tarjan离线算法 O(n+q)):
#include <bits/stdc++.h>
#define mm 40005
struct note
{
int u, v, w, lca, ne;
} edge[mm << 1], edge1[805];
int head[mm], ip, head1[mm], ip1, m, n, fa[mm], vis[mm], ance[mm], dir[mm];
//head&edge存单向边 head1&edge1存每组询问 以1点为根第i点深度为dir[i]
void init()
{
memset(vis, 0, sizeof(vis)), memset(dir, 0, sizeof(dir));
memset(head, -1, sizeof(head)), memset(head1, -1, sizeof(head1));
ip = ip1 = 0;
}
void add(int u, int v, int w)
{
edge[ip].v = v, edge[ip].w = w, edge[ip].ne = head[u], head[u] = ip++;
}
void add1(int u, int v)
{
edge1[ip1].u = u, edge1[ip1].v = v, edge1[ip1].lca = -1;
edge1[ip1].ne = head1[u], head1[u] = ip1++;
}
int find(int x)
{
if (fa[x] == x)
return x;
return fa[x] = find(fa[x]);
}
void Union(int x, int y)
{
x = find(x), y = find(y);
if (x != y)
fa[y] = x;
}
void tarjan(int u)
{
int i, v, w;
vis[u] = 1, ance[u] = fa[u] = u;
for (i = head[u]; i != -1; i = edge[i].ne)
{
v = edge[i].v, w = edge[i].w;
if (!vis[v])
dir[v] = dir[u] + w, tarjan(v), Union(u, v);
}
for (i = head1[u]; i != -1; i = edge1[i].ne)
{
v = edge1[i].v;
if (vis[v])
edge1[i].lca = edge1[i ^ 1].lca = ance[find(v)];
}
}
int main()
{
//O(n+q)
int u, v, w, i, lca;
while (~scanf("%d%d", &n, &m))
{
init();
for (i = 1; i < n; i++)
scanf("%d%d%d", &u, &v, &w), add(u, v, w), add(v, u, w);
for (i = 0; i < m; i++)
scanf("%d%d", &u, &v), add1(u, v), add1(v, u);
dir[1] = 0, tarjan(1);
for (i = 0; i < m; i++)
{
u = edge1[i << 1].u, v = edge1[i << 1].v, lca = edge1[i << 1].lca;
printf("%d\n", dir[u] + dir[v] - 2 * dir[lca]);
}
}
}
例:dij求路程小于L的情况下第二种路最少走多少(dis维护答案 pb维护总路程和第二种路程 总路程小于L才更新)
#include <bits/stdc++.h>
using namespace std;
int mp[5005][5005], dis[5005], vi[5005], p[5005], b[5005];
int main()
{
int i, x, y, z, n, m1, m2, l, mi, ma;
while (~scanf("%d%d%d%d", &n, &m1, &m2, &l))
{
memset(mp, -1, sizeof(mp));
for (i = 0; i <= n; i++)
dis[i] = p[i] = 1 << 28, b[i] = vi[i] = 0;
for (i = 0; i < m1; i++)
scanf("%d%d", &x, &y), mp[x][y] = mp[y][x] = 0;
for (i = 0; i < m2; i++)
{
scanf("%d%d%d", &x, &y, &z);
if (mp[x][y] == -1 || mp[x][y] > z)
mp[x][y] = mp[y][x] = z;
}
dis[1] = p[1] = b[i] = 0;
while (1)
{
ma = 1 << 28, mi = -1;
for (i = 1; i <= n; i++)
if (vi[i] == 0 && ma > p[i])
mi = i, ma = p[i];
if (mi == -1)break;
vi[mi] = 1, dis[mi] = p[mi];
for (i = 1; i <= n; i++)
if (mp[mi][i] == 0)
p[i] = p[mi], b[i] = b[mi] + 1;
else if (mp[mi][i] != -1)
if (p[i] > p[mi] + mp[mi][i] && b[mi] + mp[mi][i] <= l)
p[i] = p[mi] + mp[mi][i], b[i] = b[mi] + mp[mi][i];
}
printf("%d\n", vi[n] ? dis[n] : -1);
}
}
二分图最大匹配
二分图:不存在偶数环的无向图(无环图必是二分图)(二分图并不一定连通)。
注意二分图的存图,map[i][j]表示左边第i个与右边第j个点有边(和通常的i->j的有向边不同),Vector或模拟链表也是同理。
二分图拆点后的最大匹配=原图最大匹配/2
最小点覆盖数:选取最少的点,使任意一条边至少有一个端点被选择
二分图的最小顶点覆盖=最大匹配数(或拆点后的最大匹配/2)
一般图的最小顶点覆盖=npc问题(一般图包括有向图,偶数环图)
最大独立点集:选取最多的点,使任意所选两点均不相连
二分图的最大独立点集=V-最大匹配数
一般图的最大独立点集=npc问题
最小路径覆盖:选择尽量少的路径覆盖所有点。(注意路径和边不是一个意思,路径是多条连续的边)
Dag的最小不相交路径覆盖=V-拆点后的最大匹配
Dag的最小可相交路径覆盖=V-先floyd再拆点的最大匹配
Dag的最大不可达集:选尽量多的点使其中任意2点都不可达
Dag的最大不可达集= Dag的可相交路径覆盖=V-先floyd再拆点的最大匹配
特殊的最大团用二分图解:
已知班级一些女孩和男孩,所有女生之间都相互认识,所有男生之间也相互认识,给出m对关系表示哪个女孩与哪个男孩认识。现在要选择一些学生来组成一个团,使得里面所有人都认识,求此团最大人数。
原图的最大团=补图的最大独立集,原图的最大独立集=补图的最大团
显然补图为二分图,(V-补图的最大匹配)即可。(补图就是把原图有的边去掉,没有的边加上)
O(VE)的dfs实现匈牙利算法:
#include <bits/stdc++.h>
using namespace std;
#define mm 205
int n, m, link[mm];
bool use[mm], mp[mm][mm];
//O(ve) 下标从1开始
bool dfs(int cap)
{
int i, j;
for (i = 1; i <= m; i++)
if (mp[cap][i] && !use[i])
{
use[i] = 1;
j = link[i];
link[i] = cap;
if (j == -1 || dfs(j))
return 1;
link[i] = j;
}
return 0;
}
int hungary()
{
int num = 0;
memset(link, -1, sizeof(link));
for (int i = 1; i <= n; i++)
{
memset(use, 0, sizeof(use));
if (dfs(i))
num++;
}
return num;
}
int main()
{
int i, num, tmp;
while (~scanf("%d%d", &n, &m))
{
memset(mp, 0, sizeof(mp));
for (i = 1; i <= n; i++)
{
scanf("%d", &num);
while (num-- && scanf("%d", &tmp))
mp[i][tmp] = 1;
}
printf("%d\n", hungary());
}
}
Vector加速版(甚至能过小几千的数据):
#include <bits/stdc++.h>
using namespace std;
vector<int>G[1505];
bool vis[1505];
int match[1505], n;
bool dfs(int u)
{
for (int i = 0; i < G[u].size(); i++)
{
int t = G[u][i];
if (!vis[t])
{
vis[t] = 1;
if (match[t] == -1 || dfs(match[t]))
{
match[t] = u;
return 1;
}
}
}
return 0;
}
int hungary()
{
int res = 0;
memset(match, -1, sizeof(match));
for (int i = 1; i <= n; i++)
{
memset(vis, 0, sizeof(vis));
if (dfs(i))
res++;
}
return res;
}
int main()
{
int i, num, tmp, m;
while (~scanf("%d%d", &n, &m))
{
for (i = 0; i < 1505; i++)
G[i].clear();
for (i = 1; i <= n; i++)
{
scanf("%d", &num);
while (num-- && scanf("%d", &tmp))
G[i].push_back(tmp);
}
printf("%d\n", hungary());
}
}
复杂度O(V^0.5E)的hopcroft_karp算法:(小数据下效率和上一个差不多,大数据时明显快点,很少有题目会卡这个算法..)
//hopcroft_karp算法,复杂度O(sqrt(n)*m)
#include <bits/stdc++.h>
using namespace std;
const int N = 320;
const int INF = 0x3f3f3f3f;
struct
{
int to, next;
} g[N * N];
int head[N];
bool used[N];
int p, n;
int nx, ny, cnt, dis, dx[N], dy[N], cx[N], cy[N];
//nx,ny分别是左点集和右点集的点数
//dx,dy分别维护左点集和右点集的标号
//cx表示左点集中的点匹配的右点集中的点,cy正好相反
void add_edge(int v, int u)
{
g[cnt].to = u, g[cnt].next = head[v], head[v] = cnt++;
}
bool bfs() //寻找增广路径集,每次只寻找当前最短的增广路
{
queue<int> que;
dis = INF;
memset(dx, -1, sizeof dx);
memset(dy, -1, sizeof dy);
for (int i = 1; i <= nx; i++)
if (cx[i] == -1) //将未遍历的节点入队,并初始化次节点距离为0
que.push(i), dx[i] = 0;
while (!que.empty())
{
int v = que.front();
que.pop();
if (dx[v] > dis)
break;
for (int i = head[v]; i != -1; i = g[i].next)
{
int u = g[i].to;
if (dy[u] == -1)
{
dy[u] = dx[v] + 1;
if (cy[u] == -1)
dis = dy[u]; //找到了一条增广路,dis为增广路终点的标号
else
dx[cy[u]] = dy[u] + 1, que.push(cy[u]);
}
}
}
return dis != INF;
}
int dfs(int v)
{
for (int i = head[v]; i != -1; i = g[i].next)
{
int u = g[i].to;
if (!used[u] && dy[u] == dx[v] + 1)
//如果该点没有被遍历过并且距离为上一节点+1
{
used[u] = 1;
if (cy[u] != -1 && dy[u] == dis)
continue;
//u已被匹配且已到所有存在的增广路终点的标号,直接跳过
if (cy[u] == -1 || dfs(cy[u]))
{
cy[u] = v, cx[v] = u;
return 1;
}
}
}
return 0;
}
int hopcroft_karp()
{
int res = 0;
memset(cx, -1, sizeof cx);
memset(cy, -1, sizeof cy);
while (bfs())
{
memset(used, 0, sizeof used);
for (int i = 1; i <= nx; i++)
if (cx[i] == -1)
res += dfs(i);
}
return res;
}
int main()
{
int a, b;
while (~scanf("%d%d", &p, &n))
{
cnt = 0;
memset(head, -1, sizeof head);
for (int i = 1; i <= p; i++)
{
scanf("%d", &a);
for (int j = 0; j < a; j++)
{
scanf("%d", &b);
add_edge(i, b);
}
}
nx = p, ny = n;
printf("%d\n", hopcroft_karp());
}
}
带权二分图的匹配(最佳完美匹配)
如果二分图的每条边都有一个权(可以是负数),要求一种完备匹配方案,使得所有匹配边的权和最大,记做最佳完美匹配。(所以当所有边的权为1时,就是最大匹配)(最小匹配把所有边乘以-1就好)
Kuhn-Munkras算法,可以有负边,O(n^3)~O(n^4):
#include <bits/stdc++.h>
using namespace std;
#define maxn 305
#define INF 0x3f3f3f3f
int match[maxn], lx[maxn], ly[maxn], slack[maxn];
//lx,ly,slack存权值 match存表示最终第j个目标匹配到第i个上
//下标从0开始 大常数的O(n^3) 注意nx ny的赋值
//求最小匹配则乘以-1求最大
int n, nx, ny, ans, G[maxn][maxn];
bool visx[maxn], visy[maxn];
bool findpath(int x)
{
int tempDelta;
visx[x] = 1;
for (int y = 0; y < ny; ++y)
{
if (visy[y])
continue;
tempDelta = lx[x] + ly[y] - G[x][y];
if (tempDelta == 0)
{
visy[y] = 1;
if (match[y] == -1 || findpath(match[y]))
{
match[y] = x;
return 1;
}
}
else if (slack[y] > tempDelta)
slack[y] = tempDelta;
}
return 0;
}
void KM()
{
int x, i, j;
for (x = 0; x < nx; ++x)
{
for (j = 0; j < ny; ++j)
slack[j] = INF;
while (1)
{
memset(visx, 0, sizeof(visx));
memset(visy, 0, sizeof(visy));
if (findpath(x))
break;
else
{
int delta = INF;
for (j = 0; j < ny; ++j)
if (!visy[j] && delta > slack[j])
delta = slack[j];
for (i = 0; i < nx; ++i)
if (visx[i])
lx[i] -= delta;
for (j = 0; j < ny; ++j)
{
if (visy[j])
ly[j] += delta;
else
slack[j] -= delta;
}
}
}
}
}
void solve()
{
memset(match, -1, sizeof(match));
memset(ly, 0, sizeof(ly));
for (int i = 0; i < nx; ++i)
{
lx[i] = -INF;
for (int j = 0; j < ny; ++j)
if (lx[i] < G[i][j])
lx[i] = G[i][j];
}
KM();
}
int main()
{
int i, j, ans;
while (~scanf("%d", &n))
{
nx = ny = n;
for (i = 0; i < nx; ++i)
for (j = 0; j < ny; ++j)
scanf("%d", G[i] + j);
solve();
ans = 0;
for (i = 0; i < ny; ++i)
if (match[i] != -1)
ans += G[match[i]][i];
printf("%d\n", ans);
}
}
二分图的最小点权覆盖= 最大完美匹配(最大流)
二分图点权最大独立集=二分图点权和-二分图最小点权覆盖集
最大流:向图源点到汇点的最大流量。
矩阵存边的话重边需要加上
初始化最大值时要大于最终的最大流(比如所有边之和)
st2点间最大流=st两点间的最小割。
O(m*n^2)的dinic算法:(poj 1273)
#include <bits/stdc++.h>
using namespace std;
#define inf 11111111
const int N = 255;
struct
{
int v, w, ne;
} ed[444];
int cnt, co, id[N], flor[N], cur[N];
//co为点数,遍历所有点时用到(函数中唯一需要改的地方)
void add(int a, int b, int x)
{
//加边一次加4个,直接调用就好
ed[cnt].v = b, ed[cnt].w = x;
ed[cnt].ne = id[a], id[a] = cnt++;
ed[cnt].v = a, ed[cnt].w = 0;
ed[cnt].ne = id[b], id[b] = cnt++;
}
int bfs(int s, int t)
{
queue<int> q;
memset(flor, 0, sizeof(flor));
flor[s] = 1, q.push(s);
while (q.size())
{
int now = q.front();
q.pop();
if (now == t)
return 1;
for (int i = id[now]; ~i; i = ed[i].ne)
{
int to = ed[i].v;
if (flor[to] == 0 && ed[i].w > 0)
{
flor[to] = flor[now] + 1;
q.push(to);
if (to == t)
return 1;
}
}
}
return flor[t] != 0;
}
int dfs(int s, int t, int value)
{
int ret = value, a;
if (s == t || value == 0)
return value;
for (int &i = cur[s]; ~i; i = ed[i].ne)
{
int to = ed[i].v;
if (flor[to] == flor[s] + 1 && (a = dfs(to, t, min(ret, ed[i].w))))
{
ed[i].w -= a, ed[i ^ 1].w += a;
ret -= a;
if (!ret)
break;
}
}
if (ret == value)
flor[s] = 0;
return value - ret;
}
int dinic(int s, int t)
{
int ret = 0;
while (bfs(s, t))
{
for (int i = 1; i <= co; i++)//遍历所有点,这里是需要修改的!!
cur[i] = id[i];
ret += dfs(s, t, inf);
}
return ret;
}
int main()
{
int a, b, c, n, m;
while (~scanf("%d%d", &m, &n))
{
memset(id, -1, sizeof(id)), cnt = 0;
for (int i = 0; i < m; i++)
{
scanf("%d%d%d", &a, &b, &c);
add(a, b, c);
}
co = n;
printf("%d\n", dinic(1, n));
}
return 0;
}
有上下界的网络流:就是有下界的最大流(上界本来就有)
流程是:
把每条边拆成必要边和不必要边(必要边是下界流量,不必要边是上界-下界)
加点x,y,将每个不必要边u->v拆成u->x和y->v,再加边原汇点->原源点,容量为无穷。此时新图为y是源点x是汇点的最大流图。
若最大流<所有流入x的边之和(即最大流小于流量下界和),则原图无解。
否则,记sum1为从s点流出的流量和。
在残余网络里,去掉st之间的所有边(就是上一步加的无穷边和反向0边这2个边),再做一次s到t的最大流(xy点存在与否并不影响,因为这2点只有出或入,并不能传递流量),最大流为sum2。
有下界的最大流流量即sum1 + sum2。
要求最大流的具体信息的话,第一次跑完存图为G1,第二次跑完图为G2,原图下界为LC,
则原图i->j流量为:G1[i][j] – G2[i][j] + LC[i][j]
有重边的话:注意流量和下界都要累加,合并为1条边。
下界不会是负数(mi = min(x, mi))。
在原最大流模板下加一个有下界的最大流函数,用二维矩阵存上下界,在函数内建图,跑2遍最大流记录sum1,sum2,并返回即可。
模板(上面和dinic一样,只多了一行数组定义):
#include <bits/stdc++.h>
using namespace std;
#define inf 11111111
const int N = 255;
struct
{
int v, w, ne;
} ed[444], ed1[444];
int cnt, co, id[N], flor[N], cur[N];
//co为点数,遍历所有点时用到(函数中唯一需要改的地方)
int mi[N][N], ma[N][N];
void add(int a, int b, int x)
{
//加边一次加4个,直接调用就好
ed[cnt].v = b, ed[cnt].w = x;
ed[cnt].ne = id[a], id[a] = cnt++;
ed[cnt].v = a, ed[cnt].w = 0;
ed[cnt].ne = id[b], id[b] = cnt++;
}
int bfs(int s, int t)
{
queue<int> q;
memset(flor, 0, sizeof(flor));
flor[s] = 1, q.push(s);
while (q.size())
{
int now = q.front();
q.pop();
if (now == t)
return 1;
for (int i = id[now]; ~i; i = ed[i].ne)
{
int to = ed[i].v;
if (flor[to] == 0 && ed[i].w > 0)
{
flor[to] = flor[now] + 1;
q.push(to);
if (to == t)
return 1;
}
}
}
return flor[t] != 0;
}
int dfs(int s, int t, int value)
{
int ret = value, a;
if (s == t || value == 0)
return value;
for (int &i = cur[s]; ~i; i = ed[i].ne)
{
int to = ed[i].v;
if (flor[to] == flor[s] + 1 && (a = dfs(to, t, min(ret, ed[i].w))))
{
ed[i].w -= a, ed[i ^ 1].w += a;
ret -= a;
if (!ret)
break;
}
}
if (ret == value)
flor[s] = 0;
return value - ret;
}
int dinic(int s, int t)
{
int ret = 0;
while (bfs(s, t))
{
for (int i = 1; i <= co; i++)//遍历所有点,这里是需要修改的!!
cur[i] = id[i];
ret += dfs(s, t, inf);
}
return ret;
}
int bound_flow(int s, int t)
{
//由mi ma数组求有上界的网络流 co为遍历所有点的参数,需要提前传入
memset(id, -1, sizeof(id));
int i, j, x = co + 1, y = co + 2;
int sum = 0, sum1 = 0, sum2 = 0, s1 = 0;
for (i = 0; i <= co; i++)
for (j = 0; j <= co; j++)
if (mi[i][j] != 0)//拆必要边
{
if (i == s)
s1 += mi[i][j];
add(i, x, mi[i][j]), add(y, j, mi[i][j]);
sum += mi[i][j];//必要边之和
}
for (i = 0; i <= co; i++)
for (j = 0; j <= co; j++)
if (ma[i][j] - mi[i][j] > 0)//加不必要边
{
if (i == s)
s1 += ma[i][j] - mi[i][j];
add(i, j, ma[i][j] - mi[i][j]);
}
add(t, s, inf);//加无穷边
memcpy(ed1, ed, sizeof(ed));
co = co + 2;//改点数
int temp = dinic(y, x);
if (temp != sum)
return -1;
i = s;
for (j = id[i]; ~j; j = ed[j].ne)
if (ed[j].w < ed1[j].w)
sum1 += ed1[j].w - ed[j].w;//计算sum1
for (j = id[i]; ~j; j = ed[j].ne)
if (ed[j].v == t)
ed[j].w = 0;
for (j = id[t]; ~j; j = ed[j].ne)
if (ed[j].v == s)
ed[j].w = 0;//删st边
sum2 = dinic(s, t);
return sum1 + sum2;
}
最小费用最大流:对于每条有向边i->j,都有一个单位费用和流量(流量每多1都要多一个单位费用),求s到t的最大流中,总费用最小的。
求无向图1->n->1的不重复最短路径。(以poj2135为例)
相当于求1->n 的2条不相交边的路径的和的最小。
建立源点汇点,源点连到1一条流量为2(表示2条路)费用为0的有向边,汇点同理连到n。
对于每条费用为c的无向边i<->j,,建立i->j和j->i的流量为1费用c的边(带0边)。
这时候源点到汇点的最小费用就是所求最短路。
#include <bits/stdc++.h>
using namespace std;
const int inf = 1 << 28, MAXN = 1005, MAXM = 10005 << 2;
struct
{
int s, to, ne, ca, va;//费用va 容量ca
} ed[MAXM];
int id[MAXN], pre[MAXN], dis[MAXN], cnt;
bool vis[MAXN];
void addedge(int a, int b, int v, int c)
{
//加边和反向0边
ed[cnt].to = b, ed[cnt].s = a, ed[cnt].va = v;
ed[cnt].ca = c, ed[cnt].ne = id[a], id[a] = cnt++;
ed[cnt].to = a, ed[cnt].s = b, ed[cnt].va = -v;
ed[cnt].ca = 0, ed[cnt].ne = id[b], id[b] = cnt++;
}
bool spfa(int s, int t, int nnum)
{
//[0,nnum]中s到t是否存在最短路
memset(vis, 0, sizeof(vis));
memset(pre, -1, sizeof(pre));
for (int i = 0; i <= nnum; i++)
dis[i] = inf;
queue<int> que;
que.push(s);
dis[s] = 0, vis[s] = 1;
while (!que.empty())
{
int temp = que.front();
que.pop(), vis[temp] = 0;
for (int i = id[temp]; ~i; i = ed[i].ne)
if (ed[i].ca)
{
int ne = ed[i].to;
if (dis[temp] + ed[i].va < dis[ne])
{
dis[ne] = dis[temp] + ed[i].va;
pre[ne] = i;
if (!vis[ne])
vis[ne] = 1, que.push(ne);
}
}
}
return dis[t] != inf;
}
int getMincost(int s, int t, int nnum)
{
//[0,nnum]中s到t的最小费用最大流的最小费用
int ans_flow = 0, ans_cost = 0, temp, minc;
while (spfa(s, t, nnum))
{
temp = t;
minc = inf;
while (pre[temp] != -1)
{
minc = min(ed[pre[temp]].ca, minc);
temp = ed[pre[temp]].s;
}
temp = t;
while (pre[temp] != -1)
{
ed[pre[temp]].ca -= minc;
int ss = pre[temp] ^ 1;
ed[ss].ca += minc;
temp = ed[pre[temp]].s;
}
ans_cost += dis[t] * minc;
}
return ans_cost;
}
int main()
{
int i, a, b, v, s, t, n, m;
while (~scanf("%d%d", &n, &m))
{
memset(id, -1, sizeof(id)), cnt = 0;
memset(ed, 0, sizeof(ed));
for (i = 0; i < m; i++)
{
scanf("%d%d%d", &a, &b, &v);
addedge(a, b, v, 1);
addedge(b, a, v, 1);
}
s = n + 1, t = n + 2;
addedge(s, 1, 0, 2);
addedge(n, t, 0, 2);
printf("%d\n", getMincost(s, t, t));
}
return 0;
}
连通性:
强连通:有向图中,如果任意2点都相互可达,则该图是强连通图。
强连通分量:有向图中,其强连通图子图,称为强连通分量。(缩点后每个点都原图中最大的强连通分量)
一个有向图是强连通的,等价于G中有一个回路,它至少包含每个节点一次。(只是一笔画经过所有点回到原点,点可以通过多次,不一定是一个大环,也可能是几个小环的拼接。但环上的所有点一定构成强连通分量)。
一些问题只要变成有向无环图就容易解决,但其中有环就比较难办,而环等价于强连通分量,把每个强连通分量缩成一个点,就是dag了。
常用算法是tarjan算法,复杂度是O(n+m),线性的。(注意有很多个tarjan算法..这个是求强连通的,还有离线求lca的,求双联通的..不是同一个算法...)
一些简单推论:
从任一点出发都可以到达的点有几个?
缩点后如果出度为0的点集唯一,符合条件的点就是该点集中的点,否则不存在符合条件的点。(poj 2186)
最小点基:选择最少的点,使得从这些点出发可以到达所有点。
最小权点基:选择权和尽量小的点集,使得从这些点出发可以到达所有点。(hdu5934)
解法:入度为0的强连通分量个数即为最小点基,从每个入度为0的强连通分量中取出权值最小的点,构成的集合即最小权点基。
最少加多少边能使图变为强连通图?(poj1236)
Ans = max(缩点后入度为0的点集,缩点后出度为0的点集)(特判如果缩点后只有一个点则原图已经强连通,ans = 0)
以poj1236为例(求最小点基点数,及至少加几个点变成强连通图),比较模板的写法(主函数中只需要调用,缩点后的信息都有直接处理好,加边后跑一遍当做黑盒用就好..)
#include <bits/stdc++.h>
#define ll long long
#define mm 10005
using namespace std;
stack<int> sta;
bool vis[mm], in[mm], out[mm];
//vis点是否在栈中 缩点后的点是否有出度入度
int n, m, tim, num, cnt;
//tim点的标记 num强连通分量个数(缩点后的点数) cnt链表计数 都是从1开始
int h[mm], dfn[mm], low[mm], siz[mm], bel[mm];
//bel:u是属于哪个集合中的 siz[i]第i个强连通的点数
int xx[mm], yy[mm];//备份边
struct
{
int to, ne;
} ed[mm * 5];
void init()
{
memset(vis, 0, sizeof(vis)), memset(in, 0, sizeof(in));
memset(h, 0, sizeof(h)), memset(dfn, 0, sizeof(dfn));
memset(low, 0, sizeof(low)), memset(bel, 0, sizeof(bel));
memset(siz, 0, sizeof(siz)), memset(out, 0, sizeof(out));
cnt = tim = num = 0;
while (!sta.empty())
sta.pop();
}
void add(int u, int v)
{
ed[++cnt].to = v, ed[cnt].ne = h[u], h[u] = cnt;
xx[cnt] = u, yy[cnt] = v;
}
void tarjan(int u)
{
dfn[u] = low[u] = ++tim;
vis[u] = 1;
sta.push(u);
for (int i = h[u]; i; i = ed[i].ne)
{
int v = ed[i].to;
if (!dfn[v])
tarjan(v), low[u] = min(low[u], low[v]);
else if (vis[v])
low[u] = min(low[u], dfn[v]);
}
if (dfn[u] == low[u])//发现新的强连通分量
{
int v;
num++;
do
{
v = sta.top(), sta.pop();
vis[v] = 0, bel[v] = num;
siz[num]++;//num从1开始
} while (u != v);
}
}
int main()
{
int i, j, te;
while (~scanf("%d", &n))
{
init();
for (i = 1; i <= n; i++)
while (scanf("%d", &te) && te)
add(i, te);
for (i = 1; i <= n; i++)
if (!dfn[i])
tarjan(i);
//缩点后共num个点 i缩点后为bel[i] 缩点后第x个点中有原siz[x]个点
for (i = 1; i <= cnt; i++)
if (bel[xx[i]] != bel[yy[i]])
out[bel[xx[i]]] = 1, in[bel[yy[i]]] = 1;
//点集bel[x[i]]有出度 bel[y[i]]有入度
if (num == 1)
{
puts("1\n0");
continue;
}
int ans1 = 0, ans2 = 0;
for (i = 1; i <= num; i++)
{
if (in[i] == 0)
ans1++;
if (out[i] == 0)
ans2++;
}
printf("%d\n%d\n", ans1, max(ans1, ans2));
}
return 0;
}
数据结构
单点修改线段树:(单点增加+区间和查询为例)
#include<bits/stdc++.h>
using namespace std;
struct { int l, r, n; }t[50005 << 2];
void build(int l, int r, int i)
{
t[i].l = l, t[i].r = r, t[i].n = 0;
if (l == r)
return;
int mid = (l + r) >> 1;
build(l, mid, i << 1), build(mid + 1, r, i << 1 | 1);
}
void update(int i, int x, int k)
{
t[k].n += x;
if (t[k].l == t[k].r)
return;
int mid = (t[k].l + t[k].r) >> 1;
if (i <= mid)
update(i, x, k << 1);
else
update(i, x, k << 1 | 1);
}
int sea(int l, int r, int k)
{
if (t[k].l == l && t[k].r == r)
return t[k].n;
int mid = (t[k].r + t[k].l) >> 1;
if (r <= mid)
return sea(l, r, k << 1);
if (l > mid)
return sea(l, r, k << 1 | 1);
return sea(l, mid, k << 1) + sea(mid + 1, r, k << 1 | 1);
}
int main()
{
char ss[13];
int tt, i, n, x, y;
scanf("%d", &tt);
for (int ca = 1; ca <= tt; ca++)
{
printf("Case %d:\n", ca);
scanf("%d", &n);
build(1, n, 1);
for (i = 1; i <= n; i++)
scanf("%d", &x), update(i, x, 1);
while (scanf("%s", ss) && ss[0] != 'E')
{
scanf("%d%d", &x, &y);
if (ss[0] == 'Q')
printf("%d\n", sea(x, y, 1));
if (ss[0] == 'A')
update(x, y, 1);
if (ss[0] == 'S')
update(x, -y, 1);
}
}
return 0;
}
区间修改线段树(区间增加+查询为例):
#include <bits/stdc++.h>
using namespace std;
#define LL long long
struct { int l, r; LL n, ad; } t[100005 << 2];
void fx(int k, LL c)//i点的值和标记都更新c
{
t[k].n += (t[k].r - t[k].l + 1)*c;
t[k].ad += c;
}
void up(int k)//根据子节点更新该点
{
t[k].n = t[k << 1].n + t[k << 1 | 1].n;
}
void down(int k)//将lazy标记推向子节点
{
fx(k << 1, t[k].ad), fx(k << 1 | 1, t[k].ad);
t[k].ad = 0;
}
void build(int l, int r, int k)
{
t[k].l = l, t[k].r = r;
t[k].n = t[k].ad = 0;
if (l == r)
return;
int mid = (l + r) >> 1;
build(l, mid, k << 1), build(mid + 1, r, k << 1 | 1);
}
void update(int l, int r, int c, int k)
{
if (l == t[k].l && r == t[k].r)
{
fx(k, c);
return;
}
if (t[k].ad)
down(k);
int mid = (t[k].l + t[k].r) >> 1;
if (r <= mid)
update(l, r, c, k << 1);
else if (l > mid)
update(l, r, c, k << 1 | 1);
else update(l, mid, c, k << 1), update(mid + 1, r, c, k << 1 | 1);
up(k);
}
LL sea(int l, int r, int k)
{
if (l == t[k].l && r == t[k].r)
return t[k].n;
if (t[k].ad)
down(k);
int mid = (t[k].r + t[k].l) >> 1;
if (r <= mid)
return sea(l, r, k << 1);
if (l > mid)
return sea(l, r, k << 1 | 1);
return sea(l, mid, k << 1) + sea(mid + 1, r, k << 1 | 1);
up(k);
}
int main()
{
int i, n, q, l, r, c;
LL x, y;
char s[33];
while (~scanf("%d%d", &n, &q))
{
build(1, n, 1);
for (i = 0; i < n; i++)
{
scanf("%lld", &x);
update(i + 1, i + 1, x, 1);
}
while (q-- && scanf("%s", s))
{
if (s[0] == 'Q')
{
scanf("%d%d", &l, &r);
printf("%lld\n", sea(l, r, 1));
}
if (s[0] == 'C')
{
scanf("%d%d%d", &l, &r, &c);
update(l, r, c, 1);
}
}
}
return 0;
}
线段树扫描线求面积交O(nlogn):
#include <bits/stdc++.h>
using namespace std;
struct
{
int l, r, co;
double n1, n2;
} t[2005 << 2];
struct lin
{
double x, y1, y2;
int f;
} sc[2005];
double y[2005];
int cmp(lin a, lin b)
{
return a.x < b.x;
}
void build(int l, int r, int i)
{
t[i].l = l, t[i].r = r, t[i].co = 0, t[i].n1 = t[i].n2 = 0;
if (r - l == 1)
return;
int mid = (l + r) >> 1;
build(l, mid, i << 1), build(mid, r, i << 1 | 1);
}
void up(int i)
{
if (t[i].co > 1)
t[i].n2 = y[t[i].r] - y[t[i].l], t[i].n1 = 0;
else if (t[i].co == 1)
if (t[i].r - t[i].l != 1)
{
t[i].n2 = t[i << 1].n1 + t[i << 1].n2 +
t[i << 1 | 1].n1 + t[i << 1 | 1].n2;
t[i].n1 = y[t[i].r] - y[t[i].l] - t[i].n2;
}
else
{
t[i].n2 = 0;
t[i].n1 = y[t[i].r] - y[t[i].l];
}
else if (t[i].r - t[i].l == 1)
&