题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4351
题意就不描述了,这道题目的标准做法应该是区间合并的线段树,因为比赛的时候想到了能水过的方法,赛后证明是可行的,特意记录说明一下。
题目说明 0 <= Ai <= 1e9 ,为了方便讨论暂时不管 Ai = 0 的情况,这样对于每次查询 [l, r],由于子区间数量增长很快,而有效的状态只有10个,所以枚举的过程中状态 9、8、7、6、5 很快就会出现,这些状态一旦出现就可以立即终止枚举。
如果数据中有大量连续的0,易知枚举必定会TLE,因此需要构造一个 next[] 数组,作用就是跳过当前元素前面的所有连续 0。此外因为只有10个状态,可以用状态压缩来优化时间效率。
这样一来,这道题目就能完美水过了,比赛的时候对这种方法信心不足导致TLE一次后就放弃了,自信不足。晚点再用线段树的方法做一遍。
经测试,不用 next[] 数组跳过连续 0 的枚举要 600+ms(如果数据强应该会TLE的),加了这个优化后 200+ms 能跑完,此外是否用状态压缩对时间效率没影响,总的来看这种水水的方法无论时间、空间、代码量上都应该比线段树的写法有优势(我没具体提交别人的线段树代码测时间效率,但据说线段树写法不用状态压缩还过不了),上面所有测试结果都是在下面这份代码上修改的。
/****************************************************************
Problem: HDU 4351
User: Jeflie
Language: C++
Result: Accepted
Time: 218 ms
Memory: 1424 kb
****************************************************************/
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
const int N = 100010;
int dig[N], next[N];
int sum[N];
int Digit(int x)
{
return (x - 1) % 9 + 1;
}
void Cal(int l, int r)
{
int judge = 0;
int target = (1<<5) | (1<<6) | (1<<7) | (1<<8) | (1<<9);
for (int j = l; j <= r; j = next[j])
{
for (int k = l; k <= j; k = next[k])
{
if (k < r && next[k] != k+1) // 检测是否跳过0
judge |= 1;
int tmp = Digit(sum[j] - sum[k-1]);
judge |= (1 << tmp);
if ((judge & target) == target)
{
printf("9 8 7 6 5\n");
return;
}
}
}
bool flag = 0;
int cnt = 5;
for (int i = 9; i >= 0; i--)
{
if ((judge&(1<<i)) && cnt)
{
if (flag) printf(" ");
printf("%d", i);
cnt--;
flag = 1;
}
}
while (cnt--)
{
if (flag) printf(" ");
printf("-1");
flag = 1;
}
printf("\n");
}
int main()
{
int T, Case = 1;
scanf("%d", &T);
while (T--)
{
sum[0] = 0;
int n, tmp;
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
scanf("%d", &dig[i]);
dig[i] = Digit(dig[i]);
sum[i] = sum[i-1] + dig[i];
}
tmp = n+1;
for (int i = n; i > 0; i--) // 用next数组跳过元素前面的连续0
{
next[i] = tmp;
if (dig[i]) tmp = i;
}
printf("Case #%d:\n", Case++);
int k, l, r;
scanf("%d", &k);
while (k--)
{
scanf("%d %d", &l, &r);
Cal(l, r);
}
if (T) printf("\n");
}
return 0;
}