ISIJ 2018 奇怪的字符串(Training Round D6T1)
无忧公主 2018-07-10
题目描述
考虑字符串 s 仅由小写字母组成,例如 “abba”。定义 W(s) 为 s 所有本质不同的连续子串的集合,例如 W(“abba”) = { “a”,”b”,”ab”,”ba”,”bb”,”abb”,”bba”,”abba” }。定义 Y(s) 为 s 所有本质不同的非连续子串的集合,例如 Y(“abba”) = W(“abba”) ∪ { “aa”,”aba” },显然 W(s) 是 Y(s) 的子集。
对于一些奇怪的字符串 s 满足 W(s) = Y(s),例如 “abba” 就不是奇怪的,但 “abb” 是奇怪的因为 W(s) = Y(s) = { “a”,”b”,”ab”,”bb”,”abb” }。现在小明有一个字符串 s,请你求出 W(s) 中有多少个字符串是奇怪的?
注意:集合中的所有元素互不相同
限制
1s 256M
1≤|s|≤ 200,000
输入格式
一个字符串 s
输出格式
一个整数,表示 W(s) 中有多少个字符串是奇怪的
输入样例
abba
输出样例
7
样例解释
abba 的所有连续子串中,除了 abba 以外都是 ” 奇怪的 “
题解
通过观察可以发现,s 是 ” 奇怪的 ” 的条件是形如 aaaa 或 aaabbbb。对于连续的一段字母进行 “ 压缩 ” 扫描线处理,记录同种字母 k 的最大连续长度、字母 k1 在固定长度 c1 后面接字母 k2 的最大长度,以将本质不同(这是最麻烦的地方)的字符串区分开来。这些均可用数组和 map 维护(容易 MLE),并最后一边维护最大值一边统计答案。
#include <bits/stdc++.h>
using namespace std;
template <typename T> void read(T &t) {
char ch=getchar(); int f=1; t=0;
while ('0'>ch||ch>'9') { if (ch=='-') f=-1; ch=getchar(); }
do { (t*=10)+=ch-'0'; ch=getchar(); } while ('0'<=ch&&ch<='9'); t*=f;
}
typedef long long ll;
const int maxn=200010;
int n,c1,c2,a[30];
ll ans;
map<pair<int,int>,int> b[30];
struct node {
int k1,k2,c1,c2;
} d[maxn];
char s[maxn],k1,k2;
int main() {
scanf("%s",s+1); n=strlen(s+1);
int pos=0,cnt=0; s[0]=s[1];
for (int i=1;i<=n;i++) {
while (pos<n) {
if (s[pos]!=s[pos+1]&&cnt==1) break;
if (s[pos]!=s[pos+1]) {
cnt++;
k2=s[pos+1]; c2++;
} else if (c2) c2++; else k1=s[pos],c1++;
pos++;
}
d[i]=(node){k1-'a'+1,k2-'a'+1,c1,c2};
a[k1-'a'+1]=max(a[k1-'a'+1],c1);
if (s[i]!=s[i+1]) {
cnt--;
k1=k2; c1=c2; c2=0;
} else c1--;
}
for (int i=1;i<=26;i++)
ans+=a[i];
for (int i=1;i<=n;i++) {
if (d[i].c2==0) continue;
int tmp=b[d[i].k1][make_pair(d[i].c1,d[i].k2)];
b[d[i].k1][make_pair(d[i].c1,d[i].k2)]=max(tmp,d[i].c2);
ans+=max(0,d[i].c2-tmp);
}
printf("%lld\n",ans);
return 0;
}
但是,比如我这种蠢笨的,肯定是看不出来是矩形面积并的
所以
有一种新的办法
譬如我们先遇到了 aabb
然后我们遇到了新的 aaabbb
那么 aabb 肯定是被包含在了 aaabbb 中的,所以我们可以用 aaabbb 覆盖前面那个
但关键处理是
如果我们遇见了新的一个
aaaabb
怎么搞呢
很容易发现新增的方案就是,a多出的长度(相较于aaabbb)乘以aaaabb中b的长度,也就是说,多了四个a与两个b匹配的方案
但是问题又来了
譬如说
abbbb
aaaab
aaaabbbb
这个就有很难弄了,因为如果按照原来的方案计算就会发生重复
所以我们把所有的字符串记录下来,相同的字母按左右都以长度从大到小排序
那么左边从大到小排,后面就影响不到前面的了,我们只要比较右边大小的差值
即可
#include<bits/stdc++.h>
#define ll long long
using namespace std;
struct node{
int pre,nex;
bool operator < (const node x) const{
if(pre==x.pre) return nex>x.nex;
return pre>x.pre;
}
};
vector<node> mp[26][26];
vector<int> vv;
vector<char> cc;
//char s[200000];
string s;
ll num[200000],l,vis[26],cnt,ans;
int check(int b,int x,int y){
return x*(y-b);
}
int main(){
freopen("1.in","r",stdin);
freopen("strange.out","w",stdout);
// scanf("%s",s);//不知道为什么,在测评机上的时候用scanf就不会错,用cin会错
// l=strlen(s);
cin>>s;
l=s.size();
cout<<l<<endl;
num[0]=1;
vis[s[0]]=1,cnt++;
for(int i=1;i<l;i++){
if(s[i]==s[i-1])num[i]=num[i-1]+1;
else cc.push_back(s[i-1]),vv.push_back(num[i-1]),num[i]=1;
if(vis[s[i]]==0){
vis[s[i]]=1;
cnt++;
}
else{
if(num[i]>vis[s[i]]){
cnt+=num[i]-vis[s[i]];
vis[s[i]]=num[i];
}
}
}
cc.push_back(s[l-1]),vv.push_back(num[l-1]);
l=cc.size();
for(int i=0;i<l-1;i++) mp[cc[i]-'a'][cc[i+1]-'a'].push_back((node){vv[i],vv[i+1]});
for(int i=0;i<26;i++){
for(int j=0;j<26;j++){
l=mp[i][j].size();
if(l==0) continue;
sort(mp[i][j].begin(),mp[i][j].end());
int maxx=mp[i][j][0].pre,maxy=mp[i][j][0].nex;
ans+=maxx*maxy;
for(int k=1;k<l;k++){
if(mp[i][j][k].nex>maxy){
ans+=check(maxy,mp[i][j][k].pre,mp[i][j][k].nex);
maxy=mp[i][j][k].nex;
}
}
}
}
printf("%lld",ans+cnt);
}