[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

题解:

首先因为R与C不确定,所以我们需要将点们放在一个一维数组中,算算编号就好了

接下来记录lr[i]与down[i]分别表示点i最多可以向左右延伸和向下延伸多少个1(不包括自己)

然后我们枚举双十字的下面那个交点,推一推公式:

{注:len是下面横线的长度,top表示能到达的最上的位置(连续的1),j是上面的横线的中心位置}

对于一个点i,它对答案的贡献是:$$\sum_{len=1}^{lr[i]} {min?(lr[j],len-1)*(j-top)*down[i]}$$

显然这个min非常恶心,我们考虑把它拆开成下面的样子:

1.当(lr[j] <= len-1) 贡献是$$\sum_{len=1}^{lr[i]} {lr[j]×(j-top)×down[i]}$$

2.当(lr[j] > len-1) 贡献是$$\sum_{len=1}^{lr[i]} {(len-1)×(j-top)×down[i]}$$

再进一步变形: 
1.当(lr[j] <= lr[i]) 贡献$$((lr[i]×lr[j]-lr[j]×\frac{lr[j]+1}{2}×down[i]×(j-top))$$

2.当(lr[j] > lr[i]) 贡献是$$(lr[i]×\frac{lr[i]-1}{2}×down[i]×(j-top))$$

(对于1的解释: lr[i]×lr[j]是总方案数,lr[j]×(lr[j]+1)/2是不合法方案数)

现在,需要解决的是所有带j的式子,我们定义3个树状数组t1, t2, t3,分别记录:

1.$$(-lr[j]*\frac{lr[j]+1}{2})×(j-top)$$

2.$$lr[j]×(j-top)$$

3.$$j-top$$

把lr[i]作为位置插入,先枚举列再枚举行,每次注意清空。

并且因为两根横线不能挨在一起,所以枚举点(i, j)插入(i-1, j)的值

  1 //Never forget why you start
  2 #include<iostream>
  3 #include<cstdio>
  4 #include<cstdlib>
  5 #include<cstring>
  6 #include<cmath>
  7 #include<algorithm>
  8 #define mod (1000000009)
  9 using namespace std;
 10 typedef long long lol;
 11 int n,m,l,c[1300005],down[1300005];
 12 //c[]表示一个点最多能向左右两边延伸多少的长度
 13 //down[]表示一个点最多能向下延伸多少长度
 14 bool map[1300005];
 15 lol ans=0;
 16 int p(int x,int y) {
 17     return (x-1)*m+y;
 18 }//计算坐标在数组中对应的值
 19 int lowbit(int x) {
 20     return x&(-x);
 21 }
 22 struct BIT {
 23     int ord[10005],tot;//这个数组是树状数组的骚操作,每次记录修改的位置,然后清零的时候就不需要memset了
 24     lol c[10005];
 25     void clean() {
 26         int i,j;
 27         for(i=1; i<=tot; i++)
 28             for(j=ord[i]; j<=m; j+=lowbit(j))c[j]=0;//清零的时候只要修改所记录的位置就好
 29         tot=0;
 30     }
 31     void add(int x,lol v) {
 32         ord[++tot]=x;//记录修改位置
 33         int i;
 34         for(i=x; i<=m; i+=lowbit(i))(c[i]+=v)%=mod;
 35     }
 36     lol query(int x) {
 37         int i;
 38         lol ans=0;
 39         for(i=x; i; i-=lowbit(i))
 40             (ans+=c[i])%=mod;
 41         return ans;
 42     }
 43 } t1,t2,t3;
 44 /*
 45 c[j]>c[i] c[i]*(c[i]-1)/2*(j-top)*down[i];
 46 c[j]<=c[i] c[i]*c[j]*(j-top)*down[i]-c[j]*(c[j]+1)/2*(j-top)*down[i]
 47
 48 t1:-c[j]*(c[j]+1)/2*(j-top)
 49 t2:c[j]*(j-top)
 50 t3:(j-top)
 51 */
 52 //本题公式推导到最后就是这个样子
 53 void solve() {
 54     int i,j;
 55     for(j=1; j<=m; j++) {
 56         t1.clean();
 57         t2.clean();
 58         t3.clean();//每换一列就要将树状数组清零
 59         int top=0;
 60         for(i=1; i<=n; i++) {
 61             int t=p(i,j);
 62             if(map[t]) {
 63                 top=i;
 64                 t1.clean();
 65                 t2.clean();
 66                 t3.clean();
 67                 continue;
 68             }
 69             (ans+=1ll*t1.query(c[t])*down[t]%mod)%=mod;
 70             (ans+=1ll*t2.query(c[t])*c[t]*down[t]%mod)%=mod;
 71             (ans+=1ll*(t3.query(m)-t3.query(c[t]))*c[t]*(c[t]-1)/2*down[t]%mod)%=mod;
 72             t=p(i-1,j);
 73             if(i==1)continue;
 74             if(c[t]) {
 75                 t1.add(c[t],-1ll*c[t]*(c[t]+1)/2*(i-1-top-1));
 76                 t2.add(c[t],1ll*c[t]*(i-1-top-1));
 77                 t3.add(c[t],1ll*(i-1-top-1));
 78             }
 79         }//更新答案
 80     }
 81 }
 82 int main() {
 83     int i,j;
 84     scanf("%d%d",&n,&m);
 85     scanf("%d",&l);
 86     for(i=1; i<=l; i++) {
 87         int x,y;
 88         scanf("%d%d",&x,&y);
 89         map[p(x,y)]=1;
 90     }
 91     for(i=1; i<=n; i++) {
 92         int now=0;
 93         for(j=1; j<=m; j++) {
 94             int t=p(i,j);
 95             if(map[t])now=j;
 96             else c[t]=j-now-1;
 97         }
 98         now=m+1;
 99         for(j=m; j>=1; j--) {
100             int t=p(i,j);
101             if(map[t])now=j;
102             else c[t]=min(c[t],now-j-1);
103         }
104     }//预处理出c[]
105     for(i=n; i>=1; i--) {
106         for(j=1; j<=m; j++) {
107             int t=p(i,j);
108             if(map[t])down[t]=-1;
109             else if(i==n)down[t]=0;
110             else down[t]=down[p(i+1,j)]+1;
111         }
112     }//预处理出down[]
113     solve();
114     printf("%lld\n",ans);
115     return 0;
116 }

原文地址:https://www.cnblogs.com/huangdalaofighting/p/8260158.html

时间: 2024-10-22 02:49:58

[HNOI2012]双十字的相关文章

bzoj 2727: [HNOI2012]双十字

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

YCB 的暑期计划

前言 YCB现在很弱(TAT) 暑假有一个月,赶快狂补一下. 大概的计划如下: 首先前期会以数据结构为主,毕竟代码能力太弱,涉及内容:线段树分治.二进制分组.KD-Tree. 等数据结构做到没有智商的时候加入一波数论,内容为 杜教筛.min_25筛. 然后中途小清新一下,做一些 组合博弈与构造题. 接着继续练代码能力,顺便学一些神奇的暴力:启发式合并.dsu on tree . 然后图论也忘的差不多了,就回过头去学点新东西,大概会有spfa判负环.0/1分数规划.差分约束. 估计这个时候也没有什

BZOJ 2730:[HNOI2012]矿场搭建(割点+连通块)

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

HNOI2012永无乡

[HNOI2012]永无乡 Description 永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己的独一无二的重要度,按照重要度可 以将这 n 座岛排名,名次用 1 到 n 来表示.某些岛之间由巨大的桥连接,通过桥可以从一个岛 到达另一个岛.如果从岛 a 出发经过若干座(含 0 座)桥可以到达岛 b,则称岛 a 和岛 b 是连 通的.现在有两种操作:B x y 表示在岛 x 与岛 y 之间修建一座新桥.Q x k 表示询问当前与岛 x连通的所有岛中第 k 重要的是哪座岛,即所有与岛 x

[HNOI2012]矿场搭建 题解

[HNOI2012]矿场搭建 时间限制: 1 Sec  内存限制: 128 MB 题目描述 煤矿工地可以看成是由隧道连接挖煤点组成的无向图.为安全起见,希望在工地发生事故时所有挖煤点的工人都能有一条出路逃到救援出口处.于是矿主决定在某些挖煤点设立救援出口,使得无论哪一个挖煤点坍塌之后,其他挖煤点的工人都有一条道路通向救援出口.请写一个程序,用来计算至少需要设置几个救援出口,以及不同最少救援出口的设置方案总数. 输入 输入文件有若干组数据,每组数据的第一行是一个正整数 N(N≤500),表示工地的

bzoj2734【HNOI2012】集合选数

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

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

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

洛谷——P3225 [HNOI2012]矿场搭建

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

【BZOJ2729】[HNOI2012]排队 组合数

[BZOJ2729][HNOI2012]排队 Description 某中学有 n 名男同学,m 名女同学和两名老师要排队参加体检.他们排成一条直线,并且任意两名女同学不能相邻,两名老师也不能相邻,那么一共有多少种排法呢?(注意:任意两个人都是不同的) Input 只有一行且为用空格隔开的两个非负整数 n 和 m,其含义如上所述. 对于 30%的数据 n≤100,m≤100 对于 100%的数据 n≤2000,m≤2000 Output 输出文件 output.txt 仅包含一个非负整数,表示不