G. Count the Trains && 楼房重建 线段树区间合并

思路

楼房重建思路一致

这里转载一篇题解仅供学习参考

显然可以想到进行斜率处理,通过斜率的单调递增来求出len。

其实答案就是整个1—n区间中从第一项开始,每一个大于前一项的必选,小于等于前一项的必须不选,所的得到的序列长度。

因为区间是固定的,并且发现一个区间内的答案可以通过两个子区间用某种方式进行转移。所以可以考虑到线段树做法。

线段树中只需要维护两个值,一个是区间最大值,还有一个是区间序列长度(按照刚才的理解)的值。

建树(甚至不用),修改,甚至不用pushdown,一切好说。但是发现pushup不好处理,显然两个子区间的值不能直接合并。必须满足一定关系。

可以发现,区间内的第一项一定在这个序列内,区间最大值也一定在这个序列内。

对于要被pushup的区间,它的两个子区间已经处理好了,容易知道,左儿子区间内的序列每一项一定都在这个大区间内。(因为前面形态固定,又不能选择不看到)所以只需要处理右儿子区间和左儿子区间最大值的关系,即可递归处理len值。

递归要传入该区间的值必须大于的最小值,设为lx。对于开始进入时,也就是左儿子的最大值。

1.如果l==r,该位置的值大于lx,return1,否则return0;

2.将该区间劈成两段,设为s1,s2区间。

①如果s1的最大值小于等于lx,那么s1必然不会对答案产生贡献,去找s2。即代码中: return pushup2(lx,s2,mid+1,r)

②如果s1的最大值大于lx,那么s2中剩下的在s1,s2组成的原区间中做贡献的项一定能贡献到最终答案中。即+l(x)-l(s1),这里注意,不是 l(s2),因为可能在l(s2)中存在的项,不一定在l(x)这个大区间中出现。所以这两个值是完全不同的概念。

之后再去寻找s1. 即:return pushup2(lx,s1,l,mid)+l(x)-l(s1);

Count the Trains

struct node{
    int mi;
    int len;
}t[100005<<2];
int n,m;
int a[100005];
int cnt = 0;
int up(int i,int l,int r,int lx){
    //cout << ++cnt << endl;
    if(t[i].mi >= lx) return 0;
    if(a[l] < lx) return t[i].len;
    if(l == r) return a[l] < lx;
    
    int mid = l+r>>1;
    if(t[i<<1].mi >= lx) return up(i<<1|1,mid+1,r,lx);
    else return up(i<<1,l,mid,lx)+t[i].len -  t[i<<1].len;
}

void chg(int i,int l,int r,int p,int v){
    if(l==r){
        t[i].len = 1;
        t[i].mi = v;
        return;
    }
    int mid = l+r >> 1;
    if(p <= mid) chg(i<<1,l,mid,p,v);
    else chg(i<<1|1,mid+1,r,p,v);
    t[i].mi = min(t[i<<1].mi,t[i<<1|1].mi);
    t[i].len = t[i<<1].len + up(i<<1|1,mid+1,r,t[i<<1].mi);
}
void solve(){
    cin>>n>>m;
    forr(i,1,n) cin >> a[i],chg(1,1,n,i,a[i]);
    while(m--){
        int p,v;
        cin>>p>>v;
        a[p] -= v;
        chg(1,1,n,p,a[p]);
        cout << t[1].len <<" ";
    }
    cout << '\n';
}
signed main()
{
    _orz;
    int t;cin>>t;
    while(t--) solve();
    return 0; 
}

楼房重建

int n,m;
struct node{
    double k;
    int len;
}t[100005<<2];

map<int,double> mp;

int up(int i,int l,int r,double mx){
    if(t[i].k <= mx) return 0;
    if(mx < mp[l]) return t[i].len;
    if(l==r) return mp[l] > mx;
    int s1 = i<<1,s2 = i<<1|1;
    int mid = l+r >> 1;
    if(t[s1].k <= mx) return up(s2,mid+1,r,mx);
    else return up(s1,l,mid,mx) + t[i].len - t[s1].len;
}
void chg(int i,int l,int r,int p,double val){
    if(l==r){
        t[i].len = 1;
        t[i].k = val;
        return;
    }
    int mid = l + r >> 1;
    if(p <= mid)chg(i<<1,l,mid,p,val);
    else chg(i<<1|1,mid+1,r,p, val);
    t[i].k = max(t[i<<1].k,t[i<<1|1].k);
    t[i].len = t[i<<1].len + up(i<<1|1,mid+1,r,t[i<<1].k);
}

signed main()
{
    cin>>n>>m;
    while(m--){
        int i,x;
        cin >> i >> x;
        double c = (double)x/i;
        mp[i] = c;
        chg(1,1,n,i,c);
        cout << t[1].len << endl;
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值