完美子图

完美子图 (分治 (starstarstar ))

Descrption

  • (Q) 和小 (P) 都非常喜欢做一些有趣的题目,他们经常互相出一些题目来考对方。
  • 一天,小 (Q) 给小 (P) 出了这样一道题目:给出一个 (n imes n) 的网格图,在网格中放置 (n) 个点,(不会有两个点放置在同一个网格中)。如果一个 (m imes m(1≤m≤n)) 的子网格图恰好包含 (m) 个点,则称这样的子网格图为完美子网格图。
  • 现在小 (Q) 问小 (P),对于给定的网格图存在多少个完美子网格图。小 (P) 这回被难住了,他请你来帮忙,你能帮他解决这个问题吗?

Input

  • 第一行,一个正整数 (n),表示网格图的大小以及点的数量。
  • 接下来 (n) 行,每行两个整数, (x_i,y_i),表示第 (i) 个点的坐标。
  • 保证每一行每一列只有一个点

Output

  • 一行,完美子网格图的数量。

Sample Input

5
1 1
3 2
2 4
5 5
4 3

Sample Output

10

Hint

  • 显然,分别以 ((2,2))((4,4)) 为左上,右下顶点的一个子网格图中有 (3) 个点,我们找到了一个完美子网格图,类似的完美子网格图在原图中能找出 (10) 个。
  • 对于 (30\%) 的数据,(n ≤ 100);
  • 对于 (60\%) 的数据,(n ≤ 5000) ;
  • 对于 (100\%) 的数据,(n ≤ 50000) ;
  • 来源:(Codeforces 526F)

分析

  • 每行每列只有一个,我们很快就能想到我们做状压 (dp) 第一题《特殊方格棋盘》,很容易用一维表示放置状态。
  • 这样我们就得到了一个 (1sim n) 组成的一个序列。问题就可以简化为:给定了 (n) 个数的一个排列,问这个序列中有多少个子区间的数恰好的连续的。相当于统计有多少个子区间 ([l,r]) 满足 (Max-Min=r-l)(Max,Min) 分别为区间 $ [l,r]$ 的最大、最小值。
  • 对这个问题,我们可以用分治来解决,我们把区间分成两部分即:([l,mid],[mid+1,r] (mid=frac{l+r}{2})) ,这样我们只需要考虑跨过 (mid) 的区间,不跨的则是递归的子问题。
    1. 预处理去区间的最大最小值:
    • (Min[i],Max[i]):当 (lle ile mid) 时表示 (isim mid) 的最小值和最大值。
    • (Mix[i],Max[i]):当 (mid+1le i le r) 时表示 (mid+1sim i) 的最小、最大值。
    1. 如果单个的点正好是一个 (1 imes 1) 的完美子图。在两个区间合并的时候,跨过 (mid) 的区间的最值问题分成下面四种情况:
      1. 最大最小值均在左边,此时我们枚举区间的左端点 (i) ,则右端点 (j=i+Max[i]-Min[i])
      2. 最大最小值均在右边,此时我们枚举右端点 (i),则有左端点 (j=i-(Max[i]-Min[i]))
      3. 最小值在 (mid) 左边,最大值在 (mid) 右边,枚举区间的左端点 (i) 则有:(Max[j]-Min[i]=j-iRightarrow Min[i]-i=Max[j]-j)
      4. 最小值在 (mid) 右边,最大值在 (mid) 左边,枚举区间右端点 (i) 则有:(j=i-(Max[j]-Min[i])Rightarrow Min[i]+i=Max[j]+j)
    2. 对于上面的情况 (1,2) 容易解决,只要枚举其中一个端点另一个端点就确定了,但第 (3,4) 种情况的端点不确定,比如说第 (3) 中情况,当左边的 (i) 确定了,但右边的 (j) 并不确定,因为随着 向右扫描,(Max[j]) 在发生变化,所以要扫描右边所有满足条件的 (j) 。不过我们不需要对每一个 (i) 都对 (j)(mid+1) 开始扫描,而只需从上一个 (j) 接着往下扫描即可,因为当 (i--) 的时候,如果左侧增加的这个数更新了最小值,则上一轮已经扫描过 (mid+1sim j) 仍然满足 (Min[j]>Min[i]) 。如果增加的是一个较大的数且大于了 (Max[j]) 此时我们也只需往后扫描找到当前的满足条件的 (j) 即可。具体情况见代码。

Code

#include <bits/stdc++.h>
const int maxn=1e5+5,Inf=0x3f3f3f3f;
typedef long long LL;
int Min[maxn],Max[maxn],cnt[maxn<<1];
int n,a[maxn];
LL ans;
void Divi(int L,int R){
    if(L==R){ans++;return;}//单独一个是完美子图
    int mid=(L+R)>>1;
    Divi(L,mid);
    Divi(mid+1,R);
    //预处理左右两边的最值
    Max[mid]=Min[mid]=a[mid];
    Max[mid+1]=Min[mid+1]=a[mid+1];
    for(int i=mid-1;i>=L;--i){
        Max[i]=std::max(Max[i+1],a[i]);
        Min[i]=std::min(Min[i+1],a[i]);
    }
    for(int i=mid+2;i<=R;++i){
        Max[i]=std::max(Max[i-1],a[i]);
        Min[i]=std::min(Min[i-1],a[i]);
    }
    for(int i=mid;i>=L;i--){//最大、最小均在左侧
        int j=Max[i]-Min[i]+i;//区间[i,j]
        if(j<=R && j>mid && Max[j]<Max[i] && Min[i]<Min[j])
            ans++;
    }
    for(int i=mid+1;i<=R;++i){//都在右侧
        int j=i-Max[i]+Min[i];
        if(j<=mid && j>=L && Max[j]<Max[i] && Min[j]>Min[i])
            ans++;
    }
    int j=mid+1,k=mid+1;
    for(int i=mid;i>=L;--i){//左小右大:Min[i]-i==Max[j]-j
        while(j<=R && Min[j]>Min[i])//右边的最小值要比左边的大
            cnt[Max[j]-j+n]++,++j;//Max[j]-j有可能为负,所以+n
        while(k<j && Max[k]<Max[i])//右边如果有从mid+1开始连续的区间的最大值比左边小--
            cnt[Max[k]-k+n]--,++k;
        ans+=(LL)cnt[Min[i]-i+n];
    }
    while(k<j)//清空,避免影响下面
        cnt[Max[k]-k+n]--,k++;
    j=mid;k=mid;
    for(int i=mid+1;i<=R;++i){//左大右小:Min[i]+i==Max[j]+j
        while(j>=L && Min[j]>Min[i])
            cnt[Max[j]+j]++,--j;
        while(k>j && Max[k]<Max[i])
            cnt[Max[k]+k]--,--k;
        ans+=(LL)cnt[Min[i]+i];
    }
    while(k>j)
        cnt[Max[k]+k]--,--k;
}
void Solve(){
    scanf("%d",&n);
    int x,y;
    for(int i=1;i<=n;++i)
        scanf("%d%d",&x,&y),a[x]=y;
    Divi(1,n);
    printf("%lld
",ans);
}
int main() {
    Solve();
    return 0;
}
原文地址:https://www.cnblogs.com/hbhszxyb/p/13283005.html