2019暑假集训8.24(problem2.baritone)(链表(巧妙数据结构))

数据结构好题!

因为思路是第一次见,所以就直接说思路。

题目抽象:

这是一个矩形

里面有很多的点,求至少覆盖k个点的矩形有多少个

 先确定上边界,下边界为低端

上边界下面的点用链表存起来

考虑以每个点作为左边界的贡献(线上的点也算在矩形内),假如k=3,那么右边界至少在橙色这根线这儿

符合要求的矩形的左边界范围L和右边界范围R如图

做出的贡献为L*R,如果前面还有点注意是左开右闭(因为右边到底都是可以的)

                                                                             然后移动左边界

到下一个点再计算贡献

再来移动下边界

一些点可能在同一水平线上,所以一开始可以用vector存起来,然后要删的点一个一个处理。现在考虑下边界移上去后删去一些点的影响

比如删去这个点

 (k=3),对于前面k-1个点和后面一个点会造成影响:前面k-1个点:删去后因为每次至少覆盖k个点,所以右边界要向右移动一个点;后面一个点:因为删去了前面的那个点,所以左边界要向左移动


 所以总的思路就是:枚举上边界,确定左边界和右边界,再移动下边界。

时间复杂度O(n^2 k)。上边界往下,下边界走到上边界,上边界n条,相当于图从上往下用线扫n次,每次链表中最多n个点,那么左边界最多有n条,每一条往后推k个点,n*n*k=n^2*k。

实现的细节在代码里有说,记得注意左开右闭以及删点影响的范围(画图看一下就明白了)。

#include<bits/stdc++.h>
#define N 3021
#define LL long long
using namespace std;
int read()
{
    int x=0,f=1;char s=getchar();
    while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
    while(s>='0'&&s<='9'){x=x*10+s-'0';s=getchar();}
    return x*f;
}
vector<int>belong[N];
struct point{
    int x,y;
}a[N*N];
LL val[N],id[N],pointx[N],pointy[N],nextt[N],pre[N],ord[N];
LL sum=0,ans=0,cnt=0,k,r,c,n;
bool cmp(const int &u,const int &v)
{
    if(pointy[u]==pointy[v])return pointx[u]<pointx[v];
    return pointy[u]<pointy[v];
} 
void del(int d)
{
    nextt[pre[d]]=nextt[d];
    pre[nextt[d]]=pre[d];
    sum-=val[d];//以d为左边界的矩形要减去 
    int x=nextt[d],y=nextt[d];//对前k的点和后一个点作为左边界会有影响
    for(int i=1;i<k;++i)y=nextt[y];
    for(int i=1;i<=k;++i)
    {
        int v=(pointy[x]-pointy[pre[x]])*(c-pointy[y]+1);//如果说两点在同一竖直线上则v=0,也满足要求 
        sum+=v-val[x];
        val[x]=v;
        x=pre[x];y=pre[y];
    }
}
int idc;
int main()
{
    freopen("baritone.in","r",stdin);
    freopen("baritone.out","w",stdout);
    r=read(),c=read(),n=read();k=read();
    for(int i=1;i<=n;++i)
      a[i].x=read(),a[i].y=read(),belong[a[i].x].push_back(i);////保存这一行有那些点 
    for(int i=1;i<=r;++i)//枚举上边界 
    {
        sum=0,cnt=0;//sum是全局变量,每一层下边界改变时会变化,每一个上边界对应一个sum 
        for(int j=0;j<=10;++j)//防止越界,k最大是10 
          pointy[++cnt]=0,pointy[++cnt]=c+1;
        for(int j=1;j<=n;++j)
          if(a[j].x>=i)//pointx 行坐标,pointy 纵坐标 
            pointy[++cnt]=a[j].y,pointx[cnt]=a[j].x,ord[j]=cnt;//ord[j]是第j个点在链表中的序号 
        for(int j=1;j<=cnt;++j)id[j]=j;
        sort(id+1,id+1+cnt,cmp);
        for(int j=1;j<cnt;++j)//前驱后继 
          nextt[id[j]]=id[j+1],pre[id[j+1]]=id[j];
        nextt[id[cnt]]=id[cnt];pre[id[1]]=id[1];
        for(int j=1;j<=cnt;++j)//枚举左边界
        {
            int R=j;
            for(int p=1;p<k;++p) R=nextt[R];//R为右边界 
            val[j]=(pointy[j]-pointy[pre[j]])*(c-pointy[R]+1);//乘法原理 ,右边界一直到c都是满足的 
            sum+=val[j];//val上边界为i,下边界为r,左边界为j的矩形个数 
        }
        for(int j=r;j>=i;--j)//枚举下边界
        {
            ans+=sum;
            for(int p=0;p<(int)belong[j].size();++p)
              del(ord[belong[j][p]]);
        } 
    }
    printf("%lld\n",ans);
} 
/*
2 2 1 1
1 2

3 2 3 3
1 1
3 1
2 2

3 2 3 2
1 1
3 1
2 2
*/
View Code

这道题还是很有价值的!✿✿ヽ(°▽°)ノ✿

猜你喜欢

转载自www.cnblogs.com/yyys-/p/11405741.html