repost 懊悔自己没能在本科和研究生期间邂逅ACM

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)

&

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值