现在的位置: 首页 > 综合 > 正文

HDU-4467 Graph 类似于分段函数的思想的解题技巧

2012年10月03日 ⁄ 综合 ⁄ 共 3260字 ⁄ 字号 评论关闭

题意:给定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;
}

 

 

抱歉!评论已关闭.