zoukankan      html  css  js  c++  java
  • [JZOJ6347] 【NOIP2019模拟2019.9.8】ZYB玩字符串

    题目

    题目大意

    有一个字符串(p)。一开始字符串(s)为空串。
    接下来进行若干次操作:在(s)的某个空隙中插入(p)
    给出操作后的(s),问长度最小的(p)


    思考历程

    感觉是一道神仙题。
    于是考虑暴力。
    (s)前面找连续的最长串,作为(p)的前缀。显然这个串中只出现过一次(s_1)
    同样地,在后面也找一条,作为后缀。
    将前缀出现的位置和后缀出现的位置标记一下。
    统计每个字符出现的个数,求最大公因数(g),表明操作的次数为(g)的因数。
    然后按照长度从小到大枚举子串,如果当前子串的头和尾都被标记了,并且中间的字符个数的比例和整个字符串的比例相等,就取这个字符串作为(p)暴力判断。
    暴力判断的时候,每次用(KMP)找出一个子串,将它删去,递归。
    如果有多个这样的子串,那就分别搞。

    然而出现了某细节错误,导致只有(10)分。


    正解

    这题的正解居然是(DP)
    考虑一个字符串,它的所有元素会在后面的操作中逐渐分离,但是它们的相对顺序是不变的。
    而且它原来相邻的两个元素在后面的操作之后,两个元素之间的空间可以转化成个子问题。如果可行,则这个子问题也必定可行。
    先考虑普通的(DP)思路:(f_{l,r,k})表示区间([l,r])中,有零散的(k)个字母拼起来等于(p_{1..k}),其它的都合法,是否可行。
    转移有两种:一种是从(f_{l,r-1,k-1})转移过来,条件是(s[j]=p[k]),就是加上一个零散的元素。
    另一种是从(f_{l,j,k} and f_{j,r,0}),就是在后面拼一个合法的。
    然而这样会(TLE)
    紧接着我们发现,实际上,(k=(r-l+1)mod len)
    所以就可以省去一维,然后就可以(AC)了。


    代码

    using namespace std;
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <cassert>
    #define N 210
    inline int gcd(int a,int b){
    	while (b){
    		int k=a%b;
    		a=b;
    		b=k;
    	}
    	return a;
    }
    int n,nt;
    char s[N],t[N],s2[N][N];
    int buc[255],b2[255];
    int m;
    char c[255];
    bool beg[N],end[N];
    int p[N];
    bool f[N][N];
    inline bool work(int k,int len){
    	nt=len;
    	for (int i=1;i<=nt;++i)
    		t[i]=s[k+i-1];
    	memset(f,0,sizeof f);
    	for (int i=n;i>=1;--i){
    		f[i][i]=(s[i]==t[1]);
    		for (int j=i+1;j<=n;++j){
    			f[i][j]|=(f[i][j-1]&&s[j]==t[(j-1-i+1)%nt+1]);
    			for (int k=i+(j-i)%nt;k<j && !f[i][j];k+=nt)
    				f[i][j]|=(f[i][k]&&f[k+1][j]);
    		}
    	}
    	return f[1][n];
    }
    int main(){
    	freopen("string.in","r",stdin);
    	freopen("string.out","w",stdout);
    	int T;
    	scanf("%d",&T);
    	while (T--){
    		scanf("%s",s+1);
    		n=strlen(s+1);
    		memset(buc,0,sizeof buc);
    		for (int i=1;i<=n;++i)
    			buc[s[i]]++;
    		m=0;
    		for (char ch=' '+1;ch<='~';++ch)
    			if (buc[ch])
    				c[++m]=ch;
    		int g=n;
    		for (int i=1;i<=m;++i)
    			g=gcd(g,buc[c[i]]);
    		int l=1,r=n;
    		while (l<n && s[l+1]!=s[1])
    			++l;
    		while (r>1 && s[r-1]!=s[n])
    			--r;
    		memset(beg,0,sizeof beg);
    		memset(end,0,sizeof end);
    		for (int i=1,j;i+l-1<=n;++i){
    			for (j=1;j<=l;++j)
    				if (s[i+j-1]!=s[j])
    					break;
    			if (j<=l)
    				continue;
    			beg[i]=1;
    		}
    		for (int i=n,j;i+r-n>=1;--i){
    			for (j=n;j>=r;--j)
    				if (s[i+j-n]!=s[j])
    					break;
    			if (j>=r)
    				continue;
    			end[i]=1;	
    		}
    		for (int len=max(l,n-r+1);len<=n;++len)
    			if (n%len==0 && g%(n/len)==0){
    				int t=n/len,hg=0;
    				memset(b2,0,sizeof b2);
    				for (int i=1;i<=len;++i)
    					b2[s[i]]++;
    				for (int i=1;i<=m;++i)
    					if (b2[c[i]]*t==buc[c[i]])
    						hg++;
    				int i;
    				for (i=1;i+len-1<=n;++i){
    					if (beg[i] && end[i+len-1] && hg==m){
    						if (work(i,len)){
    							for (int j=i;j<i+len;++j)
    								putchar(s[j]);
    							putchar('
    ');
    							break;
    						}
    					}
    					if (b2[s[i]]*t==buc[s[i]])
    						hg--;
    					b2[s[i]]--;
    					if (b2[s[i]]*t==buc[s[i]])
    						hg++;
    					if (b2[s[i+len]]*t==buc[s[i+len]])
    						hg--;
    					b2[s[i+len]]++;
    					if (b2[s[i+len]]*t==buc[s[i+len]])
    						hg++;
    				}
    				if (i+len-1<=n)
    					break;
    			}
    	}
    	return 0;
    }
    

    总结

    这都想不出来……我真是太菜了……

  • 相关阅读:
    安装虚拟机及学习linux系统 20155222卢梓杰
    技能获取与编程学习 卢梓杰20155222
    人生第一篇博客
    20155228 2016-2017-2 《Java程序设计》第1周学习总结
    20155228 基于VirtualBox安装Ubuntu和学习linux命令的学习经历和心得
    20155228 获取技能的成功经验和关于C语言学习的调查
    20155228 你期望的师生关系是什么?
    预备作业03:安装虚拟机
    足球运动训练心得及经验分析-c语言学习调查
    我期望的师生关系
  • 原文地址:https://www.cnblogs.com/jz-597/p/11488881.html
Copyright © 2011-2022 走看看