又是一个单调队列的题目,这个比前面的就多了点优化,不过还是搞了好久,错了不知道多少。。。太水了。。。
题意是:告诉一个有字母C和字母J组成的长度不大于1e6的环行排列,现在从某个地方断开,由此向前或者向后,如果可以使得其中之一满足任何时刻数的的C不小于J.那么就成合法的切开位置,求合法的位置的数目。
思路:先将环行的排列展开,在展开的地方在续上一个展开串,使之和环行排列等价。 然后以任意位置为起点,记录下C的个数剪掉J的个数s[i], 如果i位置的左边或右边有cj[i] >= 0 或者右边 cj[i] >= 0(SL和SR都是相对的个数),那么就可以说段开点i是合法的。这里可以用一个最小单点队列(队首元素为区间最小值)来表示。
代码:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int mm=1e6+10;
char ss[mm*2];
struct node{
int x,y;
}q[mm*2];
int w[mm*2];
bool cj[2][mm];
void solve(int n,int m,int k){
for(int i=1;i<m;i++){
if(ss[i] == 'C') w[i]=w[i-1]+1;
else w[i]=w[i-1]-1;
}
int f=1,r=0;
for(int i=1;i<m;i++){
while(f <= r && q[r].x >= w[i]) r--;
q[++r].x=w[i];
q[r].y=i;
if(i >= n){
while( f <= r && q[f].y <= i-n) f++;
//printf(" f=%d\n",f);
if(w[i-n]-q[f].x <= 0)
cj[k][i-n]=1;
}
}
}
int main()
{
int t,i,len,len1,ans;
int sum;
scanf("%d",&t);
for(ans=1;ans<=t;ans++){
sum=0;
memset(w,0,sizeof(w));
memset(cj,0,sizeof(cj));
scanf("%s",ss+1);
len=strlen(ss+1);
//printf("123_____%d\n",len);
len1=2*len+1;
for(i=1;i<=len;i++){
ss[i+len]=ss[i];
}
ss[len1]=0;
solve(len,len1,0);
reverse(ss+1,ss+len1);
solve(len,len1,1);
//printf("_____%d\n",len);
for(i=1;i<=len;i++){
//printf(" %d %d\n",cj[0][i],cj[1][n-i]);
if(cj[0][i] || cj[1][len-i] ) sum++;
}
printf("Case %d: %d\n",ans,sum);
}
return 0;
}
路途中....