HDU 6406 Taotao Picks Apples 【預處理 + 二分 + 思維(rmq || 線段樹)】 多校。 好題!!
阿新 • • 發佈:2019-02-13
傳送門
題意: 題意相當於問你改變一個位置之後,從左往右掃描最大值, 這個最大值會改變多少次. 每次改變獨立
思路:我們首先要預處理出每一個位置從前往後的答案數, 以及字首最大值, 還有從後往前的答案數, 前面兩個可以邊讀入邊處理, 後面那個需要用到單調佇列維護一個最大值來處理, 上次多校出過這樣的題. 然後有了這三個東西后, 對於每次的修改, 我們要找的就是修改位置前面是否有比修改後的這個數大的, 然後和自己比較一下, 如果沒有則ans += 1 + 字首處理的答案, 否則就不加1, 然後我們要找的就是比較後的最大值從修改位置往右第一個比它大的位置在哪, 然後ans += 字尾處理的答案即可. 處理判斷不存在的情況.
然後這道題最經典的問題就是求某段區間中第一個大於某個值的位置在哪裡, (不包含該值的位置), 然後這個有很多方法, 可以用線段樹維護每個位置的數字, 然後詢問區間最大值, 再樹裡二分找, 也可以再外面用二分找, 然後還是用rmq或者線段樹維護最大值, 都是可以的. 記住這個經典問題.
綜合一下複雜度: 外面二分找 + rmq是比較優秀的. 所以就給出這個寫法.
AC Code
const int maxn = 1e5+5;
int a[maxn];
int dp1[maxn], dp2[maxn];
void read(int &x){
char ch = getchar();x = 0;
for (; ch < '0' || ch > '9'; ch = getchar());
for (; ch >='0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
}
int dp[maxn][20];
int n, m;
void rmq() {
for(int j=1;(1<<j)<=n;j++)
for(int i=1;i+(1<<(j-1))<=n;i++)
dp[i][j]=max(dp[i][j-1 ], dp[i+(1<<(j-1))][j-1]);
}
int get_ma(int l, int r) {
int k = 0; if (l > r) return 0;
while((1<<(k+1))<=r-l+1)k++;
return max(dp[l][k] ,dp[r-(1<<k)+1][k]);
}
int Find(int l, int r, int x) {
int ans = -1;
while(l <= r) {
int mid = (l + r) >> 1;
int tmp = get_ma(l, mid);
if (tmp > x) {
ans = mid;
r = mid - 1;
}
else l = mid + 1;
}
return ans;
}
int que[maxn], cnt[maxn];
void solve() {
read(n); read(m);
int mx = 0;
for(int i = 1 ; i <= n ; i ++) {
read(a[i]); dp[i][0] = a[i];
if (a[i] > mx) {
dp1[i] = dp1[i-1] + 1;
mx = a[i];
}
else dp1[i] = dp1[i-1];
cnt[i] = mx;
}
rmq();
int tail = 0, head = 1;
for (int i = n ; i >= 1 ; i --) {
while(head <= tail && que[tail] <= a[i]) --tail;
que[++tail] = a[i];
dp2[i] = (tail - head + 1);
}
// cnt[i] 表示i位置前面最大數是多少, dp1[i]表示字首的答案, dp2是字尾處理的答案.
while(m--) {
int l, r;
read(l); read(r);
int ans = dp1[l-1]; // 前面是具有可推性質的!!!
if (r > cnt[l-1]) ++ ans;
r = max(r, cnt[l-1]);
int pos = Find(l+1, n, r);
if (pos != -1) ans += dp2[pos];
printf("%d\n", ans);
}
}