hiho1622 有趣的子区间(YY)
题目链接:http://hihocoder.com/problemset/problem/1622?sid=1230113
#1622 : 有趣的子区间
描述
如果一个区间[a, b]内恰好包含偶数个回文整数,我们就称[a, b]是有趣的区间。
例如[9, 12]包含两个回文整数9和11,所以[9, 12]是有趣的区间。[12, 20]包含0个回文整数,所以[12, 20]也是有趣的。
现在给定一个区间[a, b],请你求出[a, b]中所有满足a ≤ p ≤ q ≤ b的子区间[p, q]有多少个有趣的。
输入
第一行包含两个整数a和b。
对于30%的数据,1 ≤ a ≤ b ≤ 1000
对于60%的数据,1 ≤ a ≤ b ≤ 100000
对于100%的数据, 1 ≤ a ≤ b ≤ 1000000000
输出
有趣的子区间数目
- 样例输入
-
10 20
- 样例输出
-
46
菜ji题解,大牛请略过。。。
刚看到题目是半点思路也没有啊,要统计所有区间(10^9)^2,还要计算区间内回文数的个数。这。。玩不了。(手动捂脸。。
但是仔细一想特么即便区间大小长达1e9,但是1e9内的回文数不多啊(因为前一半确定了,后一半也就确定了= = ),估算一下差不多在1e5个回文数左右。。于是很顺理成章的预处理1e9内的所有回文数并排序。。。(但是有什么用呢= =)
如果每两个回文数看成一个区间,计算有趣区间个数。比如a, b, c, d, e五个回文数(从小到大),那么考虑左端点在(a, b]、右端点在(c, d]的所有区间都是有趣的,这样的计算复杂度是O(1)的。[有趣区间数=(b - a) * (d - c)],这样只要枚举回文数个数为偶数的所有回文数区间
比如加入上述例子中区间为[l, r] 且 l < a, r > e, 那么枚举有趣的区间的:
1.左端点在[l, a],右端点在[b, c) // 区间内有两个回文数
2.左端点在[l, a],右端点在[d, e) // 区间内有四个回文数
3.左端点在(a, b],右端点在[c, d) // ...
4.左端点在(a, b],右端点在[e, r] // ...
5.左端点在(b, c],右端点在[d, e) // ...
...
对于每个枚举都是保证区间内回文数为偶数的前提下,计算左端点可取的个数x(比如样例1:x=a - l + 1), 右端点可取的个数y(比如样例1:y=c - b),那么此次枚举的有趣的区间数为x * y。最后对所有的x * y求和(即上面的枚举情况1.2.3.4.5....所有的x * y求和)即可。可以看到这样的复杂度为O(1e5 ^ 2)=O(1e10),复杂度减少了不少,但是还是接受不了啊= =
还要优化。。。
那。。继续来。。。
相信细心的读者已经注意到了,在上面的例子中情况2和情况5枚举了同一个右边界的情况[d, e),原因在于他们的左边界[l, a]和(b, c]这两个区间之间始终差距两个(偶数个)回文数,而情况1中右边界为[b,c),与情况5的左边界一致,那么在计算情况1、2、5时,便可以统一处理:
首先计算区间[b, c)和区间[d, e)的长度和(差距为偶数的区间长度和):
sum = (c - b) + (e - d) // + (g - f) + ...
那么情况1和情况2可以合并为
ans += (a - l + 1) * sum (其实这个代表所有有趣的区间的左端点落在[l, a]的方法)
在计算情况5时:
首先令sum -= c - b
ans += (c - b) * sum(其实这个代表所有有趣的区间的左端点落在(b, c]的方法)
好啦,复杂度顺利降到了O(1e5)
至于代码什么的,我的一向可读性不高啦。。有了思路应该都可以搞得定~(只是大神分分钟,蒟蒻我花了一整晚T_T)
顺便说一句,我的代码思路是[l, r]所有区间数减去非有趣的区间数计算的,因为不要忘了一个回文数没有的区间也是有趣的区间,也就是上面的左右端点都在[l, a)的区间也是有趣的。
1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <vector> 5 #include <queue> 6 #include <algorithm> 7 using namespace std; 8 9 typedef long long LL; 10 11 const int N = 1000005; 12 const int M = 1000000000; 13 14 int hwNums[N], cntHW; 15 16 int rever(int n) 17 { 18 int ans = 0; 19 while(n) 20 { 21 ans = ans * 10 + n % 10; 22 n /= 10; 23 } 24 return ans; 25 } 26 27 int getLenP(int n) 28 { 29 int ans = 1; 30 while(n) ans *= 10, n /= 10; 31 return ans; 32 } 33 34 void init() 35 { 36 for(int i = 1; i < 10000; i ++) 37 { 38 if(i < 10) hwNums[cntHW ++] = i; 39 40 int r = rever(i), p = getLenP(i); 41 hwNums[cntHW ++] = i * p + r; 42 43 for (int j = 0; j < 10; j ++) 44 { 45 LL num = (LL)i * 10 * p + j * p + r; 46 if(num <= M) hwNums[cntHW ++] = num; 47 } 48 } 49 // 随意添加两个更大的数,可以略去后面的边界情况讨论 50 hwNums[cntHW ++] = 1000000001; 51 hwNums[cntHW ++] = 1100000011; 52 sort(hwNums, hwNums + cntHW); 53 } 54 55 // 对于l, hw1, hw2, hw3, ..., r 56 // 计算有趣的区间的左端点落在[l, hw1], (hw2, hw3], (hw4, hw5] ... 的所有的方法数 57 LL count_ans(int l, int r) 58 { 59 int id = 0, tid; 60 while(hwNums[id] <= l) id ++; // while中略去了id < cntHW,因为上面添加了两个超出边界的数 61 if(hwNums[id] > r) return 0; 62 tid = id; 63 64 LL sum = 0, pre = hwNums[id ++]; 65 while(hwNums[id] <= r) 66 { 67 sum += hwNums[id] - pre; 68 pre = hwNums[++ id]; 69 id ++; 70 } 71 if(pre <= r) sum += r + 1 - pre; 72 73 LL ans = 0; 74 pre = l; 75 while(hwNums[tid] <= r) 76 { 77 ans += (hwNums[tid] - pre) * sum; 78 sum -= hwNums[tid + 1] - hwNums[tid]; 79 pre = hwNums[++tid]; 80 tid ++; 81 } 82 return ans; 83 } 84 85 int main() 86 { 87 //freopen("in.txt", "r", stdin); 88 89 init(); 90 91 int a, b; 92 cin >> a >> b; 93 a --; 94 95 LL n = b - a, ans = n * (n + 1) / 2; 96 97 int id = 0; 98 while(hwNums[id] <= a) id ++; 99 100 ans -= count_ans(a, b) + count_ans(hwNums[id], b); 101 102 cout << ans << endl; 103 104 return 0; 105 }