校门外的树-----珂朵莉树

校门外的树-----珂朵莉树

题目描述
某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是11米。我们可以把马路看成一个数轴,马路的一端在数轴00的位置,另一端在LL的位置;数轴上的每个整数点,即0,1,2,…,L0,1,2,…,L,都种有一棵树。

由于马路上有一些区域要用来建地铁。这些区域用它们在数轴上的起始点和终止点表示。已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。现在要把这些区域中的树(包括区域端点处的两棵树)移走。你的任务是计算将这些树都移走后,马路上还有多少棵树。

输入格式
第一行有22个整数 L(1 \le L \le 10000)L(1≤L≤10000) 和 M(1 \le M \le 100)M(1≤M≤100),LL代表马路的长度,MM代表区域的数目,LL和MM之间用一个空格隔开。
接下来的MM行每行包含22个不同的整数,用一个空格隔开,表示一个区域的起始点和终止点的坐标。

输出格式
11个整数,表示马路上剩余的树的数目。

输入输出样例
输入
500 3
150 300
100 200
470 471
输出
298
简单的模拟过去的代码

#include<iostream>
#include<string.h>
using namespace std;
int a,b;
bool flag[100005];
int main(){
	memset(flag,false,100005);//用来判断树木有没有被访问过
	int L,M,sum=0;			//从0这个树开始,将这条路上所有的树标记为未访问过
	cin>>L>>M;
	for(int i=1;i<=M;i++){
		cin>>a>>b;
		for(int j=a;j<=b;j++){//定义区间的头和尾
			if(flag[j]==false)
				flag[j]=true;//从这个区间的头和尾开始,将所有区间内的树标记为访问过
		}
	}
	for(int i=0;i<=L;i++){
		if(flag[i]==false)
			sum++;	
	}
	cout<<sum<<endl;
	return 0;
}

但是做完之后,我发现有更简洁的算法,而且有个奇特的名字珂朵莉树,嘻嘻,耐不住好奇心,就去查了查资料,然后自己总结一下。

改题目使用珂朵莉树代码如下:

#include<iostream>
#include<cstring>
#include<cstdio> 
#define re register
#define FOR(i,l,r) for(re int i=l;i<=r;++i)
using namespace std;

int a[200001],i,h1,h2,s=0,m,l,j;

inline void in(re int &x){//输入 
    x=0;
	char c=getchar();
    while(c<'0'||c>'9'){//字符 
        c=getchar();
    }
    while(c<='9'&&c>='0'){//数字 
        x=(x<<1)+(x<<3)+(c^'0');
        c=getchar();
    }
}

inline void out(re int a){//输出  递归 
    if(a>=10)out(a/10);
    putchar(a%10+'0');
}

int main(){
    in(l),in(m); 
    FOR(i,1,m){//for循环 
        in(h1),in(h2);
        FOR(j,h1,h2)
          a[j]=1;
    }
    FOR(i,0,l)  
      if(a[i]==0)
        ++s;
    out(s);
    puts("");    
}

珂朵莉树的构造代码如下:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<set>
#include<algorithm>
#define re register
#define FOR(i,l,r) for(re int i=l;i<=r;++i)
#define IT set<node>::iterator
using namespace std;

int n,m,x,y;

inline void in(re int &x){
    x=0;char c=getchar();
    while(c<'0'||c>'9'){
        c=getchar();
    }
    while(c<='9'&&c>='0'){
        x=(x<<1)+(x<<3)+(c^'0');
        c=getchar();
    }
}

inline void out(re int a){
    if(a>=10)out(a/10);
    putchar(a%10+'0');
}

struct node{
    int l,r;
    mutable int v;
    node(int L,int R=-1,int V=0):l(L),r(R),v(V) {}
    bool operator <(const node &o)const{
        return l<o.l;
    }
};

set<node> s;

inline IT split(re int pos){
    IT it=s.lower_bound(node(pos));
    if(it!=s.end()&&it->l==pos)
      return it;
    --it;
    int L=it->l;
    int R=it->r;
    int V=it->v;
    s.erase(it);
    s.insert(node(L,pos-1,V));
    return s.insert(node(pos,R,V)).first;
}

inline void assign_val(re int l,re int r,re int val=0){
    IT itr=split(r+1),itl=split(l);
    s.erase(itl,itr);
    s.insert(node(l,r,val));
}

inline int query(re int l,re int r){
    int res=0;
    IT itr=split(r+1),itl=split(l);
    for(;itl!=itr;++itl)
      res+=(itl->r-itl->l+1)*(itl->v);
    return res;
}

int main(){
    in(n),in(m);
    s.insert(node(0,n,1));
    FOR(i,1,m){
        in(x),in(y);
        assign_val(x,y,0);
    }
    out(query(0,n));
    puts("");
}

珂朵莉树到底是干嘛的呢?
使一整段区间内的东西变得一样,数据随机。
n 个数, m 次操作 (n,m<=10^5) 。

操作:

1.区间加

void add(int l, int r, LL val)
{
    IT itl = split(l),itr = split(r+1);
    for (; itl != itr; ++itl) 
        itl->v += val;
}

2.区间赋值

3.区间第k小

LL rank(int l, int r, int k)
{
    vector<pair<LL, int> > vp;
    IT itl = split(l),itr = split(r+1);
    vp.clear();
    for (; itl != itr; ++itl)
        vp.push_back(pair<LL,int>(itl->v, itl->r - itl->l + 1));
    sort(vp.begin(), vp.end());
    for (vector<pair<LL,int> >::iterator it=vp.begin();it!=vp.end();++it)
    {
        k -= it->second;
        if (k <= 0) 
            return it->first;
    }
}

4.求区间幂次和

LL pown(LL a, LL b, LL mod)
{
    LL res = 1;
    LL ans = a % mod;
    while (b)
    {
        if (b&1) 
            res = res * ans % mod;
        ans = ans * ans % mod;
        b>>=1;
    }
    return res;
}

LL sum(int l, int r, int ex, int mod)
{
    IT itl = split(l),itr = split(r+1);
    LL res = 0;
    for (; itl != itr; ++itl)
        res = (res + (LL)(itl->r - itl->l + 1) * pown(itl->v, LL(ex), LL(mod))) % mod;
    return res;
}

暴力找到元素,快速幂,加入答案,结束(记得不要忘乘上集合里的个数)!!!

数据随机,时限2s。

构造珂朵莉树

struct node
{
    int l,r; //范围
    mutable LL v; //数值
    node(int L, int R=-1, LL V=0):l(L), r(R), v(V) {}
    bool operator<(const node& o) const     //重载运算符
    {
        return l < o.l;
    }
};

表示【l,r】这一个区间中所有的数都是v。
重要的操作------分裂split

#define IT set<node>::iterator  //太长了

IT split(int pos)
{
    IT it = s.lower_bound(node(pos));   //找到首个不小于pos的set
    if (it != s.end() && it->l == pos)   //无需,直接返回
        return it;
    --it;  //否则一定在前一个区间中
    int L = it->l, R = it->r;  //【l,r】就是要分裂的区间
    LL V = it->v;  //取出值
    s.erase(it);   //删除原集合
    s.insert(node(L, pos-1, V));  //构建前半段的新结合
    return s.insert(node(pos, R, V)).first;  //构建后半段的新集合并且返回地址
}

实际很简单,一个集合中,有一部分需要修改,而另一部分不需要修改,就把集合拆开,拆成两部分。(要修改的就修改,不修改的就算了)

珂朵莉树的推平操作:assign_val

void assign_val(int l, int r, LL val)
{
    IT itl = split(r+1),itr = split(l);  //求出要被摊平区间的收尾地址
    s.erase(itl, itr);  //删除原集合
    s.insert(node(l, r, val));  //添加新集合
}

要是只有split还不得复杂度爆炸?我们需要assign操作迅速减少set的数量。珂朵莉树的复杂度是由assign_val保证的。由于数据随机,有1/4的操作为assign。set的大小快速下降,最终趋于logn ,使得这种看似暴力无比的数据结构复杂度接近mlogn .

完整代码:

#include<bits/stdc++.h>
#define IT set<node>::iterator
using namespace std;
typedef long long LL;
const int MOD7 = 1e9 + 7;
const int MOD9 = 1e9 + 9;
const int imax_n = 1e5 + 7;
struct node
{
    int l,r;
    mutable LL v;
    node(int L, int R=-1, LL V=0):l(L), r(R), v(V) {}
    bool operator<(const node& o) const
    {
        return l < o.l;
    }
};
LL pown(LL a, LL b, LL mod)
{
    LL res = 1;
    LL ans = a % mod;
    while (b)
    {
        if (b&1) 
            res = res * ans % mod;
        ans = ans * ans % mod;
        b>>=1;
    }
    return res;
}
set<node> s;
IT split(int pos)
{
    IT it = s.lower_bound(node(pos));
    if (it != s.end() && it->l == pos) 
        return it;
    --it;
    int L = it->l, R = it->r;
    LL V = it->v;
    s.erase(it);
    s.insert(node(L, pos-1, V));
    return s.insert(node(pos, R, V)).first;
}
void add(int l, int r, LL val)
{
    IT itr = split(r+1),itl = split(l);
    for (; itl != itr; ++itl) 
        itl->v += val;
}
void assign_val(int l, int r, LL val)
{
    IT itr = split(r+1),itl = split(l);
    s.erase(itl, itr);
    s.insert(node(l, r, val));
}
LL ranks(int l, int r, int k)
{
    vector<pair<LL, int> > vp;
    IT itr = split(r+1),itl = split(l);
    vp.clear();
    for (; itl != itr; ++itl)
        vp.push_back(pair<LL,int>(itl->v, itl->r - itl->l + 1));
    sort(vp.begin(), vp.end());
    for (vector<pair<LL,int> >::iterator it=vp.begin();it!=vp.end();++it)
    {
        k -= it->second;
        if (k <= 0) 
            return it->first;
    }
}
LL sum(int l, int r, int ex, int mod)
{
    IT itr = split(r+1),itl = split(l);
    LL res = 0;
    for (; itl != itr; ++itl)
        res = (res + (LL)(itl->r - itl->l + 1) * pown(itl->v, LL(ex), LL(mod))) % mod;
    return res;
}
int n, m;
LL seed, vmax;
LL rd()
{
    LL ret = seed;
    seed = (seed * 7 + 13) % MOD7;
    return ret;
}
LL a[imax_n];
int main()
{
    cin>>n>>m>>seed>>vmax;
    for (int i=1; i<=n; ++i)
    {
        a[i] = (rd() % vmax) + 1;
        s.insert(node(i,i,a[i]));
    }
    s.insert(node(n+1, n+1, 0));
    int lines = 0;
    for (int i =1; i <= m; ++i)
    {
        int op = int(rd() % 4) + 1;
        int l = int(rd() % n) + 1;
        int r = int(rd() % n) + 1;
        if (l > r)
            swap(l,r);
        int x, y;
        if (op == 3)
            x = int(rd() % (r-l+1)) + 1;
        else
            x = int(rd() % vmax) +1;
        if (op == 4)
            y = int(rd() % vmax) + 1;
        if (op == 1)
            add(l, r, LL(x));
        else if (op == 2)
            assign_val(l, r, LL(x));
        else if (op == 3)
            cout<<ranks(l,r,x)<<endl;
        else
            cout<<sum(l,r,x,y)<<endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值