bzoj 2727: [HNOI2012]双十字

Description

在C 部落,双十字是非常重要的一个部落标志。所谓双十字,如下面两个例子,由两条水平的和一条竖直的“1”线段组成,要求满足以下几个限制:

我们可以找到 5 个满足条件的双十字,分别如下:

注意最终的结果可能很大,只要求输出双十字的个数 mod 1,000,000,009 的值

·两条水平的线段不能在相邻的两行。

·竖直线段上端必须严格高于两条水平线段,下端必须严格低于两条水平线段。

·竖直线段必须将两条水平线段严格划分成相等的两半。

·上方的水平线段必须严格短于下方的水平线段。

所以上面右边的例子是满足要求的最小的双十字。

现在给定一个   R?C的01 矩阵,要求计算出这个 01 矩阵中有多少个双十字。

例如下面这个例子,R=6,C=8,01 矩阵如下:

Input

第一行为用空格隔开的两个正整数 R和C,分别表示

01矩阵的行数和列数。输入文件第二行是一个非负整数N,表示01矩阵中“0”的个数。接下来的N行,每行为用空格隔开的两个正整数x和y(1≤x≤R,1≤y≤C),表示(x,y)是一个“0”。数据保证N个“0”的坐标两两不同。

数据保证R,C,N≤10,000,R*C≤1,000,000.(事实上R*C可能稍大于原设定)

Output

D mod 1,000,000,009 的结果,其中D 为要求的 01

矩阵中双十字的个数。

Sample Input

6 8

12

1 2

1 3

1 4

1 6

2 2

3 2

3 3

3 4

3 7

6 4

6 6

4 8

Sample Output

5

HINT

Source

day1

跟这个题断断续续鏖战了几天了,首先在考场上想了一个n^3的暴力,然后智障的枚举的是每一列。。。

然后变成了三维偏序(j>top)并且那个东西(top不一样)还不好用树状数组维护,然后傻逼压常,结果vector葬送前程。。。

其实枚举一个个连续的纵轴会好做很多,枚举每一个纵轴的话,在一个纵轴上top都是一样的,然后又是二维偏序,树状数组维护即可。。。

具体做法:

对于每个点求出heng[i],down[i],up[i],即横着扩展的长度,往下扩展的长度,往上扩展的长度。。。(我是个sb第一个数组是用二分+前缀和找的)

然后考虑暴力的枚举两个十字的中心,然后分情况讨论一下上下的长度能有多少个组合是合法的,然后再乘上 上下多余的长度。。。

设上面的heng为l1,下面的heng为l2,上面往上扩展的长度为s,下面往下扩展的长度为x。。。

然后计算的话就是一堆等差数列求和,然后用树状数组维护一下,贼恶心。。。

// MADE BY QT666
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<iostream>
#include<cstring>
#include<map>
#include<vector>
#define RG register
#define int long long
using namespace std;
typedef long long ll;
const int N=3000050;
const int rhl=1000000009;
vector<int> Map[30000],id[30000];
int R,C,n;
int heng[N],down[N],up[N],tt,sum[200000];
ll ans,inv[10],tr[50050][5],T=40000,xu=10000,q[N],res;
inline void pre(int g){
  memset(sum,0,sizeof(sum));
  for(RG int i=1;i<=C;i++) sum[i]=sum[i-1]+Map[g][i];
}
inline int query_left(int i){
  RG int l=1,r=i,ret=i;
  while(l<=r){
    int mid=(l+r)>>1;
    if(sum[i]-sum[mid-1]==(i-mid+1)) r=mid-1,ret=mid;
    else l=mid+1;
  }
  return i-ret;
}
inline int query_right(int i){
  RG int l=i,r=C,ret=i;
  while(l<=r){
    int mid=(l+r)>>1;
    if(sum[mid]-sum[i-1]==(mid-i+1)) ret=mid,l=mid+1;
    else r=mid-1;
  }
  return ret-i;
}
inline void work_heng(int g){
  for(int i=1;i<=C;i++){
    if(Map[g][i]){
      int l1=query_left(i),l2=query_right(i);
      heng[id[g][i]]=min(l1,l2);
    }
  }
}
inline void work_down(int g){
  for(RG int i=R-1;i>=1;i--){
    if(Map[i][g]){
      if(Map[i+1][g]) down[id[i][g]]=down[id[i+1][g]]+1;
    }
  }
}
inline void work_up(int g){
  for(RG int i=2;i<=R;i++){
    if(Map[i][g]){
      if(Map[i-1][g]) up[id[i][g]]=up[id[i-1][g]]+1;
    }
  }
}
inline int calc(ll sx,ll mx){return (sx+mx)*(mx-sx+1)%rhl*inv[2]%rhl;}
inline ll qpow(ll a,ll b){ll ans=1; while (b){ if (b&1) (ans*=a)%=rhl; (a*=a)%=rhl,b>>=1; } return ans; }
inline void update(int x,int val,int type){
  x+=xu;
  for(int i=x;i<=T;i+=i&-i) tr[i][type]+=val;
}
inline int query(int x,int type){
  int ret=0;x+=xu;
  for(int i=x;i;i-=i&-i) ret+=tr[i][type];
  return ret;
}
inline void work(int i,int g,int top,int type){
  int L=heng[id[i-1][g]];
  update(L,(i-1-top)*type,1);
  update(L,L*(i-1-top)*type,2);
  update(L,L*L*(i-1-top)*type,3);
  update(L,calc(1,L-1)*(i-1-top)*type,4);
}
inline void solve(int g){
  int top=1,la=1+down[id[1][g]];
  while(1){
    if(la>R) break;
    for(int i=top;i<=la;i++){
      ll l=heng[id[i][g]],x=down[id[i][g]];
      (ans+=(query(C,1)-query(l,1))%rhl*calc(1,l-1)*x%rhl)%=rhl;
      ans+=(l*query(l,2)-query(l,3)+query(l,4))*x;
      if(i>top){
    q[++res]=i;work(i,g,top,1);
      }
    }
    for(int i=1;i<=res;i++) work(q[i],g,top,-1);
    top=la+1;res=0;
    while(top<=R&&Map[top][g]==0) top++;
    la=top+down[id[top][g]];
  }
}
main(){
  scanf("%lld%lld%lld",&R,&C,&n);
  inv[2]=qpow(2,rhl-2);
  for(RG int i=1;i<=R;i++) Map[i].push_back(0),id[i].push_back(0);
  for(RG int i=1;i<=R;i++)
    for(RG int j=1;j<=C;j++)
      ++tt,Map[i].push_back(1),id[i].push_back(tt);
  for(int i=1;i<=C;i++) Map[R+1].push_back(0),id[R+1].push_back(0);
  for(RG int i=1;i<=n;i++){
    int x,y;scanf("%lld%lld",&x,&y);Map[x][y]=0;
  }
  for(RG int i=1;i<=R;i++) pre(i),work_heng(i);
  for(RG int i=1;i<=C;i++) work_down(i);
  for(RG int i=1;i<=C;i++) work_up(i);
  for(RG int i=1;i<=C;i++) solve(i);
  printf("%lld\n",ans);
  return 0;
}

  

时间: 2024-10-07 02:41:46

bzoj 2727: [HNOI2012]双十字的相关文章

bzoj 2732 [HNOI2012]射箭 半平面交(刘汝佳版不超时) + 整型二分处理

题目来源: http://61.187.179.132/JudgeOnline/problem.php?id=2732 题意:   对于一个靶子, 得到两个不等式. 裸地半平面交 . 分析: 用的 一般的 模板,总是TLE . 改成了 刘汝佳 版本 ,依然超时, 所谓的常数太大???? 后来注意到 : 当    判断两个向量平行且 同向 ,取左边的一个,不要用 叉积,用极角判断, 可行. 精度 开 1e -16 , 卡精度严重. 注意:这里 也不需要用 friend 写, 也可以ac. 整型二分

BZOJ 2733: [HNOI2012]永无乡(treap + 启发式合并 + 并查集)

不难...treap + 启发式合并 + 并查集 搞搞就行了 ---------------------------------------------------------------------------------------- #include<cstdio> #include<cstring> #include<algorithm> #include<iostream> #define rep(i, n) for(int i = 0; i &l

bzoj 2734: [HNOI2012]集合选数 状压DP

2734: [HNOI2012]集合选数 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 560  Solved: 321[Submit][Status] Description <集合论与图论>这门课程有一道作业题,要求同学们求出{1, 2, 3, 4, 5}的所有满足以 下条件的子集:若 x 在该子集中,则 2x 和 3x 不能在该子集中.同学们不喜欢这种具有枚举性 质的题目,于是把它变成了以下问题:对于任意一个正整数 n≤100000,如何

Bzoj 2733: [HNOI2012]永无乡 数组Splay+启发式合并

2733: [HNOI2012]永无乡 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 3955  Solved: 2112[Submit][Status][Discuss] Description 永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可 以将这 n 座岛排名,名次用 1 到 n 来表示.某些岛之间由巨大的桥连接,通过桥可以从一个岛 到达另一个岛.如果从岛 a 出发经过若干座(含 0 座)桥可以到达

bzoj 2733: [HNOI2012]永无乡 离线+主席树

2733: [HNOI2012]永无乡 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 1167  Solved: 607[Submit][Status] Description 永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可 以将这 n 座岛排名,名次用 1 到 n 来表示.某些岛之间由巨大的桥连接,通过桥可以从一个岛 到达另一个岛.如果从岛 a 出发经过若干座(含 0 座)桥可以到达岛 b,则称岛 a

[HNOI2012]双十字

Description 在C 部落,双十字是非常重要的一个部落标志.所谓双十字,如下面两个例子,由两条水平的和一条竖直的“1”线段组成,要求满足以下几个限制: 我们可以找到 5 个满足条件的双十字,分别如下: 注意最终的结果可能很大,只要求输出双十字的个数 mod 1,000,000,009 的值 ·两条水平的线段不能在相邻的两行. ·竖直线段上端必须严格高于两条水平线段,下端必须严格低于两条水平线段. ·竖直线段必须将两条水平线段严格划分成相等的两半. ·上方的水平线段必须严格短于下方的水平线

[BZOJ 2729][HNOI2012]排队(排列组合+高精)

Description 某中学有 n 名男同学,m 名女同学和两名老师要排队参加体检.他们排成一条直线,并且任意两名女同学不能相邻,两名老师也不能相邻,那么一共有多少种排法呢?(注意:任意两个人都是不同的) Solution 好像必须写压位高精的QAQ 先排n名男生,插空,讨论两名老师插在两个不同的空里的情况和先排在一起再在中间插一名女生的情况 #include<iostream> #include<cstdio> #include<cstring> #include&

bzoj 2733: [HNOI2012]永无乡

23333用并查集维护联通,然后网上搞splay就行啦. %高端splay写法(2333写在struct里了,是不是叫构造函数什么的??) 1 #include<bits/stdc++.h> 2 #define N 100005 3 #define LL long long 4 #define ls tr[x][0] 5 #define rs tr[x][1] 6 using namespace std; 7 inline int ra() 8 { 9 int x=0,f=1; char ch

bzoj 2730: [HNOI2012]矿场搭建

Description 煤矿工地可以看成是由隧道连接挖煤点组成的无向图.为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处.于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口.请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数. Input 输入文件有若干组数据,每组数据的第一行是一个正整数 N(N≤500),表示工地的隧道数,接下来的 N 行每行是用空格隔开的两个整数 S 和 T