• HDU4467 Graph 类似于分段函数的思想的解题技巧


    题意:给定N个点,M条边,每个点为0或者为1,每条边有一个权值。接下来有Q组操作,要么翻转某个点的标号,要么询问某组边的权值一共为多少,总共有三种类型的边:端点分别为(0, 0), (0, 1), (1, 1)。

    解法:这题的一个蛮力的解法就是记录好每一点的标号,然后O(1)的时间修改编号,对于每次查询就遍历所有的边进行询问。这样的话时间复杂度就是O(q*m)了,显然无法接受。

    换个好点的,我们首先通过原始数据在输入的时候处理一下,保留好每条边所属的类型。那么在没有点进行修改的话,O(1)时间进行输出。问题就是如果要进行修改的话,一个合理的方式就是修改当前节点所连的边的属性,因为其他于该节点无关的节点是没有影响的。那么修改一个点连接边的复杂度是多少了,好的话可能修改几条或者几十条就够了,但是极端数据是可能达到关于m的一个复杂度。因此这个方法不失为一种好方法,如果能够处理好一些极端数据的话。

    处理方法:把图中的点类似分段函数一样的处理,对于度小于的节点我们还是采用前面所说的暴力的办法维护。而对于度大于的节点就单独拿出来处理。我们维护好度大于节点与之相邻的0的节点和1的节点的边权值总和,这样修改度大点的话根据这两个信息就能够直接得到权值的修改了,比如当前点为0,修改为1,那么(0,0)组合就减少了维护的0的权值,并且把这一部分加到(1, 0)上面去;同理,减少(0, 1)的权值加到(1, 1)上面去。前面谈到维护好相邻的0,1节点边权总和。

    这里有一个证明:设度大于的节点数为x,那么有,可以得出

    因此我们只需要开设一个不大于700*700的矩阵存在这些度大点之间的贡献关系即可。而度小的点则直接通过边的关系来更新度大的点。因此该算法在查询的时候时间复杂度为O(1),修改的时间复杂度为O(),最终的复杂度为O(q)。

    代码如下:

    View Code
    #include <cstdlib>
    #include <cstring>
    #include <cstdio>
    #include <cmath>
    #include <map>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    
    typedef long long LL;
    int N, M, LIM;
    int cl[100005], deg[100005];
    LL ans[3]; // 其中ans[0],ans[1],ans[2]表示00、01、11的边的权值总和
    LL w[700][700]; // 用来记录特殊点之间的权值关系
    int tail; // 特殊点不会超过2*sqrt(M)个
    LL fee[700][2]; // 分别记录特殊点与0点相连的边权和,与1点相连的边权和
    int sp[100005]; // hash该节点是否为特殊点
    map<LL,LL>mp;
    
    struct Edge {
        int v, f, next;
    };
    Edge e[500005];
    int idx, head[100005];
    
    void insert(int a, int b, int f) {
        e[idx].v = b, e[idx].f = f;
        e[idx].next = head[a];
        head[a] = idx++;
    }
    
    void init() {
        for (int i = 1; i <= N; ++i) {
            if (deg[i] >= LIM) {
                sp[i] = tail++;
            } // 将度大的点离散化到一个较小的区间 
        }
        map<LL,LL>::iterator it;
        for (it = mp.begin(); it != mp.end(); ++it) {
            int u = it->first / N + 1, v = it->first % N + 1;
            if (~sp[u] && ~sp[v]) { // 如果两个点都是特殊点
                w[sp[u]][sp[v]] += it->second;
                w[sp[v]][sp[u]] += it->second;
                fee[sp[u]][cl[v]] += it->second;
                fee[sp[v]][cl[u]] += it->second;
            } else if (!(~sp[u]) && !(~sp[v])) { // 如果两个点都不是特殊点 
                insert(u, v, it->second); // 有度小的点就需要构边等着以后来暴力了 
                insert(v, u, it->second);
            } else if (~sp[u]) {
                fee[sp[u]][cl[v]] += it->second;
                insert(v, u, it->second);
            } else {
                fee[sp[v]][cl[u]] += it->second;
                insert(u, v, it->second);
            }
        }
    }
    
    void change(int x) { // 修改过程 
        if (~sp[x]) { // 如果x是特殊的点
            if (cl[x] == 1) {
                ans[1] -= fee[sp[x]][0], ans[0] += fee[sp[x]][0];
                ans[2] -= fee[sp[x]][1], ans[1] += fee[sp[x]][1];
                for (int i = 0; i < tail; ++i) {
                    fee[i][1] -= w[sp[x]][i], fee[i][0] += w[sp[x]][i];
                }
            } else {
                ans[0] -= fee[sp[x]][0], ans[1] += fee[sp[x]][0];
                ans[1] -= fee[sp[x]][1], ans[2] += fee[sp[x]][1];
                for (int i = 0; i < tail; ++i) {
                    fee[i][0] -= w[sp[x]][i], fee[i][1] += w[sp[x]][i];
                }
            }
        } else {
            for (int i = head[x]; ~i; i = e[i].next) {
                if (cl[x] == 1) {
                    ans[cl[e[i].v]+1] -= e[i].f, ans[cl[e[i].v]] += e[i].f;
                    if (~sp[e[i].v]) { // 如果连接的点是特殊点
                        fee[sp[e[i].v]][1] -= e[i].f, fee[sp[e[i].v]][0] += e[i].f;
                    }
                } else {
                    ans[cl[e[i].v]] -= e[i].f, ans[cl[e[i].v]+1] += e[i].f;
                    if (~sp[e[i].v]) {
                        fee[sp[e[i].v]][0] -= e[i].f, fee[sp[e[i].v]][1] += e[i].f;    
                    }
                }
            }
        }
        cl[x] ^= 1;
    }
    
    int main() {
        int a, b, c, ca = 0;
        while (scanf("%d %d", &N, &M) != EOF) {
            LIM = (int)sqrt(1.0*M); // LIM就是这样的对度数限制的阀值 
            tail = idx = 0;
            mp.clear();
            memset(w, 0, sizeof (w));
            memset(head, 0xff, sizeof (head));
            memset(sp, 0xff, sizeof (sp));
            memset(deg, 0, sizeof (deg));
            memset(ans, 0, sizeof (ans));
            memset(fee, 0, sizeof (fee));
            for (int i = 1; i <= N; ++i) {
                scanf("%d", &cl[i]); // 获取每个节点的原始颜色 
            }
            for (int i = 1; i <= M; ++i) {
                scanf("%d %d %d", &a, &b, &c);
                if (a > b) swap(a, b); // 始终保持a < b,这样才能把所有相同的边合并
                mp[1LL*(a-1)*N+b-1] += c; // 该map用于合并一些边
                ans[cl[a]+cl[b]] += c;
                ++deg[a], ++deg[b];
            }
            init();
            int Q;
            char op[10];
            scanf("%d", &Q);
            printf("Case %d:\n", ++ca);
            while (Q--) {
                scanf("%s", op);
                if (op[0] == 'A') {
                    scanf("%d %d", &a, &b);
                    // 查询是愉悦的 
                    printf("%I64d\n", ans[a+b]);
                } else {
                    scanf("%d", &a);
                    change(a); // 修改是痛苦的 
                }
            }
        }
        return 0;
    }
  • 相关阅读:
    基于Lucene/XML的站内全文检索解决方案
    内容管理系统(CMS)的设计和选型
    Lucene入门与使用[转]
    为自己的系统搞个全文搜索 参考值:2 (转)
    C# 时间函数
    Lucene倒排索引原理(转)
    什么是内容管理系统CMS?
    网络测试常用命令
    C#与C的区别
    人生致命的八个经典问题
  • 原文地址:https://www.cnblogs.com/Lyush/p/3055525.html
Copyright © 2020-2023  润新知