codeforces 628e

好像是被我水过了  可怜  因为题目给了5s


题意:问图像中到底有多少个z型图像。


思路:对于每一个点都有可能是最左下角的点,那么把对于每一个 斜边 看成大区间  区间每一个点都是都有两个值 一个是能到右边的最远距离 一个是到左边的最远距离+i(加权)

只要对于每一个点的答案是把右边的最远距离作为区间长度 统计出区间 左边是否能满足


比如 3x3

zzz

.z.

zzz


对于 最左下角的点 统计区间长度为3  加权距离为行数+左边的最远距离 为4 然后区间内 只要大于4的数 就是满足条件的

就是转换成问这个数在区间内 是第几大的问题  就抄了别人模版=。=

ps:这个方法4s多才过,虽然我代码写搓 也有部分原因- -,不过  他们都是1s内过的 尴尬 惭愧


#include<stdio.h>
#include<string.h>
#include<vector>
#include<algorithm>
#include<queue>
#include<math.h>
using namespace std;
#define LL long long
#define MOD 1000000007

int a[3005][3005];

int c[3005][3005];
int d[3005][3005];
char ch[3005][3005];
#define CLR(a) memset(a,0,sizeof(a))

const int MAXN = 3010;

struct Node
{
  int l,r;
  int len(){return r-l;}
  int mid(){return (l+r)/2;}
  bool in(int ll,int rr){return l>=ll && r<=rr;}
  void lr(int ll,int rr){l = ll; r=rr;}
}node[MAXN*4];

int num_left[15][MAXN],seg[15][MAXN],sa[MAXN];

void Init()
{
  //CLR(seg),CLR(num_left),CLR(node);
    memset(num_left,0,sizeof(num_left));
    memset(node,0,sizeof(node));
}

void PaBuild(int t,int l, int r,int d)
{
  node[t].lr(l,r);
  if(node[t].len() == 0)return;
  int mid=(l+r)/2,lsame=mid-l+1;
  for(int i=l;i<=r;i++)//首先确定分到每一侧的数的数目
    if(seg[d][i] < sa[mid])//因为相同的元素可能被分到两侧
      lsame--;
  int lpos=l,rpos=mid+1;
  for(int i=l;i<=r;i++)
  {
    if(i == l)
      num_left[d][i]=0;
    else
      num_left[d][i]=num_left[d][i-1];
    if(seg[d][i] < sa[mid])
    {
      num_left[d][i]++;
      seg[d+1][lpos++]=seg[d][i];
    }
    if(seg[d][i] > sa[mid])
      seg[d+1][rpos++] = seg[d][i];
    if(seg[d][i] == sa[mid])
      if(lsame > 0)// 如果大于0,说明左侧可以分和sa[mid]相同的数字
      {
        lsame--;
        num_left[d][i]++;
        seg[d+1][lpos++]=seg[d][i];
      }
      else	//反之,说明左侧数字已经分满了,就分到右边去
        seg[d+1][rpos++]=seg[d][i];
  }
  PaBuild(t*2,l,mid,d+1);
  PaBuild(t*2+1,mid+1,r,d+1);
}
void Build(int s,int t)
{
  sort(sa+s,sa+t+s);
  PaBuild(1,s,t,1);
}

int find_rank(int t,int l,int r,int d,int val)//val指的是k
{
  if(node[t].len() == 0)return seg[d][l];
  int s,ss;   //s表示区间[l,r]有多少个小于sa[mid]的数被分到左边
  if( l == node[t].l)//ss表示从当前区间的L到l-1有多少个小于sa[mid]的数被分到左边,L,R指的是树上当前节点的区间范围
    ss=0;
  else
    ss=num_left[d][l-1];
  s=num_left[d][r]-ss;
  if(s>=val)
    return find_rank(t*2, node[t].l+ss,node[t].l+ss+s-1,d+1,val);
  else
  {
    int mid = node[t].mid();
    int bb=l-node[t].l-ss;  //表示从当前区间L到l-1有多少个分到右边
    int b=r-l+1-s;		  //表示[l,r]有多少个分到右边
    return find_rank(t*2+1,mid+bb+1,mid+bb+b,d+1,val-s);
  }
}

int Query(int s,int t,int k)
{
  return find_rank(1,s,t,1,k);
}


int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%s",ch[i]+1);
    n=max(n,m);
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
        {
            if(ch[i][j]=='z')
                a[i][j]=1;
        }
    for(int i=1;i<=n;i++)
    {
        int sum=0;
        for(int j=n;j>=1;j--)
        {
            if(a[i][j])
                sum++;
            else
                sum=0;
            c[i][j]=sum;
        }
    }
    for(int i=1;i<=n;i++)
    {
        int sum=0;
        for(int j=1;j<=n;j++)
        {
            if(a[i][j])
                sum++;
            else
                sum=0;
            d[i][j]=sum;
        }
    }
    LL ans=0;
    for(int i=1;i<2*n;i++)
    {
        int len=1;
        Init();
        for(int j=1;j<=n;j++,len++)
        {
            int x=i-j+1,y=j;
            if(i-j+1<=0)break;
            if(i-j+1>n){
                sa[len]=seg[1][len]=0;
                continue;
            }
            sa[len]=seg[1][len]=d[y][x]+j;
        }
        Build(1,len-1);
        int h=1;
        for(int j=1;j<=n;j++)
        {
            if(i-j+1<=0)break;
            if(i-j+1>n)continue;
            int x=i-j+1,y=j;
            if(d[y][x]==0){h=j+1;continue;}

            int t=max(j-c[y][x]+1,h);
            int l=1,r=j-t+1;
            int ret=r;

            while(l<=r)
            {
                int mid=(l+r)/2;
                int q=Query(t,j,mid);
                if(q>=j+1)
                {
                    r=mid-1;
                    ret=mid;
                }
                else
                {
                    l=mid+1;
                }
            }
            ans+=j-t+1-ret+1;
        }
    }
    printf("%lld\n",ans);
    return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值