20210814 字符串模拟赛

赛时

今天字符串这一方面可以说是一窍不通了……只会trie树和kmp……

但是还是拿到了相当满意的分数\(\text{QWQ}\)


看完题目之后,感觉哪一道都不会,以为要爆零了……

开题顺序\(2\to1\to3\to4\)

第一印象:T1一定trie树,T2一定KMP,T3T4不太会。

T2

先开始写的这道题。

看题后发现“前缀与后缀相同部分”便想到KMP,后续发现题目完全是“找到\([1,nxt_{[i]}]\)中最长的出现至少三次的子串”。

考虑Hash确定前缀与后缀所有可能性,即初步枚举并存储所有可能可行字符串。

在这些字符串中找到最长的“在原串中出现次数\(\ge3\)的串”。

发现答案具有单调性,用二分加快这个过程。

复杂度\(O(Tn\log n)\).

期望得分:\(100\)

实际得分:\(100\)

#include <bits/stdc++.h>
#define fo(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout)
using namespace std;
const int INF = 0x3f3f3f3f , N = 1e6+5 , base = 131;
typedef long long ll;
typedef unsigned long long ull;
inline ll read(){
	ll ret = 0 ; char ch = ' ' , c = getchar();
	while(!(c >= '0' && c <= '9')) ch = c , c = getchar();
	while(c >= '0' && c <= '9') ret = (ret << 1) + (ret << 3) + c - '0' , c = getchar();
	return ch == '-' ? -ret : ret;
}
int n;
char a[N];
int p[N];
ull ba[N],ha[N];
void gethash(char a[]){
	ba[0] = 1;
	for(int i = 1 ; i <= n ; i ++)
		ba[i] = ba[i-1] * base,
		ha[i] = ha[i-1] * base + a[i] - 'a' + 1;
}
inline ull Hash(int l,int r){
//	printf("gethash(%d,%d) : %llu\n",l,r,ha[r] - ha[l-1] * ba[r-l+1]);
	return ha[r] - ha[l-1] * ba[r-l+1];
}
void pre(char b[]){
	p[1] = 0;
	for(int i = 2 , j = 0 ; i <= n; i ++){
		while(j && b[j+1] != b[i]) j = p[j];
		if(b[j+1] == b[i]) j ++;
		p[i] = j;
	}
}
int kmp(char a[],char b[],int m){
	int ans = 0;
	for(int i = 1 , j = 0 ; i <= n ; i ++){
		while(j && b[j+1] != a[i]) j = p[j];
		if(b[j+1] == a[i]) j ++;
		if(j == m) ans ++ , j = p[j];
	}
	return ans;
}
int ans[N],tot;
inline bool check(int len){return kmp(a,a,len) > 2;}
void work(){
	scanf("%s",a+1);
	n = strlen(a+1);
	gethash(a);
	pre(a);
	tot = 0;
	for(int i = 1 ; i < n ; i ++)
		if(Hash(1,i) == Hash(n-i+1,n))
			ans[++tot] = i;
	int l = 0 , r = tot;
	while(l < r){
		int mid = (l + r + 1) >> 1;
		if(check(ans[mid])) l = mid;
		else r = mid - 1;
	}
	if(!l)puts("Just a legend");
	else {for(int i = 1 ; i <= ans[l] ; i ++)putchar(a[i]);puts("");}
}
signed main(){
		work();
	return 0;
} 

T1

肯定是Trie树啊。

找到多个字符串中,某些串的公共前后缀的最长值。相同时找出字典序最小的。

显然是正反分别存到字典树上,遍历字典树找到长度最大值。

问题就在这个“字典序最小的”上。

考试的时候只记得判断正向的字典序最小,但是反向的字典序最小需要多一些约束条件

期望得分:\(100\)

实际得分:\(90\)(反向字典序最小值完全没有考虑,还只掉10-pts)……

#include <bits/stdc++.h>
#define fo(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout)
using namespace std;
const int INF = 0x3f3f3f3f , N = 5e5+5 , base = 131;
typedef long long ll;
typedef unsigned long long ull;
inline ll read(){
	ll ret = 0 ; char ch = ' ' , c = getchar();
	while(!(c >= '0' && c <= '9')) ch = c , c = getchar();
	while(c >= '0' && c <= '9') ret = (ret << 1) + (ret << 3) + c - '0' , c = getchar();
	return ch == '-' ? -ret : ret;
}
int n;
int tr1[N][30],tr2[N][30];
int buc1[N],buc2[N];
ll sum1[N],sum2[N];
int pre1[N],pre2[N];
int cha1[N],cha2[N];
int tot1 = 1,tot2 = 1;
char ch[N];
void insert1(char a[]){
	int m = strlen(a+1);
	int p = 1;
	for(int i = 1 ; i <= m ; i ++){
		int c = a[i] - 'a' + 1;
		if(!tr1[p][c]) tr1[p][c] = ++tot1 , pre1[tot1] = p , cha1[tot1] = c;
		p = tr1[p][c];
		sum1[p] += buc1[p] ++;
	}
}
void insert2(char a[]){
	int m = strlen(a+1);
	int p = 1;
	for(int i = m ; i ; i --){
		int c = a[i] - 'a' + 1;
		if(!tr2[p][c])tr2[p][c] = ++tot2 , pre2[tot2] = p , cha2[tot2] = c;
		p = tr2[p][c];
		sum2[p] += buc2[p] ++;
	}
}

int ans1,pos1,ans2,pos2,minn = INF;
void dfs1(int p,int dep){
	if(!sum1[p])return;
	if(ans1 < dep)
		ans1 = dep,
		pos1 = p;
	for(int i = 1 ; i <= 26 ; i ++)
		if(tr1[p][i])
			dfs1(tr1[p][i],dep+1);
}
void dfs2(int p,int dep){
	if(!sum2[p])return;
	if(ans2 <= dep){
		if(ans2 < dep)
			ans2 = dep,
			pos2 = p,
			minn = cha2[p];//重点在这里!!!!
		else 
			if(cha2[p] < cha2[pos2])
				pos2 = p , 
				minn = cha2[pos2];
	}
	for(int i = 1 ; i <= 26 ; i ++)
		if(tr2[p][i])
			dfs2(tr2[p][i],dep+1);
}

void print1(int p){
	if(p == 1)return;
	print1(pre1[p]);
	putchar(cha1[p] + 'a' - 1);
}
void print2(int p){
	if(p == 1)return;
	putchar(cha2[p] + 'a' - 1);
	print2(pre2[p]);
}

signed main(){
//	fo("wordlist");
	n = read();
	for(int i = 1 ; i <= n ; i ++)
		scanf("%s",ch+1),
		insert1(ch),
		insert2(ch);
	sum1[1] = sum2[1] = 1;
	dfs1(1,0);
	print1(pos1);printf(" %lld\n",sum1[pos1]);
	dfs2(1,0);	
	print2(pos2);printf(" %lld\n",sum2[pos2]);
	return 0;
}

T3

考试的时候就不太会……于是打了个\(n^2\)的暴力。

对于每个节点,用set存储当前点的字符串,则转化为树上问题,可以从叶到根递归解决。

复杂度\(O(n^2).\)

因为考试的时候明确计算出复杂度是\(O(n^2)\)的,所以就没有考虑\(n\ge2\times10^3\)的数据,导致数组大小开的也是\(50\%\)的部分分写法……

后来发现这个代码,只把数组开大便可以得到\(80pts\).

缩小常数并减少计算后,可以AC此题。

期望得分:\(50\)

实际得分:\(50\)(与\(80\)\(100\)只差一线之隔……)

#include <bits/stdc++.h>
#define fo(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout)
using namespace std;
const int INF = 0x3f3f3f3f , N = 3e5+5;
typedef long long ll;
typedef unsigned long long ull;
inline ll read(){
	ll ret = 0 ; char ch = ' ' , c = getchar();
	while(!(c >= '0' && c <= '9')) ch = c , c = getchar();
	while(c >= '0' && c <= '9') ret = (ret << 1) + (ret << 3) + c - '0' , c = getchar();
	return ch == '-' ? -ret : ret;
}
int n;
struct Edge{int to,nxt;}e[N<<1];
int ecnt , head[N];
inline void add_edge(int& u,int& v){
	e[++ecnt] = (Edge){v,head[u]};
	head[u] = ecnt;
}
void write(ll x){
     if(x < 0) putchar('-'), x = -x;
     if(x > 9) write(x / 10);
     putchar(x%10+'0');
}
char ch[N],se[N][2];
set<string>s[N];
int ans[N];
typedef set<string>::iterator it;
void dfs(const int u,const int _f){
	for(register int i = head[u] ; i ; i = e[i].nxt){
		int &v = e[i].to;
		if(v == _f) continue;
		dfs(v,u);
		for(register it j = s[v].begin() ; j != s[v].end() ; ++ j)
			s[u].insert(se[u] + *j);
		s[v].clear();
	}
	ans[u] = s[u].size();
}
signed main(){
//	fo("readtree");
	n = read();
	scanf("%s",ch+1);
	string st;
	for(register int i = 1 ; i < n ; i ++){
		int u = read() , v = read();
		add_edge(u,v) , add_edge(v,u),
		se[i][0] = ch[i],st = se[i],
		s[i].insert(st);
	}
	se[n][0] = ch[n],st = se[n],
	s[n].insert(st);
	dfs(1,0);
	for(register int i = 1 ; i <= n ; i ++)
		write(ans[i]),putchar(' '); 
	return 0;
} 

T4

考试时候确实不会……口胡的贪心暴力打上去了……

得分:\(0\)


赛后:发现是思维被字符串禁锢了……这题和字符串没什么关系……

由于原序列可以由操作1看做是一个环,于是倍长一倍。

于是在倍长的这个序列上找到“最值得”的一个长度为\(n\)的序列(用hash判断字典序),后在此基础上加上前或后括号即可。

#include <bits/stdc++.h>
#define fo(a) freopen(a".in","r",stdin),freopen(a".out","w",stdout)
using namespace std;
const int INF = 0x3f3f3f3f , N = 1e6+5 , base = 131 , mod = 1e9+7;
typedef long long ll;
typedef unsigned long long ull;
inline ll read(){
	ll ret = 0 ; char ch = ' ' , c = getchar();
	while(!(c >= '0' && c <= '9')) ch = c , c = getchar();
	while(c >= '0' && c <= '9') ret = (ret << 1) + (ret << 3) + c - '0' , c = getchar();
	return ch == '-' ? -ret : ret;
}
int n;
char ch[N];
ll ha[N],ba[N];
void gethash(){
	ba[0] = 1;
	for(int i = 1 ; i <= n<<1 ; i ++)
		ba[i] = ba[i-1] * base % mod,
		ha[i] = (ha[i-1] * base + ch[i] - '(' + 1) % mod;
}
inline int Hash(int l,int r){return (ha[r] - ha[l-1] * ba[r-l+1] % mod + mod) % mod;}
int tot,a[N],sum[N];
multiset<int>s;
signed main(){
	scanf(" %s",ch+1);
	n = strlen(ch+1);
	for(int i=1;i<=n;i++)
		ch[i+n] = ch[i],
		tot += ch[i] == '(';
	for(int i = 1 ; i <= n << 1 ; i ++)
		a[i] = ch[i] == '(' ? 1 : -1,
		sum[i] = sum[i-1] + a[i];
	gethash();
	for(int i = 0 ; i < n ; i ++)
		s.insert(sum[i]);
	
	int pos = 0 ,num = (n - tot) - tot;
	for(int i = 1 ; i <= n ; i ++){
		s.erase(s.find(sum[i-1]));
		s.insert(sum[i+n-1]);
		if(*s.begin() - sum[i-1] + max(num,0) < 0) continue;
		if(!pos){pos = i ; continue ;}
		int l = 0 , r = n;
		while(l < r){
			int mid = (l + r + 1) >> 1;
			if(Hash(pos,pos+mid-1) == Hash(i,i+mid-1)) l = mid;
			else r = mid - 1;
		}
		if(l != n && ch[i+l] == '(' && ch[pos+l] == ')') pos = i;
	}
	if(num >= 0){
		for(int i = 1 ; i <= num ; i ++) putchar('(');
		for(int i = 1 ; i <= n ; i ++) putchar(ch[pos+i-1]);
	}
	else{
		num = -num;
		for(int i = 1 ; i <= n ; i ++) putchar(ch[pos+i-1]);
		for(int i = 1 ; i <= num ; i ++) putchar(')');
	}
	return 0;
}

赛后

总分\(90+100+50+0=240\),没有因自身错误导致的丢分。

而有约\(50\)分,思考更深入一些或许就可以拿到。

现在,做模拟赛已经有了自己大概的战术,争取下步更加稳定。

posted @ 2021-08-14 23:34  Last-Order  阅读(38)  评论(0编辑  收藏  举报