BZOJ3052——糖果公园

0、题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=3052

1、题目大意:给定一颗n个点的无根树,每个点有一个颜色,要进行q次操作,有两种操作,颜色总数是m。

a) Query操作,给定起始点和终点,对于这条路径,从起始点出发,对于沿途的点,如果这个点的颜色j是第i次出现,那么对于这个询问的答案的贡献是vi?wj。

b) Change操作,每次修改一个节点的颜色。

——截图来自uoj

2、解题思路:

a)这道题在BZOJ上的时间限制是200s,那么对于前3个点,我们可以每次查询在树上dfs,修改直接改颜色。时间复杂度O(n2),期望得分30。

b)对于第四个和第五个点,我们发现m相对较小而且给出的是一个链,还没有修改操作,那么我们可以将这个链看成一个序列,并对这个序列进行分块,每块的大小是,考虑莫队算法,我们将需要查询的区间排序,第一关键字按照左端点的块,第二关键字我们按照右端点的编号排序,每次我们进行暴力的转移,这样,时间复杂度是O(n??√),为什么呢?因为转移分两种,要么都在这个n??√的块中转移,转移n次,时间复杂度是O(n??√),要么是这个块中转移到另外一个块,每次转移是O(n),最多会有n??√次转移,时间复杂度也是O(n??√),于是总体的时间复杂度是O(n??√)。

i.说了这么多,也没个代码= =,莫队算法模板题:BZOJ2038 小Z的袜子

sort(q + 1, q + m + 1, cmp);   //对询问进行排序
for(int i = 1; i <= m; i ++){
        while(r < q[i].r) {
            //右端点向右转移,更新答案
        }
        while(r > q[i].r) {
            //右端点向左转移,更新答案
        }
         //实际上就是将r->q[i].r
        while(l < q[i].l) {
            //左端点向左转移,更新答案
        }
        while(l > q[i].l) {
            //左端点向右转移,更新答案
        }
 //实际上就是将l->q[i].l
        //计算这次询问的答案
    }

加上a)算法,期望得分50。

c)对于第六和第七个点,我们能看出和b)解法唯一区别就是给点的树不是链了,那么b)中的算法能不能在这里应用呢?序列能够分块,树能不能分块呢?如何分块复杂度才能有保证呢?

i.我们先来看《手把手教你树分块系列》的BZOJ1086 王室联邦。这个题如何解决呢?我们先来陈述一下做法,我们用DFS去分块,我们记录一个栈,当遍历完某个节点的所有子树后,我们将这个节点加入栈,每次遍历完一棵子树,以这个节点为祖先的栈里的元素个数已经超过了我们规定的块的大小B了,就把这些节点变成一个块,将这些元素从栈里弹出,最后将栈中的残留节点归结到最后一个块中。

ii.那么可以确定的是每棵子树对这个节点的贡献不过超过B,为什么呢?因为如果这个子树中的节点数已经超过B了,那么这棵子树本身就会成为一个块。既然我们确定了每棵子树对这个节点的贡献不会超过B,那么我们每遍历完一个子树就检查一次,所以每个块的范围在[B,2B]之间,那么为什么给定的范围强行是[B,3B]呢?因为最后剩余的还要加进最后一个块,这样构造我们可以发现每个块中的节点都是相邻的,这样在块中的两点间距离的上界就是n??√了

这样复杂度就有保证。

好了,我们会树分块了,莫队算法怎么在树上应用呢?其实是一样的,我们对于一个询问操作记录起始点和终点,每次l -> q[i].l,r -> q[i].r,排序依旧是那样的。

sort(q + 1, q + m + 1, cmp);   //对询问进行排序
for(int i = 1; i <= m; i ++){
     //什么?这怎么转移啊
    while(l != q[i].l) {
               //不断的向上跳
               //这样简单了啊
    }
    while(r != q[i].r) {
               //不断的向上跳
               //这样简单了啊
    }
    //算出这次询问的答案= =
}

时间复杂度

加上算法a)和算法b),期望得分70。

非常好= =,我们已经拿到70分了。

d) 同样,我们观察第八和第九个测试点,我们可以发现差别在于这里有修改操作了,好!我们如何处理修改操作呢?然后我们发现如果在b)算法中强行安上修改那么时间复杂度会变成O(n2),是不能AC这两个测试点的,那么怎么AC呢?考虑在原来的莫队中加上一维,表示修改的操作时间,如果加上一维,我们块的大小就要变成O(n23)了,排序是前两维按照块的编号排序,最后一维从小到大排序。

我们在转移的时候就变成的O(n23)了,于是转移的时间复杂度就是O(n?n23)了,具体证明参见b)算法的证明过程。那么我们来算一算第三维转移的时间复杂度,我们发现对于前两维,每一维都有个n13块,于是就有n23种情况,第三维的转移每次都是O(n)的,所以转移的时间复杂度就是O(n?n23)了,综上所述,总的时间复杂度是O(n?n23),能通过这两个测试点。

综合以上四个思路,我们可以拿到90分了,算法已经很优秀了。

e) 看到上面四个解法,我们思考一下最后这个点的解法,综合解法c)和解法d)

Vfleaking的解法,按照这个解法,我们发现最后我们只需要暴力的转移即可,我们 发现最后这个式子是我们想要的答案,于是我们将d)的解法转移到树上!我们就 可以拿到100分了!

附上本弱的代码

#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <algorithm>
using namespace std;
#define M 100100
#define LL long long

inline int read(){
    char ch = getchar(); int x = 0, f = 1;
    while(ch < ‘0‘ || ch > ‘9‘){
        if(ch == ‘-‘) f = -1;
        ch = getchar();
    }
    while(‘0‘ <= ch && ch <= ‘9‘){
        x = x * 10 + ch - ‘0‘;
        ch = getchar();
    }
    return x * f;
}

struct Edge{
    int u, v, next;
} G[M * 2];
int head[M], tot;
int first[M];
int n, m, q;
int v[M], w[M], col[M], cnt[M];
int blo[M];
int st[M], Top;
int height[M], ft[M][20];
int B, cnt_;
bool vis[M];
LL ansnow, ans[M];

struct node{
    int x, y, z;
} qr[M]; 

struct Node{
    int x, y, z, id;
    LL ans;
    inline void sw(){
        if(blo[x] > blo[y]) swap(x, y);
    }

    inline bool operator < (const Node& rhs) const{
        if(blo[x] != blo[rhs.x]) return blo[x] < blo[rhs.x];
        if(blo[y] != blo[rhs.y]) return blo[y] < blo[rhs.y];
        return z < rhs.z;
    }
} Q[M];

inline void add(int a, int b);
inline void update(int x);
inline void modify(int x, int y);
inline void moveto(int x, int y);
inline void dfs(int x, int fa, int h);
inline void init();
inline int lca(int a, int b);
inline void getans(int x, int y);

int main(){
    n = read(); m = read(); q = read();
    memset(head, -1, sizeof(head));
    for(int i = 1; i <= m; i ++) v[i] = read();
    for(int i = 1; i <= n; i ++) w[i] = read();
    for(int i = 1; i < n; i ++){
        int a = read(), b = read();
        add(a, b); add(b, a);
    }
    B = pow(n, 2.0/3.0);//确定块的大小
    for(int i = 1; i <= n; i ++) col[i] = read(), first[i] = col[i];
//我们用链表记录每个点修改前和修改后的真实颜色
    dfs(1, 0, 0); while(Top) blo[st[Top --]] = cnt_; init();
//对树进行分块
    int totqr = 0, totQ = 0;
    for(int i = 1; i <= q; i ++){
        int t = read();
        if(t == 0){
            qr[++ totqr].x = read();
            qr[totqr].y = first[qr[totqr].x];
            qr[totqr].z = first[qr[totqr].x] = read();
        }
        else{
            Q[++ totQ] = (Node){read(), read(), totQ, totqr};
            Q[totQ].sw(); //一个小小的常数优化
        }
    }
    sort(Q + 1, Q + totQ + 1);
    int tim = 1;
    while(tim <= Q[1].id){ //转移第三维
        modify(qr[tim].x, qr[tim].z);
        tim ++;
    }

    moveto(Q[1].x, Q[1].y);getans(1, lca(Q[1].x, Q[1].y));

    for(int i = 2; i <= totQ; i ++){
        while(tim <= Q[i].id) modify(qr[tim].x, qr[tim].z),tim ++;
        while(tim > Q[i].id + 1) modify(qr[tim - 1].x, qr[tim - 1].y), tim --;
//转移第三维
        moveto(Q[i - 1].x, Q[i].x); moveto(Q[i - 1].y, Q[i].y); getans(i, lca(Q[i].x, Q[i].y));
//xor一波 顺便求一下答案
    }
    for(int i = 1; i <= totQ; i ++) printf("%lld\n", ans[i]);
    return 0;
} 

inline void add(int a, int b){
    G[++ tot] = (Edge){a, b, head[a]};
    head[a] = tot;
}

inline void update(int x){
//将一个点从集合中删除
    if(vis[x]) ansnow -= (LL)w[cnt[col[x]] --] * v[col[x]];
    else ansnow += (LL)w[++ cnt[col[x]]] * v[col[x]];
    vis[x] ^= 1;
}

inline void modify(int x, int y){
//修改一个节点的颜色
    if(vis[x]) update(x), col[x] = y, update(x);
    else col[x] = y;
}

inline void moveto(int x, int y){
//转移,并不需要bfs之类的,两个点直接往上跳
    while(x != y){
        if(height[x] < height[y]) swap(x, y);
        update(x); x = ft[x][0];
    }
}

inline void dfs(int x, int fa, int h){
//树分块
    int bt = Top;
    height[x] = h;
    ft[x][0] = fa;
    int ot = x;
    for(int i = head[x]; i != -1; i = G[i].next) if(G[i].v != fa){
        dfs(G[i].v, x, h + 1);
        if(Top - bt >= B){
            cnt_ ++;
            while(Top != bt){
                blo[st[Top --]] = cnt_;
            }
        }
    }
    st[++ Top] = x;
}

inline void init(){
//预处理倍增数组
    for(int i = 1; i <= 18; i ++){
        for(int j = 1; j <= n; j ++){
            ft[j][i] = ft[ft[j][i - 1]][i - 1];
        }
    }
}

inline int lca(int a, int b){
//求出lca
    if(height[a] < height[b]) swap(a, b);
    int t = height[a] - height[b];
    for(int i = 18; i >= 0; i --){
        if(t & (1 << i)){
            a = ft[a][i];
        }
    }
    if(a == b) return a;
    for(int i = 18; i >= 0; i --){
        if(ft[a][i] != ft[b][i]){
            a = ft[a][i];
            b = ft[b][i];
        }
    }
    return ft[a][0];
}

inline void getans(int x, int y){
//获取答案,一定要将y这个点去掉,因为y这个点所象征的边不在路径上
    update(y);
    ans[Q[x].z] = ansnow;
    update(y);
}
时间: 2024-10-13 19:11:50

BZOJ3052——糖果公园的相关文章

bzoj-3052 糖果公园

题意: 给出一颗n个结点的树,每个结点上有一种糖果∈[1,m]: 一个人经过这个结点品尝糖果j获得的愉悦度为w[time[j]]*val[j]  (其中time[j]指j的品尝次数): 给出q次操作,操作有两种: 1:更改某结点的糖果种类: 2:查询某两个结点路径上的愉悦度总和: 题解: 250s的神题,orz各位神犇: 将树分块,每块n^2/3大小,分成n^1/3块: 用莫队算法统计路径上每种糖果的数量: 但是这里有了修改,不能用原来的方式排序了: 所以以左端点所在块为第一关键字,右端点所在块

[BZOJ3052][UOJ#58][WC2013]糖果公园

试题描述 Candyland 有一座糖果公园,公园里不仅有美丽的风景.好玩的游乐项目,还有许多免费糖果的发放点,这引来了许多贪吃的小朋友来糖果公园玩. 糖果公园的结构十分奇特,它由 n 个游览点构成,每个游览点都有一个糖果发放处,我们可以依次将游览点编号为 1 至 n.有 n?1 条双向道路连接着这些游览点,并且整个糖果公园都是连通的,即从任何一个游览点出发都可以通过这些道路到达公园里的所有其它游览点. 糖果公园所发放的糖果种类非常丰富,总共 m 种,它们的编号依次为 1 至 m.每一个糖果发放

【BZOJ-3052】糖果公园 树上带修莫队算法

3052: [wc2013]糖果公园 Time Limit: 200 Sec  Memory Limit: 512 MBSubmit: 883  Solved: 419[Submit][Status][Discuss] Description Input Output Sample Input Sample Output 84 131 27 84 HINT Source Solution 树上带修莫队 本质还是树上莫队,详情可以转 BZOJ-3757苹果树 但是这里需要修改,就需要一些特殊的地方

WC2013 糖果公园

COGS 1817. [WC2013]糖果公园 http://www.cogs.pro/cogs/problem/problem.php?pid=1817 ★★★☆   输入文件:park.in   输出文件:park.out   简单对比时间限制:8 s   内存限制:512 MB [题目描述] Candyland 有一座糖果公园,公园里不仅有美丽的风景.好玩的游乐项目,还有许多免费糖果的发放点,这引来了许多贪吃的小朋友来糖果公园玩. 糖果公园的结构十分奇特,它由 n 个游览点构成,每个游览点

[bzoj 3052][wc2013]糖果公园

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3052 [wc2013]糖果公园 Time Limit: 200 Sec  Memory Limit: 512 MBSubmit: 1213  Solved: 609[Submit][Status][Discuss] Description Input Output Sample Input Sample Input Sample Output 84 131 27 84 树上莫队,把树分块,

[WC2013][UOJ58]糖果公园 莫队算法

这道题有毒!!!!!!!!!!!!!!!!!! 先贴个题面吧QwQ #58. [WC2013]糖果公园 Candyland 有一座糖果公园,公园里不仅有美丽的风景.好玩的游乐项目,还有许多免费糖果的发放点,这引来了许多贪吃的小朋友来糖果公园玩. 糖果公园的结构十分奇特,它由 nn 个游览点构成,每个游览点都有一个糖果发放处,我们可以依次将游览点编号为 11 至 nn.有 n?1n?1 条双向道路连接着这些游览点,并且整个糖果公园都是连通的,即从任何一个游览点出发都可以通过这些道路到达公园里的所有

bzoj 3052: [wc2013]糖果公园 带修改莫队

3052: [wc2013]糖果公园 Time Limit: 250 Sec  Memory Limit: 512 MBSubmit: 506  Solved: 189[Submit][Status] Description Input Output Sample Input Sample Input Sample Output 84 131 27 84 HINT 本来这道题想到了莫队算法,但是看到带修改就直接放弃了.结果看题解才发现带修改居然也能用莫队!!!之所以可以这样用,是因为修改的时间复

AC日记——[WC2013]糖果公园 cogs 1817

[WC2013]糖果公园 思路: 带修改树上莫队(模板): 来,上代码: #include <cmath> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define maxn 100005 #define ll long long struct QueryType { ll u,v,t,id;

bzoj 3052: [wc2013]糖果公园(带修改的树上莫队)

3052: [wc2013]糖果公园 Time Limit: 200 Sec  Memory Limit: 512 MB Submit: 892  Solved: 425 [Submit][Status][Discuss] Description Input Output Sample Input Sample Input Sample Output 84 131 27 84 HINT Source [Submit][Status][Discuss] 题解:bzoj 2120 和 bzoj 37