题意:有一段楼梯长度为n(1<=n<=1e6),每一阶楼梯上有一个字母,字母要么是‘U’要么是‘D’,楼梯上还有一个人,若他所处台阶是‘U’,则他会走上上一阶台阶,并继续根据上面的字母走路;若是‘D ’,则向下走,他每经过一个台阶,台阶上的字母就会改变(原先是‘U’就变成‘D’,‘D’变‘U’,这样……)。当这个人掉出楼梯时,计步结束,请给出这个人以每一阶台阶为出发点时,分别要走几步才会掉出楼梯。(如果不会掉出楼梯就输出-1)
思路:说实话这题的确很考验人的思维……
然后自己也参考了别人的代码(QAQ):
http://blog.csdn.net/fsss_7/article/details/53170360
首先有这样一个结论,不管楼梯上字母分布如何,这个人一定会掉出楼梯,具体证明可以看
题解,大致证明思路就是假如这个人在往上走,他要么一只往上走,直到掉出,要么遇到一个‘D’,然后开始一直往下走,同样,他要么一直往下走直到掉出,要么遇到一个‘U’,重新开始往上走,此时之前使他往下走的那个‘D’已经变成了‘U’,因为阶梯数是有限的,所以他一定会走出楼梯。
1.先讨论从楼梯的哪个方向掉下
明白了上述结论后,很容易想到,楼梯上可以改变人行走方向的点只有出发点前面的‘U’,和出发点后面的‘D’。进一步简化问题,设当前点为x,因为x前面的点起作用的都是‘U’点,所以考虑x前面的点都是‘U’,后面的点都是‘D’的特殊情况。
比如:UUxDD,此时x前后的U,D数量相等,他们对人的行走方向的影响互相抵消,容易想到这种情况下掉出去的方向取决于x的方向。(x为D,从前掉,x为U,从后掉)
从上面的讨论展开:设x前U的数量为cntu,若从x(包括x)开始往后有cntd>cntu,则一定是从楼梯的前方掉下去的;若从x(包括x)开始往后有cntd<=cntu那么就是从楼梯的后方掉下去的。
2.计数方法
从上面的关于方向的讨论可以看出,人走路的模式大致是:在一个个U,R之间来回走,直到把这些障碍都清除了,然后再笔直地走到楼梯外,设x点走到它前面的每一个U点的
来回距离之和为X,x点走到它后面的每一个D点的
来回距离之和为Y,则我们一旦确定了跳下去的方向,答案就清楚了。
向前跳:ans=X+Y+x
向后跳:ans=X+Y+n-x+1
于是程序要做的事情就是:从头开始遍历每一个台阶,用一个双指针来维护当前的cntu和cntd个数,并不断更新X,Y。
关于更新的细节:
由于是从前往后扫(当然你愿意也可以从后往前扫QAQ),所以对于每个台阶,前面的cntu是一直的,所以就要用双指针找后面的d,尽量使cntd>cntu,如果找不到,那么说明人是往后跳的,要减去一些前面多余的U
详见代码:
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int MAXN=1e6+10;
int n;
char str[MAXN];
int main(){
while(~scanf("%d%s",&n,str+1)){
LL u=0,d=0,x=0,y=0,l=0,r=0;
for(int i=1;i<=n;++i){
if(str[i-1]=='U') ++u;
if(str[i-1]=='D') --d;
x+=2*u;
y-=2*d;
while(u>=d&&r<n) if(str[++r]=='D') ++d,y+=2*(r-i);
while(u>d&&r==n) if(str[++l]=='U') --u,x-=2*(i-l);
if(u==d) printf("%I64d ",x+y+1+n-i);
else printf("%I64d ",x+y+i);
}
putchar(10);
}
}