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\)分,思考更深入一些或许就可以拿到。
现在,做模拟赛已经有了自己大概的战术,争取下步更加稳定。