zoukankan      html  css  js  c++  java
  • 【BZOJ 3998】弦论

    【链接】h在这里写链接


    【题意】


        给你一个长度为n的子串;
        让你求出第k小的子串是什么;
        输出答案的类型分两种;
        第一种,重复的算两次,第二种,重复的算一次。
         
        你的程序要能分别处理这两种情况。
        n最大5*10^5


    【题解】


        感觉是道裸的后缀数组了。
        先求出后缀数组;
        然后按照后缀的大小升序枚举每一个后缀就好。
     
        对于重复算作一个的情况,则用每一个后缀的长度减去Height[i];
            这个被减过的值就是以i开头的子串,且不和之前出现过的重复的子串的个数了。
            ->也即算出来字典序为k的子串是多少。
        对于重复算作多个的情况。感觉反而不好写。。
            这样做.
            先把排名前i的后缀的所有的子串的个数算出来,->做一个前缀和
            便于后续的操作。
            然后,对于最后的子串的每一位,
            枚举它应该为多少
            ->一个类似数位DP的做法。
             
            假如最后的子串的第一位是'c'
            那么
            形如
            axxxxxxx
            bxxxxxxx
            的后缀,都能够把它删掉了。
            也即把所有小于它的子串全都减掉。
            直到
            'j'xxxxx的子串个数>=k为止。
            这样就能确定第一位是j.
            然后再枚举第二位是什么也从'a'..'z'
                直到能够确定每一位是什么为止。
     
            我们可以用二分+Sa来快速确定第i位是j的后缀有多少个。
            再用一开始处理好的前缀和。
            就能知道第i位是j的后缀,包含的子串个数。

            (一开始二分的范围是1..n)
            (这个范围表示的是后缀排名为1..n)
            (之后我们会逐渐缩小这个二分的范围)
            (左端点会逐渐变大..)
            (一旦确定第一位是某个字符之后,二分排名的范围就都会变小了..
                因为【第一位是某个字符的后缀】是【集中在一片区域里面】的,同理第二位也确定
                了之后,这个范围【又会变小】了。)
            (所以二分的时候,这片区域的后缀的前i-1位全都是相同的)
                 
            从小到大枚举,逐渐确定每一位字符。
            具体实验看程序吧


    【错的次数】


    0

    【反思】


    类似数位DP确定每一位是什么的思路。

    【代码】

    #include<bits/stdc++.h>
    #define ll long long
    using namespace std;
    
    const int N = 5e5;
    const int MAX_CHAR = 255;//每个数字的最大值。
    char s[N + 10];//如果是数字,就写成int s[N+10]就好,从0开始存
    int Sa[N + 10], T1[N + 10], T2[N + 10], C[N + 10];
    int Height[N + 10], Rank[N + 10];
    
    void build_Sa(int n, int m) {
    	int i, *x = T1, *y = T2;
    	for (i = 0; i<m; i++) C[i] = 0;
    	for (i = 0; i<n; i++) C[x[i] = s[i]]++;
    	for (i = 1; i<m; i++) C[i] += C[i - 1];
    	for (i = n - 1; i >= 0; i--) Sa[--C[x[i]]] = i;
    	for (int k = 1; k <= n; k <<= 1)
    	{
    		int p = 0;
    		for (i = n - k; i<n; i++) y[p++] = i;
    		for (i = 0; i<n; i++) if (Sa[i] >= k) y[p++] = Sa[i] - k;
    		for (i = 0; i<m; i++) C[i] = 0;
    		for (i = 0; i<n; i++) C[x[y[i]]]++;
    		for (i = 1; i<m; i++) C[i] += C[i - 1];
    		for (i = n - 1; i >= 0; i--) Sa[--C[x[y[i]]]] = y[i];
    		swap(x, y);
    		p = 1; x[Sa[0]] = 0;
    		for (i = 1; i<n; i++)
    			x[Sa[i]] = y[Sa[i - 1]] == y[Sa[i]] && y[Sa[i - 1] + k] == y[Sa[i] + k] ? p - 1 : p++;
    		if (p >= n) break;
    		m = p;
    	}
    }
    
    void getHeight(int n)
    {
    	int i, j, k = 0;
    	for (i = 1; i <= n; i++) Rank[Sa[i]] = i;
    	for (i = 0; i<n; i++) {
    		if (k) k--;
    		j = Sa[Rank[i] - 1];
    		while (s[i + k] == s[j + k]) k++;
    		Height[Rank[i]] = k;
    	}
    }
    
    ll k, sum[N + 10];
    int T, n;
    
    void print(int x, int y)
    {
    	for (int i = x; i <= y; i++)
    		putchar(s[i]);
    }
    
    int main() {
    	//freopen("F:\rush.txt", "r", stdin);
    	scanf("%s", s);
    	scanf("%d%lld", &T, &k);
    	n = strlen(s);
    	s[n] = 0;
    	build_Sa(n + 1, MAX_CHAR);//注意调用n+1
    	getHeight(n);
    	if (T == 0)
    	{
    		ll ans = 0;
    		for (int i = 1; i <= n; i++)
    		{
    			int t = n - Sa[i] - Height[i];
    			if (k <= t)
    				return print(Sa[i], (int)k + Sa[i] + Height[i] - 1), 0;
    			k -= t;
    		}
    	}
    	else
    	{
    		sum[0] = 0;
    		for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + n - Sa[i];
    		//sum[i]表示排名第i的后缀的子串个数
    		if (sum[n] < k) return puts("-1"), 0;
    		int L = 1, R = n, pl = 1;
    		for (int i = 1; i <= n; i++)
    		{
    			int l2 = L;
    			for (int j = 'a'; j <= 'z'; j++)
    			{
    				int l = l2, r = R, temp = l2 - 1;
    				while (l <= r)
    				{
    					int mid = (l + r) >> 1;
    					if (s[Sa[mid] + i - 1] > j)//如果第i为大于j,就变小一点
    					{
    						r = mid - 1;
    					}
    					else//小于等于->其实就是等于了
    					{
    						temp = mid;
    						l = mid + 1;
    					}
    				}
    				if (temp == l2 - 1)//没找到第i为j的情况的。直接continue;
    				{
    					continue;
    				}
    				//找到了多少个后缀以xxxj为前缀 ->temp-l2+1;
    				//然后算出来变成xxxj...会添加多少个子串
    				//只要把后缀的前缀为xxxj的后缀的子串个数累加起来,然后减去之前重复计数过
    				//的就好
    				ll have = sum[temp] - sum[l2 - 1] - 1LL * (i - 1)*(temp - l2 + 1);
    				//前面i-1位那个子串已经计数过了,不重复计数
    				if (k <= have)
    				{
    					//如果第i位是j的后缀的子串数目大于等于k了
    					//则可以肯定第i为肯定是j
    					if (temp - l2 + 1 >= k)//如果j是最后一个字符了,直接输出就好
    						return print(Sa[l2], Sa[l2] + i - 1), 0;
    					L = l2, R = temp;//否则可以缩小二分的范围,因为第i位已经固定是j了,只有某一段的后缀的第i位是j
    					k -= temp - l2 + 1;//xxxxj一共有temp-l2+1个
    									   //要减去
    					break;
    				}
    				l2 = temp + 1, k -= have;//把第i位为j的后缀去掉,之后就不会管它了。
    				//所以直接跳到temp+1就好,然后因为第i位不是j,是大于j的,所以把xxxj...
    				//它所有的子串都去掉就好
    			}
    		}
    	}
    	puts("-1");
    	return 0;
    }


  • 相关阅读:
    图书管理系统---基于form组件和modelform改造添加和编辑
    Keepalived和Heartbeat
    SCAN IP 解释
    Configure Active DataGuard and DG BROKER
    Oracle 11gR2
    我在管理工作中積累的九種最重要的領導力 (李開復)
    公募基金公司超融合基础架构与同城灾备建设实践
    Oracle 11g RAC for LINUX rhel 6.X silent install(静默安装)
    11gR2 静默安装RAC 集群和数据库软件
    Setting Up Oracle GoldenGate 12
  • 原文地址:https://www.cnblogs.com/AWCXV/p/7625979.html
Copyright © 2011-2022 走看看