题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=6070
题意: 给出一个题目提交序列, 从中选出一个正确率最小的子串. 选中的子串中每个题目当且仅当最后一次提交是正确的.
思路: 分数规划
二分答案, 然后在 check 函数中查找是否存在某个区j间 [l, r] 使得 sum(l, r) / (r - l + 1) <= mid, 即 sum(l, r) + l * mid <= (r + 1) * mid. 可以用个线段树来维护 sum(l, r) + l * mid . 建树时直接将 l * mid 放入树中, 然后从左到右枚举 r, 对于当前 i, a[i] 对区间 [pre[i] + 1, i] 的贡献为一(区间 [1, pre[i]] 内的贡献之前的a[i]已经计算了) . 这样对于当前更新后, 1 <= j <= i , sum[j] 即为区间 [j, i] 内的贡献. 那么对于当前 i, query(1, i) 就得到了所有以 i 为后缀的区间的贡献最小值. 遍历完 r 后即得到了所有区间的贡献最小值.
最后要注意一下线段树区间更新,区间最值的 lazy 数组维护写法, 最值和区间求和是不同的.
代码:
1 #include <iostream> 2 #include <stdio.h> 3 #include <string.h> 4 #define lson l, mid, rt << 1 5 #define rson mid + 1, r, rt << 1 | 1 6 using namespace std; 7 8 const double eps = 1e-5; 9 const int MAXN = 6e4 + 10; 10 int a[MAXN], pre[MAXN], last[MAXN], n; 11 double sum[MAXN << 2], lazy[MAXN << 2]; 12 13 void push_up(int rt){ //向上更新取最值 14 sum[rt] = min(sum[rt << 1], sum[rt << 1 | 1]); 15 } 16 17 void push_down(int rt){ 18 if(lazy[rt]){//将标记向下更新,维护的是最值,sum不需要求和 19 lazy[rt << 1] += lazy[rt]; 20 lazy[rt << 1 | 1] += lazy[rt]; 21 sum[rt << 1] += lazy[rt]; 22 sum[rt << 1 | 1] += lazy[rt]; 23 lazy[rt] = 0; 24 } 25 } 26 27 void build(int l, int r, int rt, double value){ 28 lazy[rt] = 0; 29 if(l == r){ 30 sum[rt] = value * l; 31 return; 32 } 33 int mid = (l + r) >> 1; 34 build(lson, value); 35 build(rson, value); 36 push_up(rt); 37 } 38 39 void update(int L, int R, int value, int l, int r, int rt){ 40 if(L <= l && R >= r){ 41 lazy[rt] += value; 42 sum[rt] += value;//维护的是最值,sum不需要求和 43 return; 44 } 45 push_down(rt); 46 int mid = (l + r) >> 1; 47 if(L <= mid) update(L, R, value, lson); 48 if(R > mid) update(L, R, value, rson); 49 push_up(rt); 50 } 51 52 double query(int L, int R, int l, int r, int rt){ 53 if(L <= r && R >= r) return sum[rt]; 54 push_down(rt); 55 double cnt = 1e5; 56 int mid = (l + r) >> 1; 57 if(L <= mid) cnt = min(cnt, query(L, R, lson)); 58 if(R > mid) cnt = min(cnt, query(L, R, rson)); 59 return cnt; 60 } 61 62 bool check(double mid){ 63 build(1, n, 1, mid); 64 for(int i = 1; i <= n; i++){ 65 update(pre[i] + 1, i, 1, 1, n, 1); 66 if(query(1, i, 1, n, 1) <= (double)mid *(i + 1)) return true; 67 } 68 return false; 69 } 70 71 int main(void){ 72 int t; 73 scanf("%d", &t); 74 while(t--){ 75 scanf("%d", &n); 76 memset(pre, 0, sizeof(pre)); 77 memset(last, 0, sizeof(last)); 78 for(int i = 1; i <= n; i++){ 79 scanf("%d", &a[i]); 80 pre[i] = last[a[i]]; 81 last[a[i]] = i; 82 } 83 double l = 0, r = 1; 84 while(r - l > eps){ 85 double mid = (l + r) / 2; 86 if(check(mid)) r = mid - eps; 87 else l = mid + eps; 88 } 89 printf("%.5lf ", r + eps); 90 } 91 return 0; 92 }