【Codeforces 526 F. Pudding Monsters】CDQ分治+乱搞

cf526F
• n * n的矩形,每行每列有且仅有一个特殊点
• 求问存在多少k * k的子矩形,使得子矩形内有k个特殊点
• 1 ≤ n ≤ 3 * 1e5
• 二维前缀和
• 枚举即可
• O(n ^ 3) 肯定T
• 转换为数列A,每个特殊点的横坐标为下标,纵坐标为权值 • Ax = y
• 题目相当于询问存在多少(l, r)使得
• max(Al…Ar) - min(Al…Ar) == r - l
• O(n ^ 2)
• 考虑分治
• 1. 最大值最小值在同侧 • 通过枚举l可以计算出r,判断是否可行即可
• 2. 最大值最小值在异侧 • 假设最小值在左侧,最大值在右侧 • max(Amid…Ar) - r == min(Al…Amid) - l
• 由于最大值单调不减,最小值单调不增
• min(A[mid+1]…A[r2]) > min(Al…AMid) • max(A[mid+1]…A[r1−1]) < max(Al…AMid) • [r1, r2]包含了所有可能合法的右端点
• 类似桶排的记录即可 • O(n log n)

这道题真的TM是写吐了 画了三个小时才写完 希望能增强我的分治能力

/*
    if you can't see the repay
    Why not just work step by step
    rubbish is relaxed
    to ljq
*/
#include <cstdio>
#include <cstring>
#include <iostream>
#include <queue>
#include <cmath>
#include <map>
#include <stack>
#include <set>
#include <sstream>
#include <vector>
#include <stdlib.h>
#include <algorithm>
using namespace std;

#define dbg(x) cout<<#x<<" = "<< (x)<< endl
#define dbg2(x1,x2) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<endl
#define dbg3(x1,x2,x3) cout<<#x1<<" = "<<x1<<" "<<#x2<<" = "<<x2<<" "<<#x3<<" = "<<x3<<endl
#define max3(a,b,c) max(a,max(b,c))
#define min3(a,b,c) min(a,min(b,c))

typedef pair<int,int> pll;
typedef long long ll;
const int inf = 0x3f3f3f3f;
const int _inf = 0xc0c0c0c0;
const ll INF = 0x3f3f3f3f3f3f3f3f;
const ll _INF = 0xc0c0c0c0c0c0c0c0;
const ll mod =  (int)1e9+7;

ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
ll ksm(ll a,ll b,ll mod){int ans=1;while(b){if(b&1) ans=(ans*a)%mod;a=(a*a)%mod;b>>=1;}return ans;}
ll inv2(ll a,ll mod){return ksm(a,mod-2,mod);}
void exgcd(ll a,ll b,ll &x,ll &y,ll &d){if(!b) {d = a;x = 1;y=0;}else{exgcd(b,a%b,y,x,d);y-=x*(a/b);}}//printf("%lld*a + %lld*b = %lld\n", x, y, d);

/*namespace sgt
{
    #define mid ((l+r)>>1)

    #undef mid
}*/
const int MAX_N = 300025;
ll cnt[MAX_N<<1+5],lmax[MAX_N],lmin[MAX_N],rmax[MAX_N],rmin[MAX_N],n,arr[MAX_N];
ll divide(int l,int r)
{
    if(l==r) return 1;
    ll ret = 0;
    int mid =(l+r)>>1;
    lmax[mid] = lmin[mid] = arr[mid],rmax[mid+1] = rmin[mid+1] = arr[mid+1];
    for(int i = mid-1;i>=l;--i)
        lmax[i] = max(lmax[i+1],arr[i]),lmin[i] = min(lmin[i+1],arr[i]);
    for(int i = mid+2;i<=r;++i)
        rmax[i] = max(rmax[i-1],arr[i]),rmin[i] = min(rmin[i-1],arr[i]);

    for(int i = l;i<=mid;++i)//同侧都在左边
    {
        int j = lmax[i] - lmin[i] + i;
        if(j>mid&&j<=r&&rmin[j]>lmin[i]&&rmax[j]<lmax[i]) ret++;
    }
    for(int j = mid+1;j<=r;++j)//同侧都在右边
    {
        int i = j + rmin[j] - rmax[j];
        if(i>=l&&i<=mid&&rmin[j]<lmin[i]&&rmax[j]>lmax[i]) ret++;
    }
    for(int i = mid,r1 = mid+1,r2 = mid + 1;i>=l&&r1<=r;--i)//左小右大
    {
        while(r2<=r&&rmin[r2]>lmin[i]) cnt[rmax[r2]-r2+MAX_N]++,r2++;
        while(r1<=r&&rmax[r1]<lmax[i]) cnt[rmax[r1]-r1+MAX_N]--,r1++;
        if(r1<r2) ret+=cnt[lmin[i] -i +MAX_N];
    }
    for(int i = mid+1;i<=r;++i)
        cnt[rmax[i]-i+MAX_N] = 0;
    for(int j = mid + 1,l1 = mid ,l2 = mid;j<=r&&l2>=l;++j)//左大右小
    {

        while(l1>=l&&lmin[l1]>rmin[j]) cnt[lmax[l1]+l1]++,l1--;
        while(l2>=l&&lmax[l2]<rmax[j]) cnt[lmax[l2]+l2]--,l2--;
        if(l1<l2) ret += cnt[rmin[j]+j];
    }

    for(int i = l;i<=mid;++i)
        cnt[lmax[i]+i] = 0;
    return ret+divide(l,mid)+divide(mid+1,r);
}

int main()
{
    //ios::sync_with_stdio(false);
    //freopen("a.txt","r",stdin);
    //freopen("b.txt","w",stdout);
    scanf("%lld",&n);
    for(int i = 1;i<=n;++i)
    {
        int x;
        ll y;
        scanf("%d%lld",&x,&y);
        arr[x] = y;
    }
    ll ans = divide(1,n);
    printf("%lld\n",ans);

    //fclose(stdin);
    //fclose(stdout);
    //cout << "time: " << (long long)clock() * 1000 / CLOCKS_PER_SEC << " ms" << endl;
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值