zoukankan      html  css  js  c++  java
  • [loj#2313] [HAOI2017] 供给侧改革

    题意简述

    给定一个随机生成的长度为 (n) 的由0和1构成的字符串 (S)

    (data(l,r)) 表示:在字符串 (S) 中,起始位置在 ([l,r]) 之间的这些后缀之中,具有最长公共前缀的两个后缀的最长公共前缀的长度。

    (m) 次询问,每次给定 (l,r) ,求 (sumlimits_{lleq i < r} data(i,r)) ,不强制在线。

    (n,m leq 10^5)


    想法

    注意到“随机生成”,那么两个长度为 (x) 的字符串完全相同的概率为 (frac{1}{2^x})
    (x=40) 时这个概率就很小了,所以可认为最长公共前缀的长度 (leq 40)
    将询问离线,按 (r) 从小到大排序。
    维护一棵深度40的 (trie) 树。
    从左到右扫,(trie) 树中起始位置在 (i) 的后缀。
    显然 (data(1,i)geq data(2,i) geq ... geq data(i-1,i))
    维护数组 (h[]),其中 (h[x]=y) 表示 (y) 是满足 (data(y,i)geq x) 的最大值。
    换句话说,(data(1,i)geq data(2,i) geq ... geq data(y,i) geq x > data(y+1,i))

    那么对于所有 (r=i) 的询问,将 (h[]) 扫一遍,与 (l) 比比大小再运算便可得到 (ans) ,因为 (h[]) 下标最大40,所以时间上不成问题。

    最后一个问题,每加入一个新后缀时,如何更新 (h[]) 呢?
    (trie) 树中每个点 (x) 都记录一下之前最后访问该点的那个后缀的起始位置 (lst[x]),新加入的后缀走到深度为 (d) 的点 (x) 时,(lst[x]) 开头的后缀与此新后缀的公共前缀至少为 (d) ,也就是 (data(lst[x],i)geq d) ,所以更新 (h[d]=max(h[d],lst[x]))

    总复杂度 (O(40n))


    总结

    抓住“随机”找特点;抓住小的数据点。
    (感觉这个题有点神,虽然代码很短但不太好想……)


    代码

    #include<cstdio>
    #include<iostream>
    #include<algorithm>
    
    using namespace std;
    
    int read(){
    	int x=0;
    	char ch=getchar();
    	while(!isdigit(ch)) ch=getchar();
    	while(isdigit(ch)) x=x*10+ch-'0',ch=getchar();
    	return x;
    }
    
    const int N = 100005;
    
    int n,m;
    char s[N];
    struct data{
    	int l,r,id;
    	bool operator < (const data &b) const{ return r<b.r; }
    }d[N];
    int ans[N],h[45];
    
    int cnt,root,ch[N*40][2],lst[N*40];
    void ins(int id){
    	int x=root,y;
    	for(int i=0;i<40 && i+id<=n;i++){
    		y=s[i+id]-'0';
    		if(!ch[x][y]) ch[x][y]=++cnt;
    		x=ch[x][y];
    		h[i+1]=max(h[i+1],lst[x]);
    		lst[x]=id;
    	}
    }
    
    int main()
    {
    	n=read(); m=read();
    	scanf("%s",s+1);
    	for(int i=1;i<=m;i++) d[i].l=read(),d[i].r=read(),d[i].id=i;
    	sort(d+1,d+1+m);
    	
    	root=++cnt;
    	int t=1;
    	for(int i=1;i<=n;i++){
    		if(t>m) break;
    		ins(i);
    		for(int j=39;j>0;j--) h[j]=max(h[j],h[j+1]);
    		while(t<=m && d[t].r==i){
    			for(int j=1;j<=40;j++){
    				if(h[j]<d[t].l) break;
    				ans[d[t].id]+=h[j]-d[t].l+1;
    			}
    			t++;
    		}
    	}
    	for(int i=1;i<=m;i++) printf("%d
    ",ans[i]);
    	
    	return 0;
    } 
    
  • 相关阅读:
    Android 编程下 Eclipse 恢复被删除的文件
    Android 编程下背景图片适配工具类
    Android 编程下 Managing Your App's Memory
    Android 编程下代码之(QQ消息列表滑动删除)
    Android 编程下 Canvas and Drawables
    Android 编程下 AlarmManager
    Android 编程下去除 ListView 上下边界蓝色或黄色阴影
    Java 编程下字符串的 16 位、32位 MD5 加密
    C#枚举类型和int类型相互转换
    MVC和普通三层架构的区别
  • 原文地址:https://www.cnblogs.com/lindalee/p/12363655.html
Copyright © 2011-2022 走看看