题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=4193
题目大意: 给定一个长度为n的循环序列,从n个不同位置开始,问有几个位置使得一下情况成立:所有前缀的和都大等于0(n <=100万).
解题思路:下午做的swerc的G题,这题想成线段树敲了十多分钟,然后TLE了,尔后就去敲第一题Polya,拿了个FB。
线段树之所以会T,是因为它的复杂度是O(nlogn),这样n等于100万的时候大概要操作8千万左右,而用单调队列只要O(n),操作数大概为2百万,结果线段树T了,单调队列过了。
之所以会想到线段树来做这题是因为顺着出题者的思路,模拟题目的n次循环操作,每次成段更新成段查询最小值就好,非常好想非常好写。这样的话就被出题者束缚住思路了,很多时候往往就是因为思路被束缚了,所以应该出的题目没有出。
这题要求说所有的前缀和大等于0,如果前面一个滚到最后,看似要更新,其实完全不要更新,只要用0加上sum[i]表示前i个数的和,这个值称为Minval,我们要做的是维护这个Minval,并计算从i位到i+n-1位所有前缀的最小sum[i],记为Minsum,如果Minsum >= Minval就合法答案加1.这个Minval其实就是前缀i的总和(1<=i<=n).
然后就是求固定区间长度的最小值,这个用单调队列最合适了。做完此题好好思考了下,对于区间查询,线段树应该是首选,但是当限制条件条件增加时应该选择更简洁效率更高的数据结构完成操作,对于本题,限制条件是区间固定,而且数组不动态变化,变化的是Minval,那么用单调队列更优。
基于以上的分析,剩下的就是编码,最近似乎称为码农了,每场比赛都是我一个人在敲,不比赛时一题经常会敲很多遍。
测试数据:
4
3 -1 -1 1
3
-1 1 1
3
1 2 3
C艹代码:
#include <stdio.h>
#include <string.h>
#define MAX 2100000
struct node {
int sum,id;
}qu[MAX];
int n,head,tail,ans;
int sum[MAX],arr[MAX];
int main()
{
int i,j,k;
while (scanf("%d",&n) != EOF) {
if (n == 0) break;
ans = head = tail = 0;
for (i = 1; i <= n; ++i) {
scanf("%d",&arr[i]);
sum[i] = sum[i-1] + arr[i];
}
for (i = 1; i <= n; ++i)
sum[i+n] = sum[i+n-1] + arr[i];
for (i = 1; i <= 2 * n; ++i) {
while (tail < head && sum[i] < qu[head-1].sum)
head--;
qu[head].sum = sum[i],qu[head].id = i,head++;
if (i > n && qu[tail].sum >= sum[i-n]) ans++;
while (tail < head && qu[tail].id <= i - n + 1) tail++;
}
printf("%d\n",ans);
}
}