码迷,mamicode.com
首页 > 其他好文 > 详细

完美子图

时间:2020-07-11 12:25:48      阅读:93      评论:0      收藏:0      [点我收藏+]

标签:特殊   增加   gre   扫描   接下来   font   顶点   des   有一个   

完美子图 (分治 \(\star\star\star?\))

Descrption

  • \(Q\) 和小 \(P\) 都非常喜欢做一些有趣的题目,他们经常互相出一些题目来考对方。
  • 一天,小 \(Q\) 给小 \(P\) 出了这样一道题目:给出一个 \(n\times n\) 的网格图,在网格中放置 \(n\) 个点,(不会有两个点放置在同一个网格中)。如果一个 \(m\times 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\) 第一题《特殊方格棋盘》,很容易用一维表示放置状态。
  • 这样我们就得到了一个 \(1\sim 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]\):当 \(l\le i\le mid\) 时表示 \(i\sim mid\) 的最小值和最大值。
    • \(Mix[i],Max[i]\):当 \(mid+1\le i \le r\) 时表示 \(mid+1\sim i\) 的最小、最大值。
    1. 如果单个的点正好是一个 \(1\times 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-i\Rightarrow Min[i]-i=Min[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+1\sim 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]
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数组,memset也可以,但会超时
        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数组,memset也可以,但会超时
        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\n",ans);
}
int main() {
    Solve();
    return 0;
}]
        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\n",ans);
}
int main() {
    Solve();
    return 0;
}

完美子图

标签:特殊   增加   gre   扫描   接下来   font   顶点   des   有一个   

原文地址:https://www.cnblogs.com/hbhszxyb/p/13283005.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!