单调队列+前缀和

本题需要用到上题的破环成链的思想,因为可以正反方向两个方向找切割点,因此我们需要正反两个方向的前缀和,当我们反向搜索时,没必要从size()-1开始,可以同样设下标为1,然后从2*len-1开始加前缀和.

这里有几个需要注意的点:

  1. 这里需要将字符串转化成数字,如果在首位插入字符凑前缀和会导致TLE
  2. 数组不宜开过多,否则MLE
  3. 会有重复切割点
 1 #include <iostream>
 2 #include <cstdio>
 3 #include <cstring>
 4 using namespace std;
 5 const int N = 1e6+10;
 6 int sumc[N*2],q[N*2],a[2*N];
 7 char str[N];
 8 bool can[N];
 9 int main()
10 {
11     freopen("in.txt","r",stdin);
12     int n,kcase = 0;
13     scanf("%d",&n);
14     while(n--){
15         int hh = 0,tt = -1,cnt = 0;
16         scanf("%s",str+1);//    s.insert(0,"A");可能是字符串太长超时,所以我们最好不要对字符串操作
17         int len = strlen(str+1);
18         fill(can+1,can+N+1,0);
19         for(int i=1;i<=len;i++){
20             a[i]=(str[i]=='C')?1:-1;
21             a[len+i] = a[i];
22         }//suma>=sumb
23         for(int i=1;i<=2*len;i++) sumc[i] = sumc[i-1]+a[i];
24         for(int i=1;i<2*len;i++){//a[i]内是两倍的字符串,最后一个不要取
25             while(hh<=tt&&i-len+1>q[hh]) ++hh;
26             while(hh<=tt&&sumc[q[tt]]>=sumc[i]) --tt;
27             q[++tt] = i;
28             if(i-len+1>0&&sumc[q[hh]]-sumc[i-len]>=0) can[i-len+1] = 1;
29         }
30         hh = 0; tt = -1;
31         for(int i=1;i<=2*len;i++)
32             sumc[i] = sumc[i-1]+a[2*len-i];
33         for(int i=1;i<2*len;i++){//反向求的最小和是反向前缀和的最小,与正向不同
34             while(hh<=tt&&i-len+1>q[hh]) ++hh;
35             while(hh<=tt&&sumc[q[tt]]>=sumc[i]) --tt;
36             q[++tt] = i;
37         if(i-len+1>0&&sumc[q[hh]]-sumc[i-len]>=0){
38              can[len*2-i] = 1;//从上面可判断i一定大于len-1 这个起点已经转化成还未破环的坐标
39              printf("%d\n",len*2-i) ;
40            }
41         }
42         for(int i=1;i<=len;i++)
43             if(can[i]) cnt++;
44         printf("Case %d: %d\n",++kcase,cnt);
45     }
46     return 0;
47 }