Codeforces697C Lorenzo Von Matterhorn

12 篇文章 0 订阅
2 篇文章 0 订阅

Lorenzo Von Matterhorn

Barney lives in NYC. NYC has infinite number of intersections numbered with positive integers starting from 1. There exists a bidirectional road between intersections i and 2i and another road between i and 2i + 1 for every positive integer i. You can clearly see that there exists a unique shortest path between any two intersections.

Initially anyone can pass any road for free. But since SlapsGiving is ahead of us, there will q consecutive events happen soon. There are two types of events:

1. Government makes a new rule. A rule can be denoted by integers vu and w. As the result of this action, the passing fee of all roads on the shortest path from u to v increases by w dollars.

2. Barney starts moving from some intersection v and goes to intersection u where there's a girl he wants to cuddle (using his fake name Lorenzo Von Matterhorn). He always uses the shortest path (visiting minimum number of intersections or roads) between two intersections.

Government needs your calculations. For each time Barney goes to cuddle a girl, you need to tell the government how much money he should pay (sum of passing fee of all roads he passes).

Input

The first line of input contains a single integer q (1 ≤ q ≤ 1 000).

The next q lines contain the information about the events in chronological order. Each event is described in form 1 v u w if it's an event when government makes a new rule about increasing the passing fee of all roads on the shortest path from u to v by w dollars, or in form 2 v u if it's an event when Barnie goes to cuddle from the intersection v to the intersection u.

1 ≤ v, u ≤ 1018, v ≠ u, 1 ≤ w ≤ 109 states for every description line.

Output

For each event of second type print the sum of passing fee of all roads Barney passes in this event, in one line. Print the answers in chronological order of corresponding events.

Example

input
7
1 3 4 30
1 4 1 2
1 3 6 8
2 4 3
1 6 1 40
2 3 7
2 2 4
output
94
0
32

题意:有一棵无限多的满二叉树,根结点的值为1,两个子结点的数分别为两倍的根结点和两倍的根结点加一。

一开始每条边的权值均为0。

现在有两种操作,操作1是将两个结点之间所有路径的边权值加w。

操作2是给你两个结点为要你求出两结点的路径长度。

解法:暴力

由于树的特性,其实每次的更新或是查找相关的结点并不多。所以可以用一个map<pair, int>存储每两个结点间的权值。

对于两个数要想找到他们的路径,我们首先需要找到两者的最近公共祖先,其实就是二进制位高位相等的位数。

比如对于数6和5 二进制分别为110和101那么1就是两者的lca,首先需要把5->2->1的权值更新,再更新1->3->6。

于是从高到低比较两者的二进制,然后先向lca更新,再向下更新。

由于数据量不大,用map暴力就可以。就是编码可能有一些些复杂。

#include <bits/stdc++.h>
#include <cstdio>
#define ll long long
#define __max(a,b) a > b ? a : b
#define pll pair<ll, ll>
using namespace std;
const int maxn = 1e5 + 10;
map<pll, ll> power;
int U[100], V[100];
ll f, u, v, w;
int main()
{
//    freopen("/Users/vector/Desktop/out.txt", "w", stdout);
    ios::sync_with_stdio(false);
    cin.tie(0);
    int q;
    cin >> q;

    while(q--)
    {
        cin >> f >> u >> v;
        if(f == 1) cin >> w;
        if(u > v)
            swap(u, v);
        int iu = 0, iv = 0;
        ll uu = u, vv = v;
        while(uu > 0)
        {
            U[iu++] = uu % 2;
            uu /= 2;
        }
        while(vv > 0)
        {
            V[iv++] = vv % 2;
            vv /= 2;
        }
        int i;
        for(i = 0; i < iu; i++)
        {
            if(U[iu - i - 1] != V[iv - i - 1])
                break;
        }
        if(f == 1)
        {
            if(i < iu)//向上更新
            {
                for(int j = i; j <= iu - 1; j++)
                {
                    power[make_pair(u, u / 2)] += w;
                    power[make_pair(u / 2, u)] += w;//注意每次要记得双向更新
                    u /= 2;
                }
            }
            for(int j = i; j < iv; j++)//向下更新
            {
                if(V[iv - j - 1])
                {
                    power[make_pair(u, (u << 1) + 1)] += w;
                    power[make_pair(u * 2 + 1, u)] += w;
                    u = (u << 1)+ 1;
                }
                else
                {
                    power[make_pair(u, u << 1)] += w;
                    power[make_pair(u << 1, u)] += w;
                    u = u << 1;
                }
            }
        }
        else//查询两点间路径时也类似
        {
            ll ans = 0;
            if(i < iu)
            {
                for(int j = i; j <= iu - 1; j++)
                {
                    ans += power[make_pair(u, u / 2)];
                    u /= 2;
                }
            }
            for(int j = i; j < iv; j++)
            {
                if(V[iv - j - 1])
                {
                    ans += power[make_pair(u, (u << 1) + 1)];
                    u = (u << 1)+ 1;
                }
                else
                {
                    ans += power[make_pair(u, u << 1)];
                    u = u << 1;
                }
            }
            cout << ans << endl;
        }
    }
    return 0;
}

事实上,这样写还是写麻烦了。

更好的办法是,由于从子结点向父结点就只有一条路径。所以可以直接用map存每个结点到父结点的权值。

每次两个值循环向上找公共祖先。( 大的数不断的除以2)

这样的代码就很elegant了。

#include <bits/stdc++.h>
#include <cstdio>
#define ll long long
#define __max(a,b) a > b ? a : b
#define pll pair<ll, ll>
using namespace std;
const int maxn = 1e5 + 10;
map<ll, ll> power;
bool U[100], V[100];
ll f, u, v, w;
int main()
{
    //    freopen("/Users/vector/Desktop/out.txt", "w", stdout);
    ios::sync_with_stdio(false);
    cin.tie(0);
    int q;
    cin >> q;
    
    while(q--)
    {
        cin >> f >> u >> v;
        if(f == 1) cin >> w;
        if(u > v)
            swap(u, v);
        if(f == 1)
        {
            while(u != v)
            {
                if(u > v)
                    swap(u, v);
                power[v] += w;
                v /= 2;
            }
        }
        else
        {
            ll ans = 0;
            while(u != v)
            {
                if(u > v)
                    swap(u, v);
                ans += power[v];
                v /= 2;
            }
            cout << ans << endl;
        }
    }
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值