• 板子


    模板整理、部分知识点文章上传至 Github

    二分(返回第一个等于x的元素的下标)

    int found(int a[],int left,int right,int x) {
    	while (left < right) {
    		int mid = (right + left) >> 1;
    		if (a[mid] < x) left = mid + 1;
    		else right = mid;
    	}
    	return left;
    }
    

    并查集

    int f[maxn];
    void init() { for (int i = 1; i <= n; ++i)f[i] = i; }
    int find(int x) { return f[x] == x ? x : f[x] = find(f[x]); }
    void merge(int x, int y) {
        x = find(x), y = find(y);
        if (x != y)  f[y] = x;
    }
    ...
    

    链式前向星

    struct Edge {
    	int next, to;
    	LL dis;
    }edges[maxn];
    
    void add_edge(int from, int to, LL dis) {
    	num++;
    	edges[num].next = head[from];
    	edges[num].to = to;
    	edges[num].dis = dis;
    	head[from] = num;
    }
    
    for (int i = head[u]; i != 0; i = edges[i].next) {
      ...
    }
    

    邻接矩阵

    struct Edge {
    	int from, to, dist;
    	//边的起点,终点,长度
    	Edge(int u,int v,int d):from(u),to(v),dist(d){}
    	//构造函数,用于边的初始化
    };
    
    struct HeapNode {
    	int d, u;//将结点的d值与结点捆绑在一起形成结构体,当然也可以用pair<int,int>代替
    	bool operator < (const HeapNode& rhs) const {
    		return d > rhs.d;
    		//当d>rhs.d为真时,优先级this<rhs.d成立,即d值小的优先级更大
    	}
    };
    
    void init(int n) {//初始化
      num=0;//边在vector中的编号
      for (int i = 0; i < n; i++) G[i].clear();
      edges.clear();
      //个人习惯,为了计数都能从1开始,先塞一个无关紧要的元素
      edges.push_back(Edge(0, 0, 0));
      //但G[i]计数还是从0开始
    }
    
    void AddEdge(int from, int to, int dist) {
      edges.push_back(Edge(from, to, dist));
      //调用Edge结构体中的构造函数,生成一条边并加入到Edge中
      G[from].push_back(++num);
    }
    

    拓扑排序

    int n, m, head[N], tot, deg[N], cnt;
    vector<int>a;
    struct node {
        int v, next;
    }e[N << 1];
    
    void add(int u, int v) {
        e[++tot].v = v;
        e[tot].next = head[u];
        head[u] = tot;
        deg[v]++;//入度
    }
    
    void topsort() {
        queue<int>q;
        for (int i = 1; i <= n; ++i)
            if (deg[i] == 0)q.push(i);
        while (q.size()) {
            int x = q.front(); q.pop();
            a[++cnt] = x;
            for (int i = head[x]; i; i = e[i].next) {
                int y = e[i].v;
                if (--deg[y] == 0)q.push(y);
            }
        }
    }
    
    int main() {
        cin >> n >> m;//点数、边数
        for (int i = 1; i <= m; ++i) {
            int x, y;//有向边
            cin >> x >> y;
            add(x, y);
        }
        topsort();
        for (int i = 1; i <= a.size(); ++i)
            cout << a[i] << " ";
        cout << endl;
    }
    

    最短路(Floyd)

    //初始化
    for (int i = 0;i < maxn ; i++) for (int j = 0;j< maxn ; j++) 
    		d[i][j] = (i == j) ? 0 : INF;
    
    for (int k = 0; k < n; k++)
      for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
          d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
    
    //传递闭包
    for (int k = 0; k < n; k++)
      for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            d[i][j] |= d[i][k] & d[k][j];
    

    最短路(SPFA)

    int cnt[maxn];
    bool inq[maxn];
    int d[maxn];
    
    bool spfa(int s) {
        queue<int>Q;
        memset(inq, 0, sizeof(inq));//标记结点是否在队列中
        memset(cnt, 0, sizeof(cnt));
        for (int i = 0; i < n; i++) d[i] = INF;
        d[s] = 0; inq[s] = true;//标记结点s已在队列中
        Q.push(s);//入队
        while (!Q.empty()) {
            int u = Q.front(); Q.pop();
            inq[u] = false;//标记已不在队列
            for (int i = 0; i < G[u].size(); i++) {
                Edge& e = Edges[G[u][i]];//遍历以结点u为起点的有向边
                if (d[u]<INF && d[u] + e.dist<d[e.to]) {
                    d[e.to] = d[u] + e.dist;//松弛
                    p[e.to] = G[u][i];//记录父节点
                    if (!inq[e.to]) {//只要不在队列中就可以入队
                        Q.push(e.to);
                        inq[e.to] = true;
                        if (++cnt[e.to] > n) return false;
                        //如果某个点迭代了超过n次,说明存在可以无限缩短的最短路,即负环
                    }
                }
            }
        }
        return true;
    }
    

    最短路(朴素Dijkstra)

    int dis[maxn];
    bool vis[maxn];
    
    void dijkstra()
    {
        /*
        memset(vis,false);
        memset(dis,inf);
        */
    	for (int i = 1; i <= n; i++) dis[i] = inf, vis[i] = 0, a[i][i] = 0; //初始化各数组
    	dis[s] = 0; vis[s] =true;//s作为起点
    	for (int i = 1; i < n; i++) //重复n-1次
    	{
    		int x = -1;//寻找的最小边点
    		for (int j = 1; j <= n; j++)
    			if (!vis[j] && (x == 0 || dis[j] < dis[x])) //寻找未访问过且离起点最近的点
    				x = j;
            if(x == -1) break;
    		vis[x] = 1;
    		for (int y = 1; y <= n; y++)  //第一轮dis[y]中由起点可到达的点得到更新
    			dis[y] = min(dis[y], dis[x] + a[x][y])
    	}
    }
    

    最短路(堆优化Dijkstra)

    #include<iostream>
    #include<cstdio>
    #include<queue>
    using namespace std;
    const int inf = 2147483647;
    const int manx = 1e4 + 5; //与n相对,对应顶点的个数
    const int mamx = 5e5 + 5; //与m相对,对应边的个数
    priority_queue< pair<int, int> >q;
    struct node {
        int next, v, w;
    }edge[mamx];  //边取mamx,其余取manx
    bool vis[manx];  //这里的标记数组与spfa的vis数组含义不同,这里标记是否入过队列
    int head[manx], d[manx];
    int k = 0;//tot 总边数
    int n, m, s, e; //s作为起点,e作为终点
    void add(int u, int v, int w) {//邻接表存储
        edge[++k].next = head[u];
        edge[k].v = v;
        edge[k].w = w;
        head[u] = k;
    }
    void dijkstra()
    {
        for (int i = 1; i <= n; i++) //初始化vis d 数组
            d[i] = inf, vis[i] = 0;
        d[s] = 0; //s作为起点
        q.push(make_pair(0, s));
        while (q.size()) {
            int x = q.top().second; //取出队头
            q.pop();
            if (vis[x]) continue; //如果点x访问过,跳过,访问下一个队头
            vis[x] = 1; //访问x做标记
            for (int i = head[x]; i; i = edge[i].next) {
                int v = edge[i].v, w = edge[i].w;
                if (d[v] > d[x] + w) { //松弛操作,更新距离
                    d[v] = d[x] + w;
                    q.push(make_pair(-d[v], v)); //把更新的距离和点入队,这里距离取负变成小根堆
                }
            }
        }
    }
    int main()
    {
        cin >> n >> m >> s;
        for (int i = 1; i <= m; i++) {
            int u, v, w;
            scanf("%d%d%d", &u, &v, &w);
            add(u, v, w);
            //无向图  add(v,u,w);
        }
        s = 1;
        dijkstra();
        for (int i = 1; i <= n; i++)
            printf("%d ", d[i]);
        return 0;
    }
    

    最小生成树(Kruskal)

    #include<bits/stdc++.h>
    using namespace std;
    struct rec { int x, y, z; }edge[500010];
    int fa[100010], n, m, ans;
    
    bool cmp(rec a, rec b) {
    	return a.z < b.z;
    }
    int find(int x) { return fa[x] == x ? x : fa[x] = find(fa[x]); }
    
    int main() {
    	//并查集初始化
    	for (int i = 1; i <= n; ++i)fa[i] = i;
    	cin >> n >> m;
    	for (int i = 1; i <= n; ++i)
    		cin >> edge[i].x >> edge[i].y >> edge[i].z;
    	//按照边权排序
    	sort(edge + 1, edge + 1 + n, cmp);
    	//求最小生成树
    	for (int i = 1; i <= m; ++i) {
    		int x = find(edge[i].x);
    		int y = find(edge[i].y);
    		if (x == y) continue;
    		fa[x] = y; //如果不在同一个集合,总权值更新,并且集合和并
    		ans += edge[i].z;
    	}
    	cout << ans << endl;
    }
    

    最小生成树(Prim)

    const int inf=2147483647;
    int a[manx][manx], d[manx], n, m, ans;
    bool vis[manx];
    void prim(){
    	for(int i=1;i<=n;i++) d[i]=inf,vis[i]=0,a[i][i]=0; //初始化各数组
    	d[1]=0; //1作为起点
    	for(int i=1;i<n;i++) //重复n-1次
    	{
    		int x=0;
    		for(int j=1;j<=n;j++)
    			if( !vis[j] && (x==0||d[j]<d[x]) ) //寻找未访问过且离集合S最近的点
    				x=j;
    		vis[x]=1;
    		for(int y=1;y<=n;y++)  //第一轮d[y]中由起点可到达的点得到更新
    			d[y]=min( d[y ], a[x][y]) //这里与Dij不同,因为Dij求的是两点间的距离,而这里是点到集合距离
    		}
    }
    

    强连通分量(korasaju 算法)

    int V;                     // 顶点数
    vector<int> G[maxn];       // 图的邻接表表示
    vector<int> rG[maxn];      // 反向图
    vector<int> vs;            // 后序遍历顺序的顶点列表
    bool book[maxn];           // 访问标记
    int cmp[maxn];             // 所属强连通分量的拓补序
    void dfs(const int& v) {
    	book[v] = true;
    	for (int i = 0; i < G[v].size(); ++i)
    		if (!book[G[v][i]])dfs(G[v][i]);
    	vs.push_back(v);
    }
    void rdfs(const int& v, const int& k) {
    	book[v] = true; cmp[v] = k;
    	for (int i = 0; i < rG[v].size(); ++i)
    		if (!book[rG[v][i]])rdfs(rG[v][i], k);
    }
    //korasaju 算法核心:双重DFS标记
    int scc() {
    	ms(book, false); vs.clear();
    	for (int v = 0; v < V; ++v)
    		if (!book[v])dfs(v);
    	ms(book, false); int k = 0;
    	for (int i = vs.size() - 1; i >= 0; --i)
    		if (!book[vs[i]])rdfs(vs[i], k++);
    	return k;//k为连通分量的个数
    }
    

    强连通分量(Tarjan 算法)

    
    

    线段树

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int inf = 0x3f3f3f3f;
    const int maxn = 5e5 + 10;
    struct Node {
        int l, r, val;
        int sum;
        int lmax, rmax, dat;
        //不能简单的对比对比左右子区间的dat和值(区间最大和值)来更新本节点的区间最大和值,还要对比右子树的rmax+左子树的lmax的和值
    }node[maxn << 2];
    int n, m;
    int a[maxn];
    
    void pushdown(int rt) {
        node[rt].sum = node[rt << 1].sum + node[rt << 1 | 1].sum;
        node[rt].lmax = max(node[rt << 1].lmax, node[rt << 1].sum + node[rt << 1 | 1].lmax);
        node[rt].rmax = max(node[rt << 1 | 1].rmax, node[rt << 1 | 1].sum + node[rt << 1].rmax);
        node[rt].dat = max(node[rt << 1].dat, node[rt << 1 | 1].dat);
        node[rt].dat = max(node[rt].dat, node[rt << 1].rmax + node[rt << 1 | 1].lmax);
    }
    
    //构建线段树,完成区间和,区间max,dat的更新
    void build(int rt, int l, int r) {
        node[rt].l = l, node[rt].r = r;
        if (l == r) {
            node[rt].val = node[rt].lmax = node[rt].rmax = node[rt].dat = a[l];
            node[rt].sum = a[l];
            return;
        }
        int mid = (l + r) >> 1;
        build(rt << 1, l, mid); build(rt << 1 | 1, mid + 1, r);
       	pushdown(rt);
        return;
    }
    
    Node query(int rt, int l, int r) {
        if (l <= node[rt].l && node[rt].r <= r) {
            return node[rt];
        }
        Node t1, t2, ans;
        t1.dat = t1.sum = t1.lmax = t1.rmax = t2.dat = t2.sum = t2.lmax = t2.rmax = -inf;
        int mid = (node[rt].l + node[rt].r) >> 1;
    
        if (l <= mid) t1 = query(rt << 1, l, r), ans.sum += t1.sum;
        if (r > mid) t2 = query(rt << 1 | 1, l, r), ans.sum += t2.sum;
    
        ans.dat = max(max(t1.dat, t2.dat), t1.rmax + t2.lmax);
        ans.lmax = max(t1.lmax, t1.sum + t2.lmax);
        ans.rmax = max(t2.rmax, t2.sum + t1.rmax);
        if (l > mid) ans.lmax = max(ans.lmax, t2.lmax);
        if (r <= mid) ans.rmax = max(ans.rmax, t1.rmax);
        return ans;
    }
    
    void update(int rt, int pos, int val) {
        if (node[rt].l == pos && node[rt].l == node[rt].r) {
            node[rt].sum = node[rt].val = val;
            node[rt].lmax = node[rt].rmax = node[rt].dat = val;
            return;
        }
        int mid = (node[rt].l + node[rt].r) / 2;
        //cout<<pos<<" "<<rt<<" "<<mid<<endl;
        if (pos <= mid) update(rt << 1, pos, val);
        else update(rt << 1 | 1, pos, val);
        pushdown(rt);
    }
    
    int main() {
        //freopen("in.txt","r",stdin);
        ios::sync_with_stdio(false);
        cin >> n >> m;
        for (int i = 1; i <= n; i++) cin >> a[i];
        build(1, 1, n);
        int op, l, r;
        while (m--) {
            cin >> op >> l >> r;
            if (op == 1) {
                if (l > r) swap(l, r);
                cout << query(1, l, r).dat << endl;//根据需要输出不同的
            }
            else {
                update(1, l, r);
            }
        }
        return 0;
    }
    

    树状数组

    int n;
    int a[1005],tree[1005]; //原数组和树状数组
    
    int lowbit(int x) return x&(-x); //取出x的最低位1
    
    //修改
    void updata(int i,int p){    //在i位置加上p
        while(i <= n){
            tree[i] += p;//更新受影响的tree[i]
            i += lowbit(i);
        }
    }
    
    //查询
    int getsum(int i){//求区间1到i的和
        int res = 0;
        while(i > 0){
            res += tree[i];
            i -= lowbit(i);
        }
        return res;
    }
    

    高级操作:

    求逆序对

    二维树状数组:①单点修改,区间查询 ②区间修改,区间查询

    二分图:匈牙利算法

    int M, N;            //M, N分别表示左、右侧集合的元素数量
    int Map[MAXM][MAXN]; //邻接矩阵存图
    int p[MAXN];         //记录当前右侧元素所对应的左侧元素
    bool vis[MAXN];      //记录右侧元素是否已被访问过
    bool match(int i)
    {
        for (int j = 1; j <= N; ++j)
            if (Map[i][j] && !vis[j]) //有边且未访问
            {
                vis[j] = true;                 //记录状态为访问过
                if (p[j] == 0 || match(p[j])) //如果暂无匹配,或者原来匹配的左侧元素可以找到新的匹配
                {
                    p[j] = i;    //当前左侧元素成为当前右侧元素的新匹配
                    return true; //返回匹配成功
                }
            }
        return false; //循环结束,仍未找到匹配,返回匹配失败
    }
    int Hungarian()
    {
        int cnt = 0;
        for (int i = 1; i <= M; ++i)
        {
            memset(vis, 0, sizeof(vis)); //重置vis数组
            if (match(i))
                cnt++;
        }
        return cnt;
    }
    

    数位DP

    LL dp[20][][];
    //维数视情况决定,比如在【不要62】中,dp状态要区分有没有6
    int a[20];
    //给出数字范围在10^18以内可用,位数更多就扩大一下
    
    LL dfs(int pos,bool lead,bool limit){
    //pos当前枚举的位置,lead有无前导零,limit有无最高位限制
        LL ans = 0;
        if (pos == -1) return 1;
        if (!limit && !lead && dp[pos] != -1) return dp[pos];
      	//无前导零和最高位限制才能使用已储存的状态
        int end = limit ? a[pos] : 9;
        for (int i = 0; i <= end; i++) {
            if (不合法的情况(除0以外)) continue;
            if (!lead && (!i)) continue;
            //当前位为是零但不是前导零,也不合法,跳过(如果0不合法的话)
            ans += dfs(pos - 1, lead&&(!i), limit && i == end);
            //当前位是合法情况,继续搜
        }
        if (!limit&&!lead)  dp[pos] = ans;
        //没有前导零也没有最高位限制,说明本次统计结果是通用的
        return ans;
    }
    
    LL part(LL x){
        int len = 0; LL k = x;
        while (k) { a[len++] = k % 10; k /= 10; }
        memset(dp, -1, sizeof (dp));
        return dfs(len - 1, true, true);
      	//如果涉及到前导零,一般刚开始时都是默认有的
    }
    
    //注意l=0的时候不能用,要特判
    LL result(LL l , LL r) {
    	  return part(r) - part(l-1);
    }
    

    LCA(倍增)

    int num = 0;
    int head[maxn], depth[maxn], lg[maxn], fathers[maxn][20];
    
    struct Edge {
    	int next, to;
    }edges[2 * maxn];
    
    void add_edge(int u, int v) {
    	num++;
    	edges[num].to = v;
    	edges[num].next = head[u];
    	head[u] = num;
    }
    
    void dfs(int u, int u_father) {
    	depth[u] = depth[u_father] + 1;
    	fathers[u][0] = u_father;
    	for (int i = 1; (1 << i) <= depth[u]; i++)
    		fathers[u][i] = fathers[fathers[u][i - 1]][i - 1];
    	for (int i = head[u]; i; i = edges[i].next) {
    		if (edges[i].to != u_father) dfs(edges[i].to, u);
    	}
    }
    
    int LCA(int u, int v) {
    	if (depth[u] < depth[v]) swap(u, v);
    	while (depth[u] > depth[v])
    		u = fathers[u][lg[depth[u] - depth[v]] - 1];
    	if (u == v) return u;
    	for (int k = lg[depth[u]] - 1; k >= 0; k--) {
    		if (fathers[u][k] != fathers[v][k]) {
    			u = fathers[u][k];
    			v = fathers[v][k];
    		}
    	}
    	return fathers[u][0];
    }
    
    int main(){
      dfs(1, 0);
      //常数优化
      for (int i = 1; i <= n; i++) {
        lg[i] = lg[i - 1] + (1 << lg[i - 1] == i);
      }
    }
    

    LCA(树剖)

    int num = 0;
    int head[maxn], depth[maxn], siz[maxn], dad[maxn], top[maxn];
    //top[i]:节点i所在重链的链头
    
    struct Edge {
    	int next, to;
    }edges[2 * maxn];
    
    void add_edge(int u, int v) {
    	num++;
    	edges[num].to = v;
    	edges[num].next = head[u];
    	head[u] = num;
    }
    
    void getDepth(int now) {
    	siz[now] = 1;
    	depth[now] = depth[dad[now]] + 1;
    	for (int i = head[now]; i != 0; i = edges[i].next) {
    		int v = edges[i].to;
    		if (v != dad[now]) {
    			dad[v] = now;
    			getDepth(v);
    			siz[now] += siz[v];
    		}
    	}
    }
    
    void getLink(int x) {
    	int t = 0;
    	if (!top[x]) top[x] = x;
    	for (int i = head[x]; i != 0; i = edges[i].next) {
    		int v = edges[i].to;
    		if (v != dad[x] && siz[v] > siz[t]) t = v;
    	}
    	if (t) {
    		top[t] = top[x];
    		getLink(t);
    	}
    	for (int i = head[x]; i != 0; i = edges[i].next) {
    		int v = edges[i].to;
    		if (v != dad[x] && t != v) getLink(v);
    	}
    }
    
    int LCA(int u, int v) {
    	while (top[u] != top[v]) {
    		if (depth[top[u]] < depth[top[v]]) swap(u, v);
    		u = dad[top[u]];
    	}
    	//直到u和v位于同一条重链上
    	return (depth[u] < depth[v]) ? u : v;
    	//深度更小的那一个就是公共祖先
    }
    
    
    int main()
    {
    	//	ios::sync_with_stdio(false);
    	//	int t; cin >> t; while (t--) {
    	int n, m;
    	scanf("%d", &n);
    	for (int i = 1; i <= n - 1; i++) {
    		int u, v;
    		scanf("%d%d", &u, &v);
    		add_edge(u, v);
    		add_edge(v, u);
    	}
    	getDepth(1);
    	getLink(1);
    	return 0;
    }
    

    康托展开&逆康托展开

    const int FAC[] = { 1,1,2,6,24,120,720,5040,40320,362880,3628800 };
    
    int cantor(int* a) {//算出全排列对应的哈希值
        int x = 0;
        for (int i = 0; i < 9; i++) {
            int smaller = 0;
            for (int j = i + 1; j < 9; j++) {
                if (a[j] < a[i]) smaller++;
            }
            x += FAC[9 - i - 1] * smaller;
        }
        return x+1;
        //注意全排列数组a是从零开始的
    }
    
    
    void decantor(int x,int*ans) {//x哈希值,n数字个数,a算出的全排列
        x--;
        vector<int> v;
        for (int i = 1; i <= 9; i++) v.push_back(i);
        for (int i = 0; i < 9; i++) {
            int r;
            r = x / FAC[9 - i - 1];
            x = x % FAC[9 - i - 1];
            sort(v.begin(), v.end());
            ans[i] = v[r];
            v.erase(v.begin() + r);
        }
        //注意算出的全排列数组ans是从0开始的
    }
    
  • 相关阅读:
    ◆ C++中通过溢出覆盖虚函数指针列表执行代码
    关于在OnTimer中连续弹出对话框的讨论
    SetTimer
    Windows内核对象
    日志收缩
    暴力求值
    低级问题
    函数限制
    字符串找字段和表
    android错误提示说明汇总
  • 原文地址:https://www.cnblogs.com/RioTian/p/13377290.html
Copyright © 2020-2023  润新知