zoukankan      html  css  js  c++  java
  • P1972 【SDOI2009】 HH的项链

    #$Description$ 给你一串长度为$n$的数,有$m$个询问,每次询问$l-r$内不相同的数的个数。 #$Solution$ 这道题显然不能直接通过线段树维护,考虑记录每一个数前一次出现的位置$pos$,然后将这个值插入线段树,每次查询$l$在这个区间内的排名即可(第几小)。这个是比较显然的,因为假如一个数的前驱$pos>=l$,说明在$l$后面至少还出现一次,而只有这个数在$l-r$内最靠前的才保证排名$>1; if(k>mid) ans+=num,query(rc[u],rc[v],mid+1,r,k);//在右子树中注意加上排名 else query(lc[u],lc[v],l,mid,k); } ``` 然而呢 ![](https://img2018.cnblogs.com/blog/1564177/201910/1564177-20191025215334451-1736364844.png) $tm$这道题数据加强后主席树就$GG$了,因为这道题数据达到了$1e6$ 主席树空间开销$nlogn$容易开不下 数组开太大加上主席树比较大的常数在极限数据下$GG$了 ##结果我发现这道题正解竟然是离线+树状数组! 关键在于如何使得数组离线,发现这样一个性质 >对于若干个询问的区间$[l,r]$,如果他们的$r$都相等的话,那么项链中出现的同一个数字,一定是只关心出现在最右边的那一个的

    比如(1 3 5 2 1),对于所有(r=5)的询问,因为第(5)个数是(1),所以前面所有的(1)都不用关心,靠右边的数一定是更“优”的
    所以我们每次只需要靠右的数看他能不能更新前面的数即可。
    考虑使用梳妆数组维护
    具体实现是按照询问的(r)排序,不停往右扫描,记录一个数上一次出现的位置,每次都在当前位置插入(1),如果出现过就在上一个出现的位置插入(-1)以消除上面的数的影响,扫描到一个(r)就进行区间求和,最后按编号输出答案就行
    具体实现如下,一个简单的区间查询+单点修改

    (Code)

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    #define maxn 1000010
    #define re register
    using namespace std;
    inline 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 P{
    	int l,r,id;
    }node[maxn];
    int tre[maxn<<2];
    int tmp,n,m,ans[maxn],a[maxn],vis[maxn];
    bool cmp(P A,P B)
    {
    	return A.r<B.r;
    }
    void add(int x,int k)
    {
    	while(x<=n)
    	{
    		tre[x]+=k;
    		x+=(x&-x);
    	}
    }
    int query(int x)
    {
    	int tmp2=0;
    	while(x>=1)
    	{
    		tmp2+=tre[x];
    		x-=(x&-x);
    	}
    	return tmp2;
    }
    int Query(int l,int r)//树状数组单点修改+区间查询 
    {
       return query(r)-query(l-1);
    }
    int main()
    {
    	n=read();
    	for(re int i=1;i<=n;++i) a[i]=read();
    	m=read();
    	for(re int i=1;i<=m;++i)
    	{
    		node[i].l=read(),node[i].r=read(),node[i].id=i;
    	}
    	sort(node+1,node+m+1,cmp);
    	tmp=1;
    	for(re int i=1;i<=m;++i)
        {
        	for(re int j=tmp;j<=node[i].r;++j)//按r排序的原因上面说过 
        	{
        		if(vis[a[j]])
        		 add(vis[a[j]],-1);
        
        		 add(j,1);//不管什么时候这里都要加 
    			vis[a[j]]=j;
    		}
    		tmp=node[i].r+1;//记录下一次开始的位置,保证扫描是$O(n)$的 
    		ans[node[i].id]=Query(node[i].l,node[i].r);//记录对应编号的答案 
    	}
    	for(re int i=1;i<=m;++i)
    	 printf("%d
    ",ans[i]);//按编号输出 
    }
    
  • 相关阅读:
    netstat命令
    为什么 netstat 对某些服务只显示了 tcp6 监听端口
    端口状态说明 LISTENING、ESTABLISHED、TIME_WAIT及CLOSE_WAIT
    进程启动时主线程创建过程分析
    [Kali]关机卡死,google拼音无法输入
    [白帽子讲WEB安全]XSS <Cross Site Script>
    [白帽子将WEB安全笔记]浏览器安全
    [白帽子将WEB安全笔记]我的安全世界观
    mongodb高可用集群 3 ---分片与副本集结合
    python计算年龄小程序
  • 原文地址:https://www.cnblogs.com/Liuz8848/p/11741055.html
Copyright © 2011-2022 走看看