打了这么久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
{
if (i*p[j]>maxn) break;
h[i*p[j]] = true;
if (i%p[j] == 0) break;
}
}
O(nlogn)欧拉函数
for (i = 1; i
for (i = 2; i
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
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
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
#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
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
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
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
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
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
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
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
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
#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
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
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
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)
t[i].n1 = t[i].n2 = 0;
else
{
t[i].n1 = t[i << 1].n1 + t[i << 1 | 1].n1;
t[i].n2 = t[i << 1].n2 + t[i << 1 | 1].n2;
}
}
void update(double l, double r, int k, int i)
{
int mid = (t[i].l + t[i].r) >> 1;
if (l <= y[t[i].l] && r >= y[t[i].r])
t[i].co += k;
else if (r <= y[mid])
update(l, r, k, i << 1);
else if (l >= y[mid])
update(l, r, k, i << 1 | 1);
else update(l, y[mid], k, i << 1), update(y[mid], r, k, i << 1 | 1);
up(i);
}
int main()
{
int n, i, z, ca = 1;
double x1, x2, y1, y2, li, sum;
while (~scanf("%d", &n) && n)
{
printf("Test case #%d\n", ca++), z = 0;
while (n-- && scanf("%lf%lf%lf%lf", &x1, &y1, &x2, &y2))
{
sc[z].x = x1, sc[z].y1 = y1, sc[z].y2 = y2;
sc[z].f = 1, y[z] = y1, z++;
sc[z].x = x2, sc[z].y1 = y1, sc[z].y2 = y2;
sc[z].f = -1, y[z] = y2, z++;
}
sort(y, y + z), sort(sc, sc + z, cmp);
build(0, unique(y, y + z) - y - 1, 1);
sum = li = 0;
for (i = 0; i < z; i++)
{
update(sc[i].y1, sc[i].y2, sc[i].f, 1);
if (i) sum += li * (sc[i].x - sc[i - 1].x);
li = t[1].n1 + t[1].n2;
}
printf("Total explored area: %.2lf\n\n", sum);
}
}
树状数组(下标必须从1开始):
一维:
void add(int i, int val)//单点增加val
{
for (; i < mm; i += i&-i)
c[i] += val;
}
int sum(int i)//1~i的元素和
{
int s = 0;
for (; i > 0; i -= i&-i)
s += c[i];
return s;
}
二维:
int sum(int x, int y)//矩阵(1,1)到(x,y)的元素和
{
int s = 0;
for (int i = x; i > 0; i -= i&-i)
for (int j = y; j > 0; j -= j&-j)
s += c[i][j];
return s;
}
void add(int x, int y, int val)//矩阵(1,1)到(x,y)每个元素+val
{
for (int i = x; i <= n; i += i&-i)
for (int j = y; j <= n; j += j&-j)
c[i][j] += val;
}
二维树状数组+坐标旋转+离散化:
#include
using namespace std;
#define mm 80005
int n, m, w, e, h[mm << 5], ss[mm << 5], pp[mm], xx[mm], yy[mm], zz[mm];
void ha(int x, int y)//离散化
{
//对与每个位运算涉及的点都要离散化
for (int i = x; i <= w; i += i & -i)
for (int j = y; j <= w; j += j & -j)
h[e++] = i * w + j;//转化至一维
}
void add(int x, int y, int d)
{
for (int i = x; i <= w; i += i & -i)
for (int j = y; j <= w; j += j & -j)
ss[lower_bound(h + 1, h + e, i * w + j) - h] += d;
}
int sum(int x, int y)
{
int s = 0, pos;
for (int i = x; i; i -= i & -i)
for (int j = y; j; j -= j & -j)
{
pos = lower_bound(h + 1, h + e, i * w + j) - h;
if (h[pos] == i * w + j)
s += ss[pos];
}
return s;
}
int main()
{
int a, b, c, d, x, y;
while (scanf("%d", &n) && n)
{
e = 1, w = n << 1;
scanf("%d", &m);
memset(ss, 0, sizeof(ss));
for (int i = 1; i <= m; i++)
{
scanf("%d%d%d%d", pp + i, xx + i, yy + i, zz + i);
x = xx[i] - yy[i] + n, y = xx[i] + yy[i];
if (pp[i] == 1) ha(x, y);
}
sort(h + 1, h + e);
e = unique(h + 1, h + e) - h;
for (int i = 1; i <= m; i++)
{
x = xx[i] - yy[i] + n, y = xx[i] + yy[i];
if (pp[i] == 1)
add(x, y, zz[i]);
else
{
a = max(1, x - zz[i]), b = max(1, y - zz[i]);
c = min(w, x + zz[i]), d = min(w, y + zz[i]);
printf("%d\n", sum(c, d) - sum(c, b - 1) -
sum(a - 1, d) + sum(a - 1, b - 1));
}
}
}
}
二维RMQ:
void initRMQ(int n, int m)
{
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
dp[i][j][0][0] = s[i][j];
for (int ii = 0; ii <= mm[n]; ii++)
for (int jj = 0; jj <= mm[m]; jj++)
if (ii + jj)
for (int i = 1; i + (1 << ii) - 1 <= n; i++)
for (int j = 1; j + (1 << jj) - 1 <= m; j++)
if (ii)
dp[i][j][ii][jj] = min(dp[i][j][ii - 1][jj], dp[i + (1 << (ii - 1))][j][ii - 1][jj]);
else
dp[i][j][ii][jj] = min(dp[i][j][ii][jj - 1], dp[i][j + (1 << (jj - 1))][ii][jj - 1]);
}
int rmq(int x1, int y1, int x2, int y2)
{
int k1 = mm[x2 - x1 + 1], k2 = mm[y2 - y1 + 1];
x2 = x2 - (1 << k1) + 1, y2 = y2 - (1 << k2) + 1;
return min(min(dp[x1][y1][k1][k2], dp[x1][y2][k1][k2]), min(dp[x2][y1][k1][k2], dp[x2][y2][k1][k2]));
}
mm[0] = -1;
for (int i = 1; i <= 500; i++)//mm[i]=log2(i)
mm[i] = ((i&(i - 1)) == 0) ? mm[i - 1] + 1 : mm[i - 1];
优先队列:(每次出队最小的 重载<符号):
#include
using namespace std;
#define maxn 150005
struct node
{
char name[205];
int v, tp;
friend bool operator <(node a, node b)
{
if (a.v == b.v) return a.tp > b.tp;
return a.v < b.v;
}
};
node fri[maxn];
struct node1
{
int t, r;
} ask[maxn];
bool cmp(node1 a, node1 b)
{
return a.t < b.t;
}
int put[maxn], wen[105];
int main()
{
int k, m, p, i, j, ok, now;
while (~scanf("%d%d%d", &k, &m, &p))
{
for (i = 0; i < k; i++)
scanf("%s%d", fri[i].name, &fri[i].v), fri[i].tp = i;
memset(ask, 0, sizeof(ask));
for (i = 0; i < m; i++)
scanf("%d%d", &ask[i].t, &ask[i].r);
for (i = 0; i < p; i++)
scanf("%d", wen + i);
priority_queue<node>q;
sort(ask, ask + m, cmp);
now = ok = 0;
for (i = 1; i <= k; i++)
{
q.push(fri[i - 1]);
if (i == ask[now].t)
{
for (j = 1; j <= ask[now].r && !q.empty(); j++)
put[ok++] = q.top().tp, q.pop();
now++;
}
}
while (!q.empty())
put[ok++] = q.top().tp, q.pop();
for (i = 0; i < p; i++)
printf("%s%c", fri[put[wen[i] - 1]].name, i == p - 1 ? '\n' : ' ');
}
}
动态规划
数位dp:
int dfs(int pos, int pre, int lim)
{
if (!pos) return pre == 2;
if (!lim&&dp[pos][pre] != -1) return dp[pos][pre];
int i, n, up = lim ? a[pos] : 9, sum = 0;
for (i = 0; i <= up; i++)
{
if ((pre == 1 && i == 9) || pre == 2) n = 2;
else if (i == 4) n = 1;
else n = 0;
sum += dfs(pos - 1, n, lim&&i == up);
}
if (!lim) dp[pos][pre] = sum;
return sum;
}
int solve(int x)
{
int pos = 1;
while (x)
a[pos++] = x % 10, x /= 10;
return dfs(pos - 1, 0, 1);
}
最长不减(递增或相等)序列 O(nlogn)
while (n--&&scanf("%d", &x))//v=0,a[0]=-inf;
if (x > a[v]) a[++v] = x;
else a[upper_bound(a, a + v, x) - a] = x;
字符串
线性Kmp匹配:
int main()
{
while (~scanf("%s%s", a, b))
{//b中有几个a(可重叠)
memset(ne, 0, sizeof(ne));
x = strlen(a), y = strlen(b);
j = 0;
for (i = 1; i < x; i++)//O(x)
{
while (j > 0 && a[i] != a[j])j = ne[j];
if (a[i] == a[j])j++;
ne[i + 1] = j;//ne数组下标从1到x ne[i]表示前i个字符中最长公共缀长度
}
j = 0, z = 0;
for (i = 0; i < y; i++)//O(y)
{
while (j > 0 && b[i] != a[j]) j = ne[j];
if (b[i] == a[j]) j++;
if (j == x) z++, j = ne[j];
}
printf("%d\n", z);
}
}
博弈论
胜负博弈基础:
所有终结状态为必败点(比如五子棋a5连珠后轮到b走,而5连珠为终结状态,所以b败)。
所有1步操作能进入必败点的点为必胜点(比如该a走时a有4连珠,a只要走出5连珠就进入了必败点,此时a必胜)。
某点的所有操作都走向必胜点,则该点为必败点。
胜负条件和取石子相反,先取完的输时。用必胜点的方法可以发现规律。特判所有堆都是1的情况就好。
博弈论Sg函数打表:
//f[N]:可改变当前状态的方式,N为方式的种类,f[N]要在getSG之前先预处理
//SG[]:0~n的SG函数值
//S[]:为x后继状态的集合
int f[] = { 1,3,7,9 }, SG[1005], S[1005];
void getSG(int n)
{
int i, j;
memset(SG, 0, sizeof(SG));
//因为SG[0]始终等于0,所以i从1开始
for (i = 1; i <= n; i++)
{
//每一次都要将上一状态 的 后继集合 重置
memset(S, 0, sizeof(S));
for (j = 0; f[j] <= i && j <= 3; j++)
S[SG[i - f[j]]] = 1; //将后继状态的SG函数值进行标记
for (j = 0;; j++)
if (!S[j])
{ //查询当前后继状态SG值中最小的非零值
SG[i] = j;
break;
}
}
}
int main()
{
int n, m, k;
getSG(1000);
while (~scanf("%d%d%d", &n, &m, &k))
puts(SG[n] ^ SG[m] ^ SG[k] ? "win" : "lose");
return 0;
}
STL
upper_bound:返回的是被查序列中第一个大于查找值的指针
lower_bound:返回的是被查序列中第一个大于等于查找值的指针
next_permutation(p,p+5),求[p,p+5)中元素的字典序的下一个排列,改变[p,p+5)中的元素。prev_permutation()同理,存在下(上)一个排列则返回1,否则返回0。(注意这个单次复杂度是O(n)的,求n次或者说求长度为n的串的全排列就是O(n^2)的,所以需要枚举比较大的数用dfs(比如遍历Cnk),dfs求Cnk时一个技巧是k>n/2则枚举另一半(k=n-k))
Map,set存元素是有序的,遍历是从大到小遍历(map<int,int>::iterator it;)
字符串用char*做map的一种方法:(需要把作为索引的所有字符串都存下来)
struct cmp
{
bool operator()(char *a, char *b) const
{
return strcmp(a, b) < 0;
}
};
map<char *, char*, cmp>d;
其他
std::ios::sync_with_stdio(false);
加速cin,cout的输入输出,但不能再用stdio头文件的函数(scanf, printf)。
10m个数,win10下,Min,读整数(字符串):
scanf:3.5s(2.5s)
无优化cin:12.7s(10s)
优化cin: 8s(3.6s)