Fork me on GitHub

HDU1686-Oulipo

继续跟邝斌飞KMP ——(之前都是POJ多,现在KMP居然大部分都是HDOJ的)

HDU1686 来源:华东区大学生程序设计邀请赛_热身赛,AC数没啥参考价值,不知道是不是不是那场比赛的提交也算上了,AC人数最多,但AC率排行榜5/9

POJ3461 来源:BAPC 2006 Qualification

 

题意:

主串 和 子串 均为大写字母A~Z,

子串叫W,称之为单词,长度:1≤|W|≤10^4,

主串叫T,称之为文本,长度:W|≤|T|≤10^6

没空格之类的不可见字符,原文: And they never use spaces. 

原文描述: So we want to quickly find out how often a word, i.e., a given string, occurs in a text ,i.e是 “即” 的意思

这题不同之处是,原文描述: Occurrences may overlap. 即可能会重叠,上一个题剪花布条 里的样例 “aaaaaa aa” 输出3,按照这个题的题意应该输出5

 

 

读完题立马起手写,感觉就是签到题啊,这不白送气球一样嘛

 

我靠不对啊,如果数据是:

位置0  1  2  3  4  5  6  7  8

主串:a  a  a  a  a  a  a  a  a

子串:a  a  a  a

这tm要朴素暴力啊子串匹配 0123 位置后,应该去匹配 1234,然后 2345,KMP 是把不匹配的做处理,所以 next 数组应运而生,那这种重复匹配的,咋感觉要发明新算法,新 next 数组了呢

好简单,只需要在上个题的基础上,j 不回退到0,回退到 next 的值就行了

用例子一想就出来了,子串为:ABABC,去你妈的想错了,因为 ABABC 到 C 这如果匹配了,该重新让主串退到能跟子串头匹配的位置去,可是这C的 next 是2,没用啊,这2表示的 子串的前两个字符 也没法保证跟主串后面的或者前面的哪里匹配啊。

妈的咋能让主串的每一位,都存一个数值,这个数值表示当前位置的前面,最往前可以在哪个位置,跟子串头的那个字符匹配,难道让老子发明KMP或者改进KMP吗

位置0  1  2  3  4  5  6  7  8

主串:a  a  a  a  a  a  a  a  a

子串:a  a  a  a

01234匹配成功后,主串 4 位置应该有个东西记录他的前面,最往前,是1位可以跟子串头匹配,那主串就退到1重新跟子串匹配。

那咋感觉也没啥用呢?如果单独匹配上头也没啥意义啊,仿佛应该也想子串一样找个 next

艹有了,那就对主串做一次 next ,匹配失败正常按照KMP来,如果匹配成功,子串一定是到了末尾,上个题是直接继续在这个位置匹配,但这题可以重复,那主串就退到 主串这个位置存的 next 那去。

那对于上面的例子,主串 next_a[4] 是 3,应该退到 1 的位置,咋能是 1 ,那我就构造位置 4 减去 next_a ,4-3==1

再试一个,

位置0  1  2  3  4  5  6  

主串:a  b  a  b  a  b  a  

子串:a  b  a

这个匹配成功后,匹配函数里,主串应该是到 3 了,主串的 next_a 是 1 ,位置 3 减去 next_a[3] 的值 1 就是 2 。完美。

其实想想也成立,就是当前主串跟子串匹配成功后,主串会加 1 ,指针到主串的下一个位置,简称 P位置,这 P 位置正好记录了前面的最长前后缀长度,其实就是 子串的头 可以跟 P 位置前面哪个位置能匹配,并且能保证一直可以匹配到 P 位置之前。

但想两个事:

0、这只是头,那等到子串拉到主串后面的时候,还能适用了吗,因为你主串记录的是你主串的前后缀啊,可是等子串拉到后面的时候,跟主串前缀的那些字母就没关系了啊。

举个例子:

主串:a b a c m n a b a b a

子串:a b a

当匹配到

位置:0  1  2  3  4  5  6  7  8  9  A

主串:a  b  a  c  m  n  a  b  a  b  a

子串:                         a  b  a

的时候,显然匹配函数里,主串指针应该指向 9位,子串指针应该指向自己的头了,那次是主串指针应该去指向 8 位了啊,你主串 next_a 有个毛用处啊。

1、要的是P位置前面的最前,或者说距离 P位置 最远,可是这么搞主串只是记录了前面最近的啊,

随便搞个例子:

主串:a a a a a a a

子串:a a a

就可以发现问题,不过严格广义上来说这个问题是0、那个的,但其实狭义上这个角度也说得通,因为题目求有几个重复的,也就是最多能匹配上几个串,例子不好举,但描述来就是:

我子串跟主串匹配,子串头从主串的哪个位置开始能匹配上的,往后一直能匹配到子串末尾,就算匹配成功了一个,那主串通过匹配函数,也到了下一个位置,

好,现在我就应该去找这个主串最远的位置,如下图:

就是说,我子串跟主串的 23456 位匹配成功后,由于上面的0、已经说了主串的 next_a 没用,那就只求子串的,那我现在给子串 01234 这几个位后再来个虚拟位,比如叫 5 位,5位会记录 next_b ,假设 next_b[5] 是 2 ,表示子串的 01位 == 34位 , 我可以知道,主串的 23位 == 56位 ,

好了,现在就可以知道我接下来该怎么匹配了,(上一个题目正常的 KMP 不考虑重复或者叫 重叠 ,应该是主串 7位 跟子串的 0位 比较)显然是应该去尝试子串的 2位 和主串的 7位 比较,应为根据前面假设,子串 next_b[5] 是 2 ,子串 01 == 34 ,主串 23 == 56 ,推理出 子串01位 == 子串34位 ==主串23位 ==主串56位 ,那子串 01位 一定等于主串 56位 ,可是现在要说的就是 1、 里考虑的问题,

这个 next_b 只是记录了最长前后缀长度,前缀没问题,就是指的是子串头,但是后缀就有问题了啊,后缀指的是从最后一位往前去数位数,即最近的,可是我算重叠,不应该考虑最远的吗?即中间的某位如果也可以应该也要去尝试匹配啊

如上面图的例子,我  01 必然跟 56 相等,就直接 子2 去比较 主7,那我有没有可能 01 还跟 45 相等啊,这个是你 next_b 根本不会记录的啊,你只记录的是距离你尾巴最近的,即4、43、432、4321这些,倒过来说就是1234、234、34、4,即1234跟0123相等或者234跟012相等或者34跟01相等或者4跟0相等。但对于 01 是否能和 45 相等不清楚啊?如果 01 和 45 相等,那是不是就 01 跟主串 45 相等然后如果都成立,就 子01234 会匹配上 主45678 ,那重叠的 ans 不就多了一个吗?

仔细想想并不是这样,之前学KMP的博客里,去搜“数学理论严谨的证明”,跟那个思想差不多,如果 01234 会匹配上 45678 能匹配,一定有

那就表明自己的 012位 等于自己的 234位 ,next_b[5]就是 3 了,与next_b[5]假设的 2 矛盾,到这一下豁然开朗,也彻底明白了那篇博客里的“数学理论严谨的证明”

 

至此懂了,匹配成功后直接让子串的虚拟位的 next_b 来跟主串的 i 比较,如果你问 i 是啥,i 匹配到哪了,连这个你不知道的话说明连最基本的KMP匹配都不懂,看我剪花布条的 博客

 

写完TLE了,用的是上一个题的string直接cin写法,百度说getline也会耗时,换成数组试试 (本来想做个测试,cin、scanf、getline分别读取10^6个数据,是多少,但不好构造数据,先搁置)

想到 上个题 的这句话 “但这道题输入形式是两个串的中间没空格,那我如果用比较耗时的cin一次直接读取整个的string,会不会比,虽然读取速度很快,但要一个一个读然后赋值的scanf好一些

 

中间插曲:

不知道为啥,freopen读文件的时候,如果输入数据最后没回车,按理说应该等待啊,但程序仿佛死循环或者哪里下标溢出了什么的,鼠标转圈,返回的是负数

注意我txt里鼠标位置,没回车

加上回车就没事

这个玄学问题之前食物链遇到过, 搁置吧,感觉研究不明白,网上也查不到,也暂时感觉没啥意义

 

感觉写了个完美的代码

复制代码
 1 #include<stdio.h>
 2 #include<string.h>
 3 #include<iostream>
 4 using namespace std;
 5 char b[10001+1];//子W
 6 char a[1000001];//主T
 7 //string W;// 8 //string T;//
 9 int ne_b[10001+1];
10 int main()
11 {
12 //    freopen("zhishu.txt","r",stdin);
13     int Case;
14     int flag;
15 //    cin>>Case;
16     scanf("%d",&Case);
17     flag=Case;
18     while(Case--){
19 //        cin>>W;
20 //        cin>>T;
21         //为了防止混淆,重新处理下
22 
23 //        string a;
24 //        string b;
25 //        a=T;
26 //        b=W;//方便理解,a主,b子
27 //        int len_a=a.length();
28 //        int len_b=b.length();
29 
30         int len_a=0;//
31         int len_b=0;//
32         char str;
33         if(flag-1 == Case)
34             getchar();//吃Case后的回车,注意Case只有第一次的时候有输入
35 
36         while(scanf("%c",&str)){//先输入的是子
37             if(str=='\n')
38                 break;
39             b[len_b]=str;
40             len_b++;
41         }
42 //        for(int i=0;i<len_a;i++)
43 //            cout<<W[i];
44 //        cout<<endl;
45 
46         while(scanf("%c",&str)){//后输入主
47             if(str=='\n')
48                 break;
49             a[len_a]=str;
50             len_a++;
51         }
52 //        for(int i=0;i<len_b;i++)
53 //            cout<<T[i];
54 //        cout<<endl;
55 
56 //        对子串 a 求 next
57         int k=-1;
58         int j=0;
59         ne_b[0]=-1;
60         while(j<len_b){
61 
62             if(k==-1 || b[j]==b[k]){
63                 j++;
64                 k++;
65                 if(b[j]==b[k])
66                     ne_b[j]=ne_b[k];
67                 else
68                     ne_b[j]=k;
69             }
70             else
71                 k=ne_b[k];
72         }
73 
74 //        匹配
75         int ans=0;
76         int i=0;
77         j=0;
78         while(i<len_a){
79             if(j==len_b){
80                 j=ne_b[j];
81                 ans++;
82 //                cout<<"#"<<i<<" "<<j<<endl;
83             }
84             if(j==-1 || a[i]==b[j]){
85                 i++;
86                 j++;
87 //                cout<<"@"<<i<<" "<<j<<endl;
88             }
89             else
90                 j=ne_b[j];
91         }
92         if(i==len_a && j==len_b)
93             ans++;
94         printf("%d\n",ans);
95 //        cout<<ans<<endl;
96 
97     }
98 }
View Code
复制代码

但还是TLE

 

看了下vjudge的讨论,里面有句“strlen()提出循环就不tle了。。。”,感觉纯属放屁,就是要在循环内每次求长度的,不过strlen忘记是啥了,

插曲开始

先问了下百度关于求数组长度的方法,方法二的模版元没管他不想研究

 

又问了下文心GPT

学到几个知识,总结下就是:

#0、size_t作为无符号整型,一般用作存储数组长度啥的,溢出会绕回 0,其余说的一堆没看懂也不想去研究

#1、关于几个长度的东西

自己总结:

size() 在容器vector那用的,之前确实用过,博客搜size,vector存图那,遍历的时候用过.size。string获取长度可以用size和length一样的

sizeof() 是一个运算符,就memset那用过,用于返回类型大小,其他没用过没必要记

length() C++字符串string获取长度的

strlen() 用于C以为\0结尾的字符数组长度

GPT总结:

size() 用于STL容器,返回元素数量

sizeof() 是运算符,获取类型大小

legnth() 字符串string长度

strlen 是C风格字符串返回,只能是C里以为 '\0' 结尾的字符串(不包括 '\0' ),整型不行,整型理论上可以这么写

int aaa[5]={3,4,5};
int length = sizeof(aaa) / sizeof(aaa[0]);
cout << length << endl;

注意:输出5

#1.1、针对求字符数组长度,又问了下GPT,单独拎出来再说下,有 strlen 和 sizeof

但 strlen 是从第一个字符到 \0 结束,不包括 \0

而 sizeof 是计算数组或者类型所占内存大小,单位是字节,但他返回的包括 \0,即

char aaa[]="sdf";
cout<<sizeof(aaa)<<endl;

输出4

且由于数组大小是固定的,在定义时就确定了,所以无法获得\0之前占多长,只能获得整个数组占多少内存,即

char aaa[12]="sdf";
cout<<sizeof(aaa)<<endl;

输出12

至于 size() 和 length,不是用于计算字符数组长度的函数,

string 里有 size 和 length 内置的成员函数。但 size 和 length 可不是内置的

#2、运算符:

算数运算符:+ - * /等等

关系运算符:用于比较两个值的大小是否相等:== != > <等等

逻辑运算符:

与 非等等

赋值运算符

= += *=等等

条件三目运算符

其他运算符:逗号运算符,指针运算符,地之大小运算符sizeof

插曲结束

 

麻痹的看了个博客,(但他文章里说:“分析:先来个模板题”??这是模板题???看了他的下面剪布条的题解发现好像正常KMP就是这个题的,上个题剪布条那种不重叠的才是与众不同的,好吧,反正都练习到了)

我居然忘记C语言可以有字符串%s这玩意直接输入。更改后直接AC

AC代码 —— POJ HDOJ两大平台均可AC 注意:cin cout 关同步,比scanf快好多好多,原因下面有解释

复制代码
 1 #include<stdio.h>
 2 #include<string.h>
 3 #include<iostream>
 4 using namespace std;
 5 char b[10001+1];//子W
 6 char a[1000001];//主T
 7 int ne_b[10001+1];
 8 int main()
 9 {
10     int Case;
11     int flag;
12     scanf("%d",&Case);
13     flag=Case;
14     while(Case--){
15 
16         int len_a=0;//
17         int len_b=0;//
18         char str;
19 //        if(flag-1 == Case)
20 //            getchar();//吃Case后的回车,注意Case只有第一次的时候有输入
21 
22         scanf("%s%s",b,a);
23 //        cout<<a<<" "<<b<<endl;
24 //        while(scanf("%c",&str)){//先输入的是子
25 //            if(str=='\n')
26 //                break;
27 //            b[len_b]=str;
28 //            len_b++;
29 //        }
30         len_a=strlen(a);
31         len_b=strlen(b);
32 //        cout<<len_a<<" "<<len_b<<endl;
33 //        while(scanf("%c",&str)){//后输入主
34 //            if(str=='\n')
35 //                break;
36 //            a[len_a]=str;
37 //            len_a++;
38 //        }
39 
40 //      子串 a 求 next
41         int k=-1;
42         int j=0;
43         ne_b[0]=-1;
44         while(j<len_b){
45 
46             if(k==-1 || b[j]==b[k]){
47                 j++;
48                 k++;
49                 if(b[j]==b[k])
50                     ne_b[j]=ne_b[k];
51                 else
52                     ne_b[j]=k;
53             }
54             else
55                 k=ne_b[k];
56         }
57 
58 //      匹配
59         int ans=0;
60         int i=0;
61         j=0;
62         while(i<len_a){
63             if(j==len_b){
64                 j=ne_b[j];
65                 ans++;
66             }
67             if(j==-1 || a[i]==b[j]){
68                 i++;
69                 j++;
70             }
71             else
72                 j=ne_b[j];
73         }
74         if(i==len_a && j==len_b)
75             ans++;
76         printf("%d\n",ans);
77     }
78 }
复制代码

总结来说,

0、string 类型用输入一个一个char拼接超级慢,必然TLE

1、两个 string 直接连 cin,依旧会TLE,但我凌晨3:46提交试了下,这段代码982ms,真的是人少就会快点,但终究是不可取,再也没A过

复制代码
 1 #include<stdio.h>
 2 #include<string.h>
 3 #include<iostream>
 4 using namespace std;
 5 string a;
 6 string b;
 7 //char b[10001+1];//子W
 8 //char a[1000001];//主T
 9 int ne_b[10001+1];
10 int main()
11 {
12     int Case;
13     int flag;
14     scanf("%d",&Case);
15     while(Case--){
16 
17         int len_a=0;//
18         int len_b=0;//19 //        char str;
20 //        if(flag-1 == Case)
21 //            getchar();//吃Case后的回车,注意Case只有第一次的时候有输入
22 
23 //        scanf("%s%s",b,a);
24         cin>>b;
25         cin>>a;
26 //        cout<<a<<" "<<b<<endl;
27 //        while(scanf("%c",&str)){//先输入的是子
28 //            if(str=='\n')
29 //                break;
30 //            b[len_b]=str;
31 //            len_b++;
32 //        }
33         len_a=a.length();
34         len_b=b.length();
35 //        cout<<len_a<<" "<<len_b<<endl;
36 //        while(scanf("%c",&str)){//后输入主
37 //            if(str=='\n')
38 //                break;
39 //            a[len_a]=str;
40 //            len_a++;
41 //        }
42 
43 //      子串 a 求 next
44         int k=-1;
45         int j=0;
46         ne_b[0]=-1;
47         while(j<len_b){
48 
49             if(k==-1 || b[j]==b[k]){
50                 j++;
51                 k++;
52                 if(b[j]==b[k])
53                     ne_b[j]=ne_b[k];
54                 else
55                     ne_b[j]=k;
56             }
57             else
58                 k=ne_b[k];
59         }
60 
61 //      匹配
62         int ans=0;
63         int i=0;
64         j=0;
65         while(i<len_a){
66             if(j==len_b){
67                 j=ne_b[j];
68                 ans++;
69             }
70             if(j==-1 || a[i]==b[j]){
71                 i++;
72                 j++;
73             }
74             else
75                 j=ne_b[j];
76         }
77         if(i==len_a && j==len_b)
78             ans++;
79 //        printf("%d\n",ans);
80         cout<<ans<<endl;
81     }
82 }
View Code
复制代码

2、两个字符数组,scanf一个char一个char的读取,依据tmd会TLE (我不知道C语言有读%s这玩意,忘记了,以前是知道的)改成%s直接AC了

3、另外之前听说过关同步,但我加 std::ios::sync_with_stdio(false); HDOJ 依旧TLE,POJ RE了(忘了,他用的万能头文件,POJ不支持)

vjudge的讨论里有句:“tle的取消cin和cout的同步性就可以了。加上std::ios::sync_with_stdio(false);”,关联博客

查了下关同步,我试了下代码加了也AC了

复制代码
 1 #include<stdio.h>
 2 #include<string.h>
 3 #include<iostream>
 4 using namespace std;
 5 string a;
 6 string b;
 7 //char b[10001+1];//子W
 8 //char a[1000001];//主T
 9 int ne_b[10001+1];
10 int main()
11 {
12     std::ios::sync_with_stdio(false);
13 //    cout<<"a";
14 //    printf("3\n");
15 //    cout<<"4"<<endl;
16     int Case;
17     int flag;
18     cin>>Case;
19 //    scanf("%d",&Case);
20     while(Case--){
21 
22         int len_a=0;//
23         int len_b=0;//24 //        char str;
25 //        if(flag-1 == Case)
26 //            getchar();//吃Case后的回车,注意Case只有第一次的时候有输入
27 
28 //        scanf("%s%s",b,a);
29         cin>>b;
30         cin>>a;
31 //        cout<<a<<" "<<b<<endl;
32 //        while(scanf("%c",&str)){//先输入的是子
33 //            if(str=='\n')
34 //                break;
35 //            b[len_b]=str;
36 //            len_b++;
37 //        }
38         len_a=a.length();
39         len_b=b.length();
40 //        cout<<len_a<<" "<<len_b<<endl;
41 //        while(scanf("%c",&str)){//后输入主
42 //            if(str=='\n')
43 //                break;
44 //            a[len_a]=str;
45 //            len_a++;
46 //        }
47 
48 //      子串 a 求 next
49         int k=-1;
50         int j=0;
51         ne_b[0]=-1;
52         while(j<len_b){
53 
54             if(k==-1 || b[j]==b[k]){
55                 j++;
56                 k++;
57                 if(b[j]==b[k])
58                     ne_b[j]=ne_b[k];
59                 else
60                     ne_b[j]=k;
61             }
62             else
63                 k=ne_b[k];
64         }
65 
66 //      匹配
67         int ans=0;
68         int i=0;
69         j=0;
70         while(i<len_a){
71             if(j==len_b){
72                 j=ne_b[j];
73                 ans++;
74             }
75             if(j==-1 || a[i]==b[j]){
76                 i++;
77                 j++;
78             }
79             else
80                 j=ne_b[j];
81         }
82         if(i==len_a && j==len_b)
83             ans++;
84 //        printf("%d\n",ans);
85         cout<<ans<<endl;
86     }
87 }
View Code
复制代码

发现几个事,问了GPT后学到了很多:(对画图见下面)

3.1、HDOJ 62ms,但scanf 两个%s却是400ms左右,好神奇啊,cincout关闭同步,比scanf还快。

GPT解释:

的确如此,原因是:关闭同步类似于异步,当然比一个接一个排队等待输入的scanf快了,你可以理解成异步,但绝对不是异步!异步需要涉及到我没学过的比如多线程、事件驱动、异步I/O库等,关闭了同步重点在于可以独立的做事,所以互相独立做事这个词更贴切,scanf和正常写cincout都是开同步的你一句执行完,再执行下一句

为了更好理解,GPT举的例子是买奶茶:

正常写代码都是默认开启同步,比如你排队等待就相当于scanf或者cin,不能做其他事,前面的结束你才能干自己的事

关闭同步就相当于你网上定奶茶,你可以在等待期间导管子,那你能在排队的时候导管子吗?显然不能,效率就很慢啊

所以关闭同步的cincout比正常顶点scanf还快,而scanf并不涉及关同步提高效率的事,关同步是C++的I/O流的事,所以 关同步cincout 远优于 scanf printf 远优于 cincout

3.2、而且上面view里的AC代码如果Case那个变量用scanf输入也会TLE,必须全都用cin才行。

GPT给的理由是:

确实如此。我本着求其上得其中,求其中的其下的原则,不说自己的理解,因为第一次看的理解肯定很肤浅,日后读多少遍永远都不会超过自己的理解,所直接贴GPT的解释原文:

cin>>Case;替换为scanf("%d",&Case);导致程序超时,这个问题可能涉及到多个方面。首先,我们来探讨一下C++的I/O流(cin)和C风格I/O(scanf)在性能上的差异。

性能差异

  1. ‌C++的I/O流(cin)‌:

    • C++的I/O流是面向对象的,提供了更多的功能和灵活性。
    • 但是,在处理大量数据时,C++的I/O流可能会比C风格I/O更慢。
  2. ‌C风格I/O(scanf)‌:

    • C风格I/O更加底层,通常更快。
    • 它牺牲了灵活性和易用性,以换取更高的性能

关闭同步的影响

此外,如果之前关闭了C++的I/O流和C风格I/O之间的同步(std::ios::sync_with_stdio(false);),那么这两种I/O方式将独立地进行I/O操作。这可能会导致以下情况:

  • 当使用cin时,它可能使用了C++的缓冲区,并且与C风格的I/O没有同步。
  • 当使用scanf时,它直接操作底层的I/O,可能与C++的I/O流产生冲突或竞争,导致性能下降。

可能的解决方案

  1. ‌保持同步‌:

    • 如果你的程序需要同时使用cinscanf,并且希望避免性能问题,可以考虑保持它们之间的同步(即不调用std::ios::sync_with_stdio(false);)。
  2. ‌选择一种I/O方式‌:

    • 为了优化性能,你可以选择只使用cin或只使用scanf,而不是混合使用它们。
    • 根据你的具体需求和测试结果,选择性能更好的那种方式。
  3. ‌优化输入处理‌:

    • 如果你的程序需要处理大量的输入,可以考虑使用更高效的输入方法,如从文件读取或使用专门的输入库。
  4. ‌性能测试‌:

    • 在进行任何优化之前,最好先对程序进行性能测试,以确定性能瓶颈所在。
    • 使用合适的性能测试工具和方法,准确地测量程序的运行时间和资源使用情况。

GPT进一步用例子说明了为啥关闭同步会更快:这个例子里面很多东西我看不懂,但大概可以理解啥意思了,类似考研操作系统讲解的线程阻塞分时异步那些玩意吧

复制代码
 1 #include <iostream>
 2 #include <cstdio>
 3 #include <thread>
 4 #include <chrono>
 5 
 6 // 模拟一个耗时的I/O操作,比如读取文件或写入文件
 7 void simulateIO(const char* name) {
 8     std::this_thread::sleep_for(std::chrono::seconds(1));
 9     std::cout << name << " finished IO operation" << std::endl;
10 }
11 
12 int main() {
13     // 关闭C++的I/O流和C风格I/O之间的同步
14     std::ios::sync_with_stdio(false);
15 
16     // 启动两个线程,分别进行模拟的I/O操作
17     std::thread t1(simulateIO, "Thread 1");
18     std::thread t2(simulateIO_c, "Thread 2"); // 注意:这里simulateIO_c是一个用printf的模拟IO函数,需要你自己定义,类似simulateIO但用printf输出
19 
20     // 等待两个线程完成
21     t1.join();
22     t2.join();
23 
24     // 如果不关闭同步,下面的代码可能需要等待上面两个线程都完成IO操作后才能输出
25     std::cout << "Main thread finished" << std::endl;
26 
27     // 注意:这里的代码是为了展示关闭同步的效果,并没有真正地实现异步IO。
28     // 在实际应用中,要实现异步IO,可能需要使用更复杂的机制,比如多线程、事件驱动等。
29 
30     // 但是,即使在这个简单的例子中,关闭同步也可以让我们看到两个线程似乎是在并行执行IO操作,
31     // 因为它们不再需要等待对方完成输出。这在一定程度上提高了效率,因为减少了等待时间。
32 
33     // 强调:这段代码不可用作商业盈利!若私自用于商业用途,出现bug导致经济损失可是要你自己承担的!
34 
35     return 0;
36 }
37 
38 // 注意:这里需要补充一个使用printf的模拟IO函数simulateIO_c
39 void simulateIO_c(const char* name) {
40     std::this_thread::sleep_for(std::chrono::seconds(1));
41     printf("%s finished IO operation with printf\n", name);
42 }
View Code
复制代码

‌注意‌:上面的代码中,simulateIO_c 函数是用 printf 来模拟IO操作的,你需要自己定义这个函数,类似 simulateIO 但使用 printf 输出。

在这个例子中,我们关闭了C++的I/O流和C风格I/O之间的同步,并启动了两个线程来并行执行模拟的I/O操作。由于关闭了同步,这两个线程可以独立地进行I/O操作,而不需要等待对方完成输出。这在一定程度上提高了效率,因为减少了等待时间。

然而,需要注意的是,这个例子并没有真正地实现异步I/O操作。在实际应用中,要实现异步I/O,可能需要使用更复杂的机制,比如多线程、事件驱动、异步I/O库等。

对此自己的实践:加上关同步语句的那个AC代码中清一色的cin string,最后结果的 cout 改成 printf 不影响,但对于输入,如果读取 Case 那改成 scanf 就会TLE,根据前面的 GPT 解释就是本来关同步,可以让cin完美的异步操作,同时读取数据,而scanf的引入,导致底层操作系统直接与C++的I/O流冲突或者竞争,导致性能下降,直观影响就是TLE了,为了验证猜想,我cin Case后,又把 cin a 和 cin b 那,搞成了一个cin a 一个 scanf b,b为字符数组,然后 len_a=a.length();len_b=strlen(b);  依旧TLE。至此有些许理解了啥叫关同步提升效率了

3.3、

std::ios::sync_with_stdio(false);
cout<<"a"; 
printf("3\n"); cout<<"4"<<endl;

输出的是3回车a4。

GPT的解释

cout << "a"; 这行代码会将字符'a'放入C++的I/O流缓冲区中,但此时并不会立即输出到屏幕,因为C++的I/O流默认是带缓冲的。

printf("3\n"); 这行代码则直接将字符'3'和换行符输出到屏幕上,因为C风格的I/O(如printf)在默认情况下是不带缓冲的(或者说,它的缓冲是行缓冲的,遇到换行符就会立即刷新缓冲区,刷新缓冲区意思就是刷新到输出设备比如屏幕) ———— 这里我有疑问,后面说

cout << "4" << endl; 这行代码首先将字符'4'放入C++的I/O流缓冲区中,然后由于endl的作用,会刷新C++的I/O流缓冲区,将'4'和之前的'a'一起输出到屏幕上。

因此,最终的输出顺序是 “3回车a4”

那如果 printf 那去掉 \n 呢?插一句知识点: 

C风格I/O(如printf)的缓冲有三种类型:无缓冲、行缓冲和完全缓冲。具体使用哪种缓冲方式,取决于输出的目标。

无缓冲‌:通常用于终端(比如控制台),这意味着输出会立即被显示,不会存储在缓冲区中。不过,实际上,对于大多数现代终端,printf通常会使用行缓冲而不是无缓冲。

行缓冲‌:输出会先存储在缓冲区中,直到遇到换行符(\n)或缓冲区满时,才会被刷新到输出设备(如屏幕)。对于控制台输出,printf默认使用行缓冲

完全缓冲‌:输出会先存储在缓冲区中,直到缓冲区满或显式调用fflush函数时,才会被刷新到输出设备。这通常用于文件输出。

好回归问题,printf去掉 '\n' ,首先,我们要明白printf确实是 行缓冲 的,这意味着它通常会在遇到换行符或缓冲区满时刷新输出。但是,这里有个重要的细节要注意:缓冲行为可能会受到输出目标(如控制台、文件等)和操作系统的影响。

具体分析:

由于std::ios::sync_with_stdio(false);的设置,C++的I/O流(cout)和C风格的I/O(printf)之间的同步被关闭了。这意味着它们可以独立地缓冲和输出数据。

现在,让我们一步步分析输出:

  1. cout << "a"; 这行代码会将字符'a'放入C++的I/O流缓冲区中,但此时并不会立即输出到屏幕,因为C++的I/O流默认是带缓冲的。

  2. printf("3"); 这行代码会将字符'3'放入printf的缓冲区中。由于printf是行缓冲的,并且此时缓冲区没有满(也没有遇到换行符),所以'3'可能不会立即被输出到屏幕上。但是,在某些情况下(特别是输出到控制台时),操作系统或C标准库的实现可能会选择立即刷新缓冲区,或者由于其他因素(如内部优化、多线程竞争等)导致缓冲区被提前刷新。这就是为什么你可能会看到'3'首先被输出的原因。

    然而,需要注意的是,这种行为并不是C标准所保证的,它可能因编译器、操作系统或环境的不同而有所变化。

  3. cout << "4" << endl; 这行代码会将字符'4'放入C++的I/O流缓冲区中,然后由于endl的作用,会刷新C++的I/O流缓冲区,将'4'和之前的'a'一起输出到屏幕上。

因此,尽管printf是行缓冲的,并且后面没有换行符,但在某些情况下,你可能会看到它的输出首先出现在屏幕上。这主要是由于缓冲行为的不确定性以及操作系统或C标准库实现的具体细节所导致的。

不过,从严格意义上讲,我们不能保证'3'一定会首先被输出。实际的输出顺序可能会因编译器、操作系统或环境的不同而有所变化 

 
再举个例子:
复制代码
// 同步的情况
std::ios::sync_with_stdio(true);
cout << "Hello, ";
printf("world!\n");
// 输出: Hello, world!

// 不同步的情况
std::ios::sync_with_stdio(false);
cout << "Hello, ";//后输出
printf("world!\n");//先输出
// 输出顺序可能是不确定的,取决于哪个系统的缓冲区先被刷新
复制代码

 

 

 

对画原文图:

 

 

 

 

写代码遇到的问题

 

###:

复制代码
宁可让别人考试时能看到后台数据但不现场返回测试结果,我看不到后台数据但需要现场返回结果


题目刷多了比撸管子上瘾多了,而且读懂题意是一回事,能AC是另一回事,就是有些题目描述可能很不好理解,强迫症总想去搞清题目里叙述的东西,但根据刷题的经验,却可以很快知道题目表达的是啥意思,可以AC,AC后题目都不一定完全懂,claris克拉丽丝酒店给q神讲题,鏼神:这不是BZOJ原题吗?

“这道题我虽然不会,但是AC还是没有问题的”
View Code
复制代码

###:string a赋值给string b直接b=a;

之前那个strcpy是C的,但它不会自动检查目标数组的大小,可能会导致缓冲区溢出等安全问题。C++的string可以避免缓冲区溢出等安全问题,以上文心GPT告诉我的

关于字符数组

我记得之前说不用管最后一个字符的,不管了就先这样吧

###:回忆对拍诡异

###:妈逼的算法有个JB用,不会开发也找不到工作,连面试简历都没法投,呵呵。好后悔入这坑,之前有人说算法就是热爱,哪怕找不到工作也不后悔打过算法竞赛,我深信不疑自我高潮,如今,呵呵你tm可真他娘的神圣高尚,如果有人再问我算法有啥用,我会说,acm狗屁用没有,纯网隐

###:

posted @   GerJCS  阅读(6)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示