多次区间修改(收敛性质+线段树维护+set维护+并查集维护方案)

原题链接:G-鸡格线_2023牛客寒假算法基础集训营1 (nowcoder.com)

 题意:
你有一个长为n的数组a,你需要支持以下两种操作:


1、输入l,r,k,对区间[l,r]中所有数字执行 a[i]=f[a[i]] 操作k次(式中等号表示赋值操作),之中f(x)=round(10*sqrt(x)),round为四舍五入函数。

2、输出当前数组所有数字的和。

你需要正确处理m次这样的操作。

范围: n,m,l,r,k均是1-1e5 ai为0-1e9

由f(x)=round(10*sqrt(x))可以看出最后f(x)会在(x=0||x=99||x=100)以后保持不变,而ai的范围为0-1e9也就是说最多进行10几次操作就可以收敛到固定值。

那么在修改l-r范围时只需要将未收敛的值经行k次或者更少的操作就行。问题就转变成如何在l-r区间找到未收敛的值。

方案1:利用set存目前还未收敛的值的下标利用set的Lower bond和erase的方法解决 l,r区间每次在set里用lowerbound找大于l小于r的下标进行修改操作,然后判断是否收敛如果收敛的话将这个下标删除。最后更新l知道找到的下标大于r。

这种方法最简单最直观的就可以算出结果。

#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define int long long
#define PII pair<int,int>
const int N=2e5+10;
const int mod=1e9+7;
int lowbit(int x){return x&(-x);}
int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
int a[N];
int fun(int x)
{
    return round(10.0*sqrt(1.0*x));
}
void solve()
{
//    freopen("D:\\c++\\in","r",stdin);
//    freopen("D:\\c++\\out1","w",stdout);
    int n,m;
    cin>>n>>m;
    int sum=0;
    set<int>se;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        if(fun(a[i])!=a[i])
        {
            se.insert(i);
        }
        sum+=a[i];
    }
    se.insert(n+1);(作为判断的最后一位)
    while(m--)
    {
        int op;
        cin>>op;
        if(op==1)
        {
            int l,r,k;
            cin>>l>>r>>k;
            while (1)
            {
                int pos= *se.lower_bound(l);
                if(pos>r)
                {
                    break;
                }
                for(int i=0;i< min(k,20ll);i++)
                {
                    int d= fun(a[pos]);
                    sum+=(d-a[pos]);
                    a[pos]=d;
                }
                if(a[pos]== fun(a[pos]))
                {
                    se.erase(pos);
                }
                l=pos+1;
            }
        }
        else
        {
            cout<<sum<<endl;
        }
    }
}
signed main()
{
//    IOS
//    int _;cin>>_;while(_--)
        solve();
    return 0;
}
/* 2 2 1 1 1
 *
 *
 */

方案2:线段数维护区间建树时多一个max和min可以观察出if(max<=100&&min>=99)就可以知道这个区间已经修改完了不需要再修改,多出来0这个数可以将a[i]==0这个点的max和min修改成100不影响结果。

比较公式的做法。

#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define int long long
#define PII pair<int,int>
const int N=2e5+10;
const int mod=1e9+7;
int lowbit(int x){return x&(-x);}
int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
int a[N];
int fun(int x)
{
    return round(10.0*sqrt(1.0*x));
}
struct node
{
    int l,r,minn,maxx,sum;
}tree[N*4];
void pushUp(int now)
{
    tree[now].sum=tree[now<<1].sum+tree[now<<1|1].sum;
    tree[now].maxx= max(tree[now<<1].maxx,tree[now<<1|1].maxx);
    tree[now].minn= min(tree[now<<1].minn,tree[now<<1|1].minn);
}
void build(int now,int l,int r)
{
    tree[now].l=l;
    tree[now].r=r;
    if(l==r)
    {
        tree[now].maxx=a[l];
        tree[now].minn=a[l];
        tree[now].sum=a[l];
        if(a[l]==0) tree[now].maxx=tree[now].minn=100;
        return;
    }
    int mid=l+r>>1;
    build(now<<1,l,mid);
    build(now<<1|1,mid+1,r);
    pushUp(now);
}
void change(int now,int l,int r,int k)
{
//    cout<<now<<" "<<tree[now].l<<" "<<tree[now].r<<endl;
    if(tree[now].maxx<=100&&tree[now].minn>=99)
    {
        return;
    }
    if(tree[now].l==tree[now].r)
    {
        for(int i=0;i<k;i++)
        {
            int d=fun(tree[now].maxx);
            if(d==tree[now].maxx) return;
            else tree[now].minn=tree[now].sum=tree[now].maxx= d;
        }
        return;
    }
    int mid=tree[now].l+tree[now].r>>1;
    if(l<=mid)
    {
        change(now<<1,l,r,k);
    }
    if(r>mid)
    {
        change(now<<1|1,l,r,k);
    }
    pushUp(now);
}
void solve()
{
//    freopen("D:\\c++\\in","r",stdin);
//    freopen("D:\\c++\\out1","w",stdout);
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
    }
    build(1,1,n);
    while(m--)
    {
        int op;
        cin>>op;
        if(op==1)
        {
            int l,r,k;
            cin>>l>>r>>k;
            change(1,l,r,k);
        }
        else
        {
            cout<<tree[1].sum<<endl;
        }

    }
}
signed main()
{
//    IOS
//    int _;cin>>_;while(_--)
        solve();
    return 0;
}
/* 2 2 1 1 1
 *
 *
 */

方案3:并查集解决。当一个值a[i]已经收敛完成可以看看a[i+1]和a[i-1]是否也收敛完毕

规定合并方向(我是向左)

如果左边的收敛完毕则:fa[find(i)]=find(i-1)。

如果右边的收敛完毕则:fa[find(i+1)]=find(i)。

最后根据合并方向进行跳跃直到目前的下标<l即可。

#include<bits/stdc++.h>
using namespace std;
#define IOS ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
#define int long long
#define PII pair<int,int>
const int N=2e5+10;
const int mod=1e9+7;
int lowbit(int x){return x&(-x);}
int dir[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
int a[N];
int fun(int x){return round(10.0*sqrt(1.0*x));}
int fa[N];
int n,m;
void init()
{
    for(int i=1;i<=n;i++) fa[i]=i;
}
int find(int x)
{
    return x==fa[x]?fa[x]: fa[x]= find(fa[x]);
}
void solve()
{
//    freopen("D:\\c++\\in","r",stdin);
//    freopen("D:\\c++\\out1","w",stdout);
    cin>>n>>m;
    int sum=0;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        sum+=a[i];
    }
    init();
    while(m--)
    {
        int op;
        cin>>op;
        if(op==1)
        {
            int l,r,k;
            cin>>l>>r>>k;
            if(find(r)== find(l)) continue;
            while(r>=l)
            {
                int t=a[r];
                for(int i=0;i<k;i++)
                {
                    int d= fun(t);
                    if(d==t) break;
                    t=d;
                }
                sum+=(t-a[r]);
                a[r]=t;
                if(fun(a[r])==a[r])
                {
                    if(r-1>=1&& fun(a[r-1])== a[r-1]) fa[find(r)]= find(r-1);
                    if(r+1<=n&& fun(a[r+1])==a[r+1]) fa[find(r+1)]= find(r);
                }
                r= find(r)-1;
            }
        }
        else
        {
            cout<<sum<<endl;
        }
    }
}
signed main()
{
//    IOS
//    int _;cin>>_;while(_--)
        solve();
    return 0;
}
/* 2 2 1 1 1
 *
 *
 */

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值