正则表达式的使用本身在ACM竞赛中并不是一种使用率非常高的技巧。因为凡是使用正则表达式能做到的,只要我们愿意,我们大可以多敲几行代码来实现。但是,使用了正则表达式后就能是我们的代码更加精简,而且效果非常显著。(代码更精、运行内存更小、时间更短这不就是我们做事的目标和意义吗?)在这里我就从我在这一周中碰到的三个问题来讨论正则表达式的活用。
首先,是一个我找到的讲解正则表达式很好的一个博客:http://blog.csdn.net/huangxy10/article/details/8117870
首先是codeforces697B题:
这个题如果只是单纯的想从AC的角度来看并不难,但是,如何使代码更加精简却是一种技术。
在下面这个代码中的输出部分还用到了一个平时不常用的一个技巧:
对于printf("%*.*s",dlen,len,s)中的输出结果是输出字符串s中的len位,而且输出后所占的宽度为dlen。
小数点.后“*”表示输出位数,具体的数据来自参数表
printf格式字符串中,与宽度控制和精度控制有关的常量都可以换成变量,方法就是使用一个“*”代替那个常量,然后在后面提供变量给“*”。
同样,小数点.前也可以添加*,也要用户输入一个位宽值来代替,表示输出的字符所占位宽。
如果是printf("%*.*d",dlen,len,5)中就是输出一个宽度为dlen,长度为len的一串数字0,最后一位数是5。
先是给大家精简的代码:
#include
int main() {
int a, dlen, b;
char d[101];
while(scanf("%d.", &a)!=EOF){
//题中就已经说明了a 的范围只有0~10所以用这种输入方式就能很好的就收a。
scanf("%[^e]%ne%d", d, &dlen, &b);
/*这里就用到了正则表达式,在d部分是一个长度为100的小数部分,在输入完
字符串d之后,接着就是输入一个字符'e'所以,用'%[^e]'就能顺利的就收字
符串d这样就省去了后续的处理。%n接收的并不是输入而是在此之前输入的长
度,在这里就是接收字符串d的长度并且赋值给dlen。然后加上'e'来跳过在
接收完字符串d之后的字符'e',从而使用%d来接收'e'之后的正数赋值给b。*/
//在我个人看来光只是这一个输入部分的活用就足够使人佩服了。
if (dlen == 1 && d[0] == '0' && b == 0)
printf("%d\n", a);
/*因为在输入过程中就已经把所有的处理都完成了。所以,在输出的部分就相当
方便只需要把这个特例进行输出就行了。(d的小数部分和指数部分都是'0'。)*/
else
if (b >= dlen)
printf("%d%s%.*d\n", a, d, b - dlen, 0);
//当b的数值比字符串d的长度更大时就是不断的往后补0,而且补0 的数量就是b-dlen。
else
printf("%d%.*s.%s\n", a, b, d, d + b);
/*%.*s对应的就是b,d表示输出的字符串d的前b位。之后,在输出一个'.'后,%s,对应
d+b将字符串d从b为开始往后输出,直至结束。(不过这个题中貌似不需要消除后导0)*/
}
return 0;
}
接下来就是前几周的一个codeforces题:
这只是简单的模拟,首先总价格和商品的名称没有任何关系,只需要找出其中的数字部分判断其中是否是有小数部分并且加到sum中。最后,按要求输出即可。(我的思路是打算在找数字的过程中就直接存储在一个数组中并且相加,在存储过程中只需注意进位即可,最后,在输出过程中没输出以为在输出一个点’.‘就行了。)
我的代码太麻烦太长太差就不贴了,只上别人的精简代码了:(对于这个题我也看了很多代码,这个代码是最精简的了。)
#include
using namespace std;
#define ll long long
ll a,b,c;
char s[1005];
char ans[2000];
int main(){
//freopen("数据输入.IN","r",stdin);
while(scanf("%*[^0-9]%[.0-9]",s)!=EOF){
//printf("%s\n",s); //这句备注在下文会提到。
/*在这个输入语句中就是充分使用了正则表达的典型,%*[^0-9]
表示了先跳过了对非数字字符的接收,(也就是跳过了对商品名
称的接收。)用%[.0-9]表示只对数字字符和'.'进行接收。
我们暂时可以先当做它只输入一个商品名称和对应价格,之后在说
明为什么可以这么做*/
int len=strlen(s);
//实际上只要在输入中插入一个%n就可以把这步省略。
if(len>=4&&s[len-3]=='.'){
b+=(s[len-2]-'0')*10+(s[len-1]-'0');
len-=3;
}
/*用这个来判断最后两位是否是小数,如果是小时就计算加到b中。
并且使len减3,因为在计算整数部分时小数部分不能计入其中。*/
c=0;
for(int i=0;iv;
while(a){
v.push_back(a%1000);
a/=1000;
} //开一个数组v用来把整数部分分成每部分三位数存储。
if(v.size()==0)printf("0");
//当v数组为空时就是总和为0的时候,只需输出一个0即可。
for(int i=v.size()-1;i>=0;i--){
if(i==v.size()-1)printf("%I64d",v[i]);
else printf(".%03I64d",v[i]);
}
//按题目要求输出整数部分每三位有一个点'.'。
if(b%100)printf(".%02I64d",b%100);
//最后输出小数部分,小数部分宽度为2当不足时用0补上。
return 0;
}
把我的备注加上后貌似感觉不出简短啊。- _- iii
在这个代码中虽然不如前697B的代码那么精简,但是,它在输入中对正则表达式的使用就非常的神奇。因为,在codeforces中测试数据每次都是单组输入的,(这也就是为什么有时候在codeforces上写的代码不需要循环输入的原因。)它不像很多高校的OJ那样输入多组测试用例。而至于EOF实际上就是保证每次读到文件末尾,所以,假如我们备注去掉在输入文本中的输入语句如下图:
图(1)输入文本中的内容
最后的输出结果是如下图:
图(2)输出结果
在这里我们发现在代码中是整个字符串一起进行处理。而且,在最后的时候还是成功得出了答案“478.666.49”。因为,“!=EOF”的作用是读到文本末尾。也就是说在第一个while循环中就已经处理了所有的字符串,在文档中的内容接收并处理结束后就跳出循环开始进行输出。
不过还是要注意:这种接收数据的方式,只适用于那些单组测试数据。如果是像HUD,POJ那些输入的是T组测试用例或者说是多组测试用例就并不适用。所以,很多时候有关scanf中使用正则表达式还是带有一定的局限性。我在做codeforces 的8A题时更是感觉到了其中的局限性。(当然,其实我相信用正则是可以做到的。只是我的水平太有限了。所以,无法在下面这题中也使用正则表达式。)
对于这个题,我不打算贴出正确的代码。我只是想通过这个题来说明,在scanf中使用正则表达式并不是万能的。
我原本打算通过sscanf输入来处理已接收的字符串s,a或b。这样只需判断最后被赋值的字符串是否是一个空串就能解决问题了。于是我就尝试了再%[str]
中能否把某个变量的字符串str放到"[]"中进行使用。于是我就实验了一下,我在试验中发现对于sscanf(s,"%*[^a]%s",ch1);中的a它无法以字符串的形式出现在其中这样也就无法像前面两个题那样直接跳过与字符串a相匹配的部分了。
接下来再总结一下:如果想把正则表达式应用在输入中来精简代码就必须是题中已经给出了限定条件,即:题中已经说明了输入格式是固定的前提下。否则不适用。
以上都只能算是我个人对正则的理解,虽然如此,但我还是觉得codeforces8A这个题用正则输入的能解的,而且非常的精简。如果有幸有哪位大神在看完了我的扯淡后能够给出一个正则做法。那我希望您能够私信将代码发给我,我也将会非常高兴。谢谢。(我要的是codeforces8A的正则做法,不是codeforces8A 的答案。)