HDU - 6070 Dirt Ratio (二分 + 線段樹)
阿新 • • 發佈:2018-12-19
題目連結:http://acm.hdu.edu.cn/showproblem.php?pid=6070
題目大意:給定一個序列a,對於任何一個區間 [l,r],它的“Dirt Ratio”值為區間內不同元素的個數除以區間長度。現在要你求出序列a中哪個區間的“Dirt Ratio”值最小,輸出最小值。
題目思路:由於是要找最小值,我們考慮用二分來找答案。假設當前二分的答案的值為 x ,如果這個序列滿足這個答案的話,必然存在一個區間的 sum[i,j] / len[i,j] <= x,(sum[i,j] 表示區間 [i,j] 內不同元素的個數,len[i,j]為區間 [i,j] 的長度),則該式子可以化為
sum[i,j] - len[i,j] * x <= 0。
對於sum[i,j] 我們可以考慮用線段樹來維護,用pre[i] 表示序列a中上一個值與a[i]相同的位置,然後再按順序往線段樹內加入a[i] ,每次對線段樹的區間 [pre[i] + 1, i] 進行更新+1,這樣加到第 i 個時,對於滿足 j < i 的sum[j] 來說就表示著區間[j,i] 內不同元素的數的個數。
接下來就只用考慮 x 的影響,每次插入a[i]時,對於線段樹區間 [1,i] 更新 -x,這樣當更新到第 i 個元素時,線段樹內的第 j 位的元素正好被減去了(i - j + 1)個x,正好是 j 到 i 的區間長度。這樣當插入到第 i 個數,線段樹內的第 j 位就代表著區間 [j,i] 的“Dirt Ratio”值,線段樹內再維護一下區間最小值,這樣順序插入之後就可以列舉完所有區間了。注意一下二分的次數,如果太多次了就會TLE,由於精度只需要1e-4,所以二分15~20次就夠了。
具體實現看程式碼:
#include <cstdio> #include <cstring> #include <cmath> #include <iostream> #include <queue> #include <vector> #include <algorithm> #define fi first #define se second #define lson l,m,rt<<1 #define rson m+1,r,rt<<1|1 #define pb push_back #define MP make_pair #define lowbit(x) x&-x #define clr(a) memset(a,0,sizeof(a)) #define _INF(a) memset(a,0x3f,sizeof(a)) #define FIN freopen("in.txt","r",stdin) #define IOS ios::sync_with_stdio(false) #define fuck(x) cout<<"["<<#x<<" "<<(x)<<"]"<<endl using namespace std; typedef long long ll; typedef unsigned long long ull; typedef pair<int, int>pii; const int MX = 60000 + 5; int n, _; int a[MX], pre[MX], last[MX]; double sum[MX << 2], lz[MX << 2]; void push_up(int rt) { sum[rt] = min(sum[rt << 1], sum[rt << 1 | 1]); } void push_down(int rt) { if (lz[rt]) { lz[rt << 1] += lz[rt]; lz[rt << 1 | 1] += lz[rt]; sum[rt << 1] += lz[rt]; sum[rt << 1 | 1] += lz[rt]; lz[rt] = 0; } } void build(int l, int r, int rt) { sum[rt] = lz[rt] = 0; if (l == r) return; int m = (l + r) >> 1; build(lson); build(rson); push_up(rt); } void update(int L, int R, double d, int l, int r, int rt) { if (L <= l && r <= R) { sum[rt] += d; lz[rt] += d; return; } push_down(rt); int m = (l + r) >> 1; if (L <= m) update(L, R, d, lson); if (R > m) update(L, R, d, rson); push_up(rt); } double query(int L, int R, int l, int r, int rt) { if (L <= l && r <= R) return sum[rt]; push_down(rt); double res = 1e9; int m = (l + r) >> 1; if (L <= m) res = min(res, query(L, R, lson)); if (R > m) res = min(res, query(L, R, rson)); return res; } bool check(double x) { build(1, n, 1); for (int i = 1; i <= n; i++) { update(pre[i] + 1, i, 1, 1, n, 1); update(1, i, -x, 1, n, 1); if (query(1, i, 1, n, 1) <= 0) return 1; } return 0; } int main() { for (scanf("%d", &_); _; _--) { scanf("%d", &n); for (int i = 0; i <= n; i++) pre[i] = last[i] = 0; for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); pre[i] = last[a[i]]; last[a[i]] = i; } double l = 0, r = 1; for (int i = 0; i < 15; i++) { double mid = (l + r) / 2; if (check(mid)) r = mid; else l = mid; } printf("%.5f\n", l); } return 0; }