奇怪的字符串

这篇博客讨论了ISIJ 2018比赛中关于奇怪字符串的问题,其中奇怪字符串满足其连续子串集合等于非连续子串集合。文章介绍了如何判断一个字符串是否奇怪,并提供了处理这类问题的思路,包括使用压缩扫描线和避免重复计数的方法。

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);
}
<think>首先,用户的问题是:“我想查找用于匹配包含空格或回车的字符串的正则表达式 请问正则表达式 匹配只有空格或换行符的字符串”。这是一个关于正则表达式的查询,用户想要一个正则表达式来匹配那些只包含空格或换行符(回车)的字符串。 根据系统级指令,我需要: - 所有行内数学表达式使用$...$格式。 - 独立公式使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 在回答中引用的段落末尾自然地添加引用标识,例如[^1]。 - 回答结构清晰,帮助用户逐步解决问题。 - 参考站内引用,但用户提供的引用是关于正则表达式的,所以我应该参考它们。 用户提供的引用: - [^1]: 解释了如何匹配任意字符(包括换行符),使用[\s\S]来匹配任何字符,因为\s匹配空白字符(包括空格、制表符、换页符等),\S匹配非空白字符。 - [^2]: 给出了一个例子,匹配电话号码,但提到了\d。 - [^3]: 在Java中,使用正则表达式匹配空格或换行符,并移除它们。代码示例:Pattern.compile("\\s*|\t|\r|\n"),但这里似乎有误,因为\s*匹配零个或多个空白字符,|表示或,\t、\r、\n分别匹配制表符、回车符、换行符。但用户的问题是匹配只有空格或换行符的字符串,而不是移除它们。 - [^4]: Java中匹配所有字符包括换行符,但具体内容是关于HTML字符串的。 用户的问题是匹配字符串,这些字符串只包含空格或换行符(回车)。在正则表达式中,回车通常用\r表示,换行用\n表示。空格用空格字符或\s表示。 关键点: - 匹配整个字符串,只包含空格或换行符(回车)。 - 这意味着字符串中不能有其他字符。 在正则表达式中,要匹配整个字符串,可以使用锚点:^表示字符串开始,$表示字符串结束。 空白字符可以用\s表示,它匹配空格、制表符、换行符、回车符等。具体来说,\s包括: - 空格(' ') - 制表符(\t) - 换行符(\n) - 回车符(\r) - 换页符(\f) - 垂直制表符(\v)等。 用户指定了“空格或回车”,回车是\r,但通常换行符\n也包括在内。在上下文中,用户说“空格或回车的字符串”,但回车(\r)是换行的一部分。在许多系统中,换行是\n(Unix)、\r\n(Windows)、或\r(Mac)。 为了安全,我应该使用\s,因为它覆盖了所有空白字符。 用户说“包含空格或回车的字符串”,所以可能只包括空格和回车,不包括其他空白如制表符。但用户没有指定,所以最好使用\s来匹配所有空白字符,或者根据需求定制。 用户说“空格或换行符”,在问题中:“匹配只有空格或换行符的字符串”。在用户输入中,是“空格或回车”,回车可能指\r。 为了精确,我可以使用字符类来指定空格和换行符。 在正则表达式中,空格可以用' '表示,换行符用\n表示,回车用\r表示。但为了匹配整个字符串只包含这些,我需要一个正则表达式来匹配从开始到结束只有这些字符。 所以,正则表达式应该是:^[\s]*$ 或 ^[\r\n ]*$ 但\s包括空格、制表符等。用户可能只想要空格和换行符。 从用户引用[^3],他们使用了"\\s*|\t|\r|\n",但那是在匹配单个空白字符,而不是整个字符串。 用户的问题是匹配整个字符串只包含空格或换行符。 所以,正则表达式:^[ \r\n]*$ 解释: - ^:字符串开始 - [ \r\n]*:零个或多个空格、回车或换行符 - $:字符串结束 或者,使用\s,但\s包括制表符等。如果用户只想要空格和换行符,我应该指定。 在用户引用[^1],提到了\s匹配空白字符,包括空格、制表符、换页符等。 但在用户问题中,是“空格或换行符”,所以可能不包括制表符。 为了安全,我可以提供两种方式:一个只匹配空格和换行符,另一个匹配所有空白。 但用户说“空格或回车”,回车是\r,换行符是\n。在许多上下文中,回车和换行是相关的。 在正则表达式中,\r匹配回车,\n匹配换行。 所以,正则表达式可以是:^[ \r\n]*$ 这匹配字符串只包含空格、回车或换行符。 或者,使用\s,但\s包括制表符,所以如果用户不想包括制表符,应该避免。 在回答中,我应该解释清楚。 现在,关于引用:我需要参考用户提供的引用。 从引用[^1],它说可以使用[\s\S]来匹配任意字符,但这里不是匹配任意字符,而是匹配特定字符。 引用[^3]有代码:Pattern.compile("\\s*|\t|\r|\n"),但这有语法错误,因为|是或操作符,所以它匹配\s*或\t或\r或\n,这很奇怪。可能应该是[\s\t\r\n]或类似的东西。 在[^3],是"\\s*|\t|\r|\n",这可能是打字错误;可能应该是"\\s|\\t|\\r|\\n"或"[\\s\\t\\r\\n]"。 但在上下文中,它是用于移除空格和换行符。 对于用户的问题,匹配整个字符串只包含这些字符。 所以,正则表达式可以是:^\s*$ 但\s包括制表符。 如果用户只想要空格和换行符,则:^[ \n\r]*$ 在JavaScript或其他语言中,可能需要处理多行模式,但既然匹配整个字符串,^和$应该工作。 在正则表达式中,^和$默认匹配字符串的开始和结束,但在多行模式下,它们匹配行的开始和结束。但这里,用户是匹配整个字符串,所以不需要多行模式。 所以,正则表达式:^[ \r\n]*$ 解释:[ \r\n]匹配一个空格、回车或换行符,*表示零个或多个,所以整个字符串只包含这些字符。 为了更精确,回车是\r,换行是\n,空格是' '。 在回答中,我需要用中文解释。 结构回答: 1. 理解问题:用户想要匹配只包含空格或换行符(回车)的字符串。 2. 提供正则表达式:^\s*$ 或更具体的 ^[ \r\n]*$ 3. 解释为什么:\s匹配所有空白字符,包括空格、制表符、换行符等。如果只想空格和换行符,使用[ \r\n]。 4. 参考引用:例如,从引用[^1],\s匹配空白字符,包括换行符。 5. 生成相关问题。 生成相关问题: 根据系统指令,需要生成相关问题,像示例中那样。 例如: 1. 如何匹配只包含空格的字符串? 2. 如何匹配只包含换行符的字符串? 3. 在正则表达式中如何处理多行字符串? 4. 如何检查字符串是否为空或只包含空白? 5. 在不同编程语言中如何使用这个正则表达式? 现在,回答结构: - 开头:直接回答问题。 - 解释正则表达式。 - 提供示例。 - 引用相关部分。 - 结束于相关问题。 使用LaTeX:由于是正则表达式,不是数学,但正则表达式可以视为文本,不需要LaTeX格式。系统指令说“行内数学表达式”使用$...$,但正则表达式不是数学,所以可能不需要。但为了安全,我可以将正则表达式放在代码块中,像示例中那样。 在示例中,代码是使用```code```格式。 所以,我可以写正则表达式在代码块中。 例如: 正则表达式:`^\s*$` 或者为了更清晰。 在行内,如果提到,可以用$...$,但正则表达式通常不是数学。 系统指令指定“数学表达式”,所以正则表达式可能不需要LaTeX。但为了符合,我可以用$...$包裹正则表达式,但可能不必要。最好遵循精神:使用代码块或行内代码。 在用户提供的引用中,他们直接写了正则表达式。 在回答中,我可以写:正则表达式是 `^\s*$` 现在,写回答。</think>### 匹配只包含空格或换行符的字符串的正则表达式 您的需求是匹配那些整个字符串包含空格(空格字符)或换行符(包括回车符 `\r` 和换行符 `\n`)的字符串。这意味着字符串中不能有其他任何字符(如字母、数字、标点符号等)。在正则表达式中,这可以通过锚点(`^` 表示字符串开始,`$` 表示字符串结束)和字符类来实现。下面我将逐步解释并提供解决方案。 #### 步骤1: 理解正则表达式的关键元素 - **空格字符**:在正则表达式中,空格可以直接用空格字符 ` ` 表示,或使用预定义字符类 `\s`(但 `\s` 匹配所有空白字符,包括空格、制表符 `\t`、换行符 `\n`、回车符 `\r` 等[^1])。 - **换行符和回车符**:换行符通常用 `\n` 表示,回车符用 `\r` 表示(在Windows系统中换行常为 `\r\n`,但正则表达式可以单独处理)。 - **匹配整个字符串**:使用 `^` 和 `$` 来确保从字符串开始到结束只包含指定字符。 - **字符类**:使用 `[...]` 来定义允许的字符合,`*` 表示零个或多个重复。 根据您的描述(只匹配空格或换行符,不包括其他空白字符如制表符),我会提供两个版本的正则表达式: - **精确版本**:只匹配空格、回车符 `\r` 或换行符 `\n`(不包括制表符等)。 - **通用版本**:匹配所有空白字符(包括制表符),使用 `\s`,这在某些场景下更实用。 #### 步骤2: 推荐的正则表达式 以下是针对您需求的正则表达式,我将在代码块中展示,并解释其含义。 **精确版本(只匹配空格、回车符或换行符)**: ```regex ^[ \r\n]*$ ``` - **解释**: - `^`:匹配字符串的开始。 - `[ \r\n]`:字符类,匹配一个空格、回车符 `\r` 或换行符 `\n`。 - `*`:表示前面的字符类可以出现零次或多次(即字符串可以是空字符串,或只包含这些字符)。 - `$`:匹配字符串的结束。 - 这个正则表达式确保整个字符串只包含空格、`\r` 或 `\n`,没有其他字符。 **通用版本(匹配所有空白字符,包括制表符)**: ```regex ^\s*$ ``` - **解释**: - `\s`:预定义字符类,匹配任何空白字符,包括空格、制表符 `\t`、换行符 `\n`、回车符 `\r` 等[^1]。 - 这个版本更简洁,覆盖所有空白字符,但如果您不需要制表符,请使用精确版本。 #### 步骤3: 示例和测试 - **示例字符串**: - 有效:`" "`(只含空格)、`"\n"`(只含换行符)、`"\r\n"`(只含回车和换行)、`" \n \r "`(混合空格和换行符)。 - 无效:`"hello"`(含字母)、`"123"`(含数字)、`" \t "`(含制表符,如果使用精确版本)。 - **测试方法**(以JavaScript为例,其他语言类似): ```javascript // 测试精确版本 const regex = /^[ \r\n]*$/; console.log(regex.test(" ")); // true: 只含空格 console.log(regex.test("\n\r")); // true: 只含换行符和回车符 console.log(regex.test("")); // true: 空字符串(零个字符) console.log(regex.test("a")); // false: 含字母 console.log(regex.test(" \t ")); // false: 含制表符(精确版本不匹配制表符) // 测试通用版本 const regexGeneral = /^\s*$/; console.log(regexGeneral.test(" \t \n")); // true: 含制表符、空格和换行符 ``` #### 步骤4: 注意事项 - **空字符串处理**:上述正则表达式也匹配空字符串(`""`),因为 `*` 允许零次重复。如果您需要字符串至少有一个字符(例如至少一个空格或换行符),可以将 `*` 改为 `+`(例如 `^[ \r\n]+$` 或 `^\s+$`)。 - **编程语言实现**:在不同语言中,正则表达式语法一致,但转义可能不同(如Java中需双反斜杠 `\\s`)。参考引用[^3],在Java中移除空格和换行符时使用了类似模式,但您的需求是匹配而非移除。 - **性能**:这个正则表达式简单高效,时间复杂度为 $O(n)$(n为字符串长度),适合大多数场景。 - **引用说明**:从引用[^1]可知,`\s` 匹配所有空白字符包括换行符,这支持了通用版本的可靠性[^1]。如果您只想匹配空格和换行符(不包括制表符),精确版本更符合您的描述。 #### 相关问题 如果您有其他相关疑问,以下是基于此主题的扩展问题: 1. 如何匹配只包含空格的字符串(不包括换行符)? 2. 在正则表达式中,如何匹配一个或多个换行符(不含其他字符)? 3. 如何检查字符串是否为空或只包含空白字符(包括制表符)? 4. 在不同编程语言(如Python、Java)中,如何实现这个正则表达式的匹配? 5. 正则表达式匹配空白字符串时,如何处理多行文本(例如匹配整个文件只含空白行)?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值