【动态点分】[高新集训]Tree 改题报告

7 篇文章 0 订阅
4 篇文章 0 订阅

这题思路很明显的,很快想到,然后写挂。好像写出来bug数不胜数。因此本文主要聚焦在debug过程,故称“改题报告”

Description

一棵树上,点有点权,边有边权。一个无序点对 (x,y) 的贡献是 (axxoray)×dis(x,y) ,即点权的异或乘上距离。
每次会修改一个点权,并查询修改后所有点对贡献之和。

Exam

写完一题后开始做,想了十几分钟就出了正解,然后看三题是网络流二合一,因为对网络流还是没这么熟悉,决定先做二题。又考虑了一下细节,然后就开始写。
写到11点过写完了(貌似这个写的速度还是不够啊),运行样例,一切行为也完全符合预期,感觉不错。若再要写三题,大概时间不够了(那时我还没想清楚如何建图)。于是上了个厕所,回来慢悠悠地写对拍。
结果拍了第一个就WA惨了。这时感到非常不妙,赶快用小数据调试。到考试结束时调了一个bug,可是还是WA。
悲惨的故事是,这时我慌忙把暴力交上去,结果没改文件名,输出文件是tree.ans

Debug

这是出错的关键部分:

long long sum[30050][30], sum_[30050][30], Ans;
int cnt[30050][30], cnt_[30050][30];
int f[30050][13], d[30050][13], t[30050];

int root, near;
void setroot(int x, int fa, int dis) {
    f[x][t[x]] = root;
    d[x][t[x]++] = dis;
    for (int i = 0; i < 15; i++) {
        register int j = i<<1 | a[x]>>i & 1;
        sum_[near][j] += dis;
        cnt_[near][j]++;
    }
    for (int i = h[x], u; i; i = nx[i])
        if (!vis[u = to[i]] && u ^ fa)
            setroot(u, x, dis+w[i]);
}

void divide(int rt) {
    int S = dfssize(rt,0) >> 1;
    while (max[rt] > S) rt = son[rt];
    vis[rt] = true;
    f[rt][t[rt]] = rt;
    d[rt][t[rt]] = 0;
    for (int i = 0; i < 15; i++)
        cnt[rt][i<<1 | a[rt]>>i&1] = 1;
    for (int i = h[rt]; i; i = nx[i])
        if (!vis[to[i]]) {
            setroot(near = to[i], root = rt, w[i]);
            for (int i = 0; i < 30; i++) {
                sum[rt][i] += sum_[near][i];
                cnt[rt][i] += cnt_[near][i];
                Ans -= sum_[near][i] * cnt_[near][i^1] << (i>>1);
            }
        }
    for (int i = 0; i < 30; i++)
        Ans += sum[rt][i] * cnt[rt][i^1] << (i>>1);
    for (int i = h[rt]; i; i = nx[i])
        if (!vis[to[i]]) divide(to[i]);
}

void modify(int x, int val) {
    for (int i = 0; i < 15; i++) {
        register int p0 = f[x][t[x]],
            fj = i<<1 | a[x]>>i & 1,
            tj = i<<1 | val >>i & 1;
        if (fj == tj) continue;
        for (int k = 1; k <= t[x]; k++) {
            register int p = f[x][k], dis = d[x][k-1];
            cnt[p0][fj]--;
            cnt_[p][fj]--;
            sum[p0][fj] -= dis;
            sum_[p][fj] -= dis;
            Ans += (cnt[p0][fj] - cnt[p0][tj] - cnt_[p][fj] + cnt_[p][tj]) * dis
                  + sum[p0][fj] - sum[p0][tj] - sum_[p][fj] + sum_[p][tj] << i;
            cnt[p0][tj]++;
            cnt_[p][tj]++;
            sum[p0][tj] += dis;
            sum_[p][tj] += dis;
            p0 = p;
        }
    }
    a[x] = val;
}

bug #1

            setroot(near = to[i], root = rt, w[i]);

near应该是rt的分治儿子,即下一层分治中心,而不是与near的连接点to[i]。因此应该对子树先divide(),得到子树重心,再setroot()
但是这样在setroot()时会受divide()设下的vis[]阻挡。可以在divide()返回时把自己的vis[]标记抹去。

bug #2

改过上面这个bug以后,由于setroot()divide()的顺序交换了,所以f[x][0..t[x]]以前是深度递增的,现在变成深度递减的了。所以关于t[x]要做一系列调整。

bug #3

我们仔细考虑一下modify()需要更新的信息:
x在点分树上所有的祖先要更新。同时更新并扣除这些点向x方向的子树的内部贡献(带_的对应数组),即从x到根除根外的点所记录的数组。
先前漏下了这一部分:最后要更新x子树内部的信息——这部分即x与其子树内点之间构成的路径。这里需要更新cnt,不需改变sum(因为dis == 0)。

bug #4

额。。。最后一个bug调了半天。。。
。。然后发现我tm数组开小了。。。
所以我考试的时候是怎么把30000的log算成13的。。。QAQ

Final Result

long long sum[30050][30], sum_[30050][30], Ans;
int cnt[30050][30], cnt_[30050][30];
int f[30050][16], d[30050][16], t[30050];
int root, near;

void setroot(int x, int fa, int dis) {
    f[x][++t[x]] = root;
    d[x][t[x]] = dis;
    for (int i = 0; i < 15; i++) {
        register int j = i<<1 | a[x]>>i & 1;
        sum_[near][j] += dis;
        cnt_[near][j]++;
    }
    for (int i = h[x], u; i; i = nx[i])
        if (!vis[u = to[i]] && u ^ fa)
            setroot(u, x, dis+w[i]);
}

int divide(int rt) {
    int S = dfssize(rt,0) >>1;
    while (max[rt] > S) rt = son[rt];
    vis[rt] = true;
    f[rt][0] = rt;
    d[rt][0] = 0;
    for (int i = 0; i < 15; i++)
        cnt[rt][i<<1 | a[rt]>>i&1] = 1;
    for (int i = h[rt]; i; i = nx[i])
        if (!vis[to[i]]) {
            near = divide(to[i]);
            setroot(to[i], root = rt, w[i]);
            for (int i = 0; i < 30; i++) {
                sum[rt][i] += sum_[near][i];
                cnt[rt][i] += cnt_[near][i];
                Ans -= sum_[near][i] * cnt_[near][i^1] << (i>>1);
            }
        }
    for (int i = 0; i < 30; i++)
        Ans += sum[rt][i] * cnt[rt][i^1] << (i>>1);
    vis[rt] = false;
    return rt;
}

void modify(int x, int val) {
    for (int i = 0; i < 15; i++) {
        register int p0 = f[x][t[x]],
            fj = i<<1 | a[x]>>i & 1,
            tj = i<<1 | val >>i & 1;
        if (fj == tj) continue;
        for (int k = t[x]; k > 0; k--) {
            register int p = f[x][k-1], dis = d[x][k];
            cnt[p0][fj]--;
            cnt_[p][fj]--;
            sum[p0][fj] -= dis;
            sum_[p][fj] -= dis;
            Ans += (cnt[p0][fj] - cnt[p0][tj] - cnt_[p][fj] + cnt_[p][tj]) * dis
                  + sum[p0][fj] - sum[p0][tj] - sum_[p][fj] + sum_[p][tj] << i;
            cnt[p0][tj]++;
            cnt_[p][tj]++;
            sum[p0][tj] += dis;
            sum_[p][tj] += dis;
            p0 = p;
        }
        cnt[x][fj]--;
        Ans += sum[x][fj] - sum[x][tj] << i;
        cnt[x][tj]++;
    }
    a[x] = val;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值