BZOJ-3211花神游历各国 并查集+树状数组

一开始想写线段树区间开方,简单暴力下,但觉得变成复杂度稍高,懒惰了,编了个复杂度简单的
3211: 花神游历各国
Time Limit: 5 Sec Memory Limit: 128 MB
Submit: 1706 Solved: 651
[Submit][Status][Discuss]
Description
这里写图片描述
Input
这里写图片描述
Output
每次x=1时,每行一个整数,表示这次旅行的开心度
Sample Input
4
1 100 5 5
5
1 1 2
2 1 2
1 1 2
2 2 3
1 1 4
Sample Output
101
11
11
HINT
对于100%的数据, n ≤ 100000,m≤200000 ,data[i]非负且小于10^9

这道题一打眼看就是区间操作,就想到线段树和树状数组,一开始想暴力开根到1为止,写个线段树区间开方
但感觉编程复杂度相对较高,外加时间复杂度不低,于是写了个树状数组+并查集
树状数组的用处不用多说,并查集的用处比较精妙:
用并查集维护一下,维护每个数右边第一个不为1的数字,暴力开根,
如果开根成1后,把他的父亲连到右边数的父亲上,这样在连续修改上,就可以跳过大量连续的1了
666666

代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define maxn 100001
long long love[maxn]={0};
int past[maxn]={0};
int father[maxn]={0};
int n;

int lowbit(int x)
{
    return x&(-x);
}

void change(int loc,int data)
{
    while (loc<=n)
        {
            love[loc]+=data;
            loc+=lowbit(loc);
        }
}

long long sum(int loc)
{
    long long tot=0;
    while (loc>0)
        {
            tot+=love[loc];
            loc-=lowbit(loc);
        }
    return tot;
}

int find(int x)
{
    if (x==father[x])
        return x;
    else
        {
            father[x]=find(father[x]);
            return father[x];
        }
}

int main()
{
    scanf("%d",&n);
    for (int i=1; i<=n; i++)
        {
            int x;
            scanf("%d",&x);
            change(i,x);
            past[i]=x;
            if (past[i]<=1)
                father[i]=i+1;
            else
                father[i]=i;//一开始对father的初始化 
        }
    int m;
    father[n+1]=n+1;
    scanf("%d",&m);
    for (int i=1; i<=m; i++)
        {
            int command,l,r;
            scanf("%d%d%d",&command,&l,&r);
            if (command==1)
                {
                    long long ans=sum(r)-sum(l-1);
                    printf("%lld\n",ans);
                }
            else
                {
                    for (l=find(l); l<=r; l=find(l+1))
                        {
                            int delta=floor(sqrt(past[l]));
                            change(l,delta-past[l]);//变成开根的方法,就是先减掉自己本身再加上开根,所以可以直接减去自身和开根的差 
                            past[l]=delta;
                            if (past[l]==1)
                                father[l]=find(l+1);//如果开根到1了,就把father连到右边数的father上 
                        }
                }
        }
    return 0;
} 

话说这个题后来修改时T了一遍,W了一遍,懵懂中搜索了一下,竟是我DCrusher蛋哥的blog,可惜蛋神做法太高端,于是还是自己修改去了,╮(╯▽╰)╭

posted @ 2015-12-05 21:56  DaD3zZ  阅读(153)  评论(0编辑  收藏  举报