[JZOJ5555]Password

该博客介绍了一种解决动态权值计数问题的方法,通过分块策略优化了时间复杂度,实现了O(n sqrt(n))的时间复杂度。作者观察到数据规律,将问题转化为询问点的前缀相同点数或次数大于等于特定值的其他颜色点数。通过维护前缀和数组,实现了修改和查询操作的高效处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

这里写图片描述
a[1][i]<=1e4,n,m<=1e5

解题思路

一眼似乎不可做。随便搞个数据打个表找找规律,发现除了第一行,第i行和第i+2行是一样的。
考虑一种权值为一种颜色。
那么问题变成了询问一个点的前面有多少个和他一样的点记为cnt0,或者询问一个点之前,次数大于等于cnt0的其他颜色的个数cnt1。
考虑分块。
考虑维护前i个块内的每个颜色x的cnt0[x]和每个次数y的cnt1[y]。那么一个元素要插入O(根号)个块里。
一开始,我们可以先把cnt0[]用桶a搞出来。然后用另一个桶b,其中b[x]表示cnt0=x的颜色个数。然后求个后缀和,就把cnt1[]给做出来了。
考虑修改怎么做,发现每次修改某个cnt0-1,某个cnt0+1,观察到前缀和数组只会改变两位,那么修改就是O(根号)的。
考虑求答案。如果求x的cnt0,我们只需要把最大的前缀块拿出来,然后用他的cnt0[x]再加上不在块内的x的个数。
如果求cnt1,我们先求出cnt0,然后答案是,最大的前缀块的cnt1[cnt0],再加上,算上块外的数之后出现次数从小于cnt0变成大于等于cnt0的颜色个数.
时间复杂度O(n sqrt(n))

代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<map>
#include<set>
using namespace std;
typedef long long ll;
typedef double db;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
#define cmax(a,b) (a=(a>b)?a:b)
#define cmin(a,b) (a=(a<b)?a:b)
const int N=1e5+5,M=4e5+5,mo=1e9+7,xs=1331,B=320;
int read()
{
    int x=0,f=1;
    char ch=getchar();
    while (ch<'0'||ch>'9') 
    {
        if (ch=='-') f=-1;
        ch=getchar();
    }
    while (ch>='0'&&ch<='9')
    {
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x*f;
}
struct blk
{
    int en;
    int a[10005],b[10005];
    void predo()
    {
        int i;
        fo(i,1,10000) if (a[i]) b[a[i]]++;
        fd(i,10000,0) b[i]+=b[i+1];
    }
}bl[B];
int n,a[N],x,y,ka,siz,i,j,cnt,l,m,z,cnt0,cnt1;
int main()
{
    freopen("t2.in","r",stdin);
    freopen("t2.out","w",stdout);
    scanf("%d",&n);
    fo(i,1,n) a[i]=read();
    siz=trunc(sqrt(n));
    cnt=n/siz+(n%siz>0);
    fo(i,1,cnt) bl[i].en=min(n,siz*i);
    fo(i,1,n) 
        fo(j,1,cnt) if (i<=bl[j].en) 
            bl[j].a[a[i]]++;
    fo(j,1,cnt) bl[j].predo();
    m=read();
    fo(l,1,m)
    {
        ka=read();x=read();y=read();
        if (ka==1)
        {
            fo(i,1,cnt)
            if (y<=bl[i].en)
            {
                bl[i].b[bl[i].a[a[y]]--]--;
                bl[i].b[++bl[i].a[x]]++;
            }
            a[y]=x;
        }
        if (ka==2)
        {
            if (x==1) printf("%d\n",a[y]);
            else
            {
                fo(z,1,cnt) if (bl[z].en>y) break;
                z--;
                cnt0=bl[z].a[a[y]];
                fo(i,bl[z].en+1,y) if (a[i]==a[y]) cnt0++;
                if (x%2==0)
                {
                    printf("%d\n",cnt0);
                }else
                {
                    cnt1=bl[z].b[cnt0];
                    fo(i,bl[z].en+1,y) if ((++bl[z].a[a[i]])==cnt0) cnt1++;
                    printf("%d\n",cnt1);
                    fo(i,bl[z].en+1,y) --bl[z].a[a[i]];
                }
            }
        }
    }
    m=0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值