题目描述

小W是一片新造公墓的管理人。公墓可以看成一块N×M的矩形,矩形的每个格点,要么种着一棵常青树,要么是一块还没有归属的墓地。

当地的居民都是非常虔诚的基督徒,他们愿意提前为自己找一块合适墓地。为了体现自己对主的真诚,他们希望自己的墓地拥有着较高的虔诚度。

一块墓地的虔诚度是指以这块墓地为中心的十字架的数目。一个十字架可以看成中间是墓地,墓地的正上、正下、正左、正右都有恰好k棵常青树。

小W希望知道他所管理的这片公墓中所有墓地的虔诚度总和是多少。

输入输出格式

输入格式:

输入文件religious.in的第一行包含两个用空格分隔的正整数N和M,表示公墓的宽和长,因此这个矩形公墓共有(N+1) ×(M+1)个格点,左下角的坐标为(0, 0),右上角的坐标为(N, M)。

第二行包含一个正整数W,表示公墓中常青树的个数。

第三行起共W行,每行包含两个用空格分隔的非负整数xi和yi,表示一棵常青树的坐标。输入保证没有两棵常青树拥有相同的坐标。

最后一行包含一个正整数k,意义如题目所示。

输出格式:

输出文件religious.out仅包含一个非负整数,表示这片公墓中所有墓地的虔诚度总和。为了方便起见,答案对2,147,483,648取模。

输入输出样例

输入样例#1:

5 6
13
0 2
0 3
1 2
1 3
2 0
2 1
2 4
2 5
2 6
3 2
3 3
4 3
5 2
2
输出样例#1:

6

题目描述摘自洛谷。


非常幸运过了这道生涯题,从上午10:30到晚上20:15。事实证明,人应该对自己有点信心,应该有点梦想,万一就见鬼了呢。
题目大意,给出一个N*M的方格,在每个点上分布着常青树/墓地,我们需要统计每个墓地的虔诚度,相加求得答案。
题中描述的是“恰好”有k颗树,看一眼样例,发现事情没那么简单,再结合答案,很容易分析出我们应该求的是每个点上下左右对应的组合数相乘。
由乘法原理可知,我们要求的是从上下左右的常青树中分别选出k颗,对应的方案数乘乘积。
(L,R,U,D分别代表Left,Right,Up,Down)
观察数据范围,k <= 10,由杨辉三角行列数对应组合数,我们可以通过杨辉三角递推式,预处理出所有的组合数。
然而再看一眼数据范围,N,M的范围过大,不用说线性递推,数组都开不下。然而常青树W的个数却只有100,000个,考虑离散化。
在离散化的过程中,顺手处理出每行每列的常青树个数。(后面在解释)。
至此,预处理的所有工作已经完成,放个代码。(码风丑,请见谅)
离散化:(1倍存原数,2倍存x,3倍存y)
inline void discretizition()
{
scanf("%d%d%d",&n,&m,&w);
for(int i = ;i<=w;i++)
{
int x,y;
scanf("%d%d",&x,&y);
nd[i+w].temp = x;
nd[i+w].kind = ;
nd[i+w].num = i;
nd[i+*w].temp = y;
nd[i+*w].kind = ;
nd[i+*w].num = i;
}
int cnt = ;
sort(nd+w+,nd+*w+,cmp1);
for(int i = w+;i<=*w;i++)
{
if(i==w+)
{
int num = nd[i].num;
if(nd[i].kind==)
{
nd[num].x = cnt;
lie[cnt]++; //列数
}else
{
nd[num].y = cnt;
hang[cnt]++; //行数
}
continue;
}
if(nd[i].temp!=nd[i-].temp)cnt++;
int num = nd[i].num;
if(nd[i].kind==)
{
nd[num].x = cnt;
lie[cnt]++; //列数
}else
{
nd[num].y = cnt;
hang[cnt]++; //行数
}
}
}
递推杨辉三角,预处理组合数:
inline void getC()
{
scanf("%d",&k);
C[][] = ;
for(int i = ;i<=W;i++)
{
for(int j = ;j<=;j++)
{
if(j==){C[i][j] = ;continue;}
C[i][j] = (C[i-][j-]+C[i-][j])&mod;
}
}
}

明确一点小技巧,取模2147483648和按位与2147483647效果是一样的。(1<<31==2147483648)
如果还不明白,请实践几组数,或者仔细想想。毕竟脑袋是用来想的,不是用来装饰的。
当我们完成预处理步骤时,我们已经将整个方格缩小到了100,000×100,000的大小了,接下来便是如何计算虔诚度。
暴力枚举墓地?必然超时(但是洛谷数据貌似可以通过一些玄学优化方法过掉,这里暂不提及)。我们需要找到一种更为快速的方法。
When you have eliminated the impossibles,whatever remains,however improbable,must be the truth.——福尔摩斯
我们希望能出现O(wlog)的方法,或者O(w)的方法。由于常青树一共只有100,000颗,既然我们不能枚举墓地,不妨枚举常青树。
而枚举常青树又不能随意枚举,不然在累积虔诚度的时候又会变成w方的情况,我们需要一些数据结构来维护虔诚度,如何维护?维护什么?这就是本题的关键。
经过我们的一番分析(标签)我们知道本题使用的数据结构是树状数组。
不要问我怎么分析出来的树状数组,我就是看到它是树状数组才决定的做这道题。
我们在枚举的过程中,想要求的是每个空地上下左右组合数的乘积,可以发现在同一行(列)(以下统一为行)两颗常青树之间的空地,它们左面和右面常青树的组合数是相同的。
这也就给了我们灵感,我们可以通过有顺序的枚举,确定一维,转移一维。这时候需要把所有点按照行优先,列其次的顺序排序。这样枚举的时候就是有序的了。
再瞅一眼图,找找感觉。

假定现在我们枚举到了蓝色点,我们在枚举的时候可以通过前后点是否在同一行,定义变量来记录这一行已经枚举了多少颗常青树,再通过这一行常青树的总数,
减去左方的常青树,即可得到右方的常青树,左右的组合数乘积即可轻松得到。这也就是之前要维护每一行常青树个数的原因。
那么如何快速计算同一行两颗常青树之间墓地的虔诚度之和?
上面的图没有同一行连续的墓地,我们把它转90°(awa)。
好,现在在下数起第三行,出现了两个蓝色点,它们左右的常青树数量相同。
不妨设L为两个蓝色点左边组合数,R为两个蓝色点右边组合数,U1为1号蓝色点上方组合数,D1为其下方组合数,U2同理为2号蓝色点上方组合数,D2为蓝色点下方组合数。
我们要求的这两个点的虔诚度为:
Ans = L×R×U1×D1 + L×R×U2×D2
= L×R×(U1×D1 + U2×D2)
看到这里,您应该已经猜到如何快速计算墓地虔诚度的和了。
树状数组里面存的不是常青树个数,我们将每一列的编号数作为树状数组的下标,将这一列在本时刻所对应的上下组合数的乘积存入树状数组中,再通过前缀和相减,
算出两颗常青树之间的墓地的上下组合数乘积之和。
对应上方例子,即树状数组四号点存储的是U1×D1,五号点存储的是U2×D2,所求虔诚度:
Ans = L×R×(getsum(5)-getsum(4-1));
在算完两棵树之间的墓地后,需要更新对应列的树状数组中的值,更新的值即为重新算出的组合数与之前的组合数之差,对应例子中,4号点应该更新的值即为:
Update(4,(U1-1)×(D1+1)-U1×D1);
5号点同理。
这里的D1/U1可以通过枚举顺序记录,作者通过自下而上枚举,所以通过数组记录了D1,之前又记录了每一列的常青树个数,相减即可得到U1。
逐渐枚举,更新答案即可。
换行或两颗常青树相邻的情况读者自行思考即可,除此以外最好将行列数坐标+1,因原题中原点为(0,0),树状数组更新不便。
Talk is cheap,show me the code。
#include<cstdio>
#include<algorithm>
#define W 100005
#define mod 2147483647
#define ll long long
using namespace std;
int n,m,w,lie[*W+],hang[*W+],k,tree[*W],down[*W];
ll C[*W+][];
ll ans;
struct node
{
int x,y,num,kind,temp;
}nd[W*+];
inline bool cmp1(node a,node b)
{
return a.temp<b.temp;
}
inline bool cmp(node a,node b)
{
if(a.y!=b.y)
{
return a.y<b.y;
}else
{
return a.x<b.x;
}
}
inline void discretizition()
{
scanf("%d%d%d",&n,&m,&w);
for(int i = ;i<=w;i++)
{
int x,y;
scanf("%d%d",&x,&y);
nd[i+w].temp = x;
nd[i+w].kind = ;
nd[i+w].num = i;
nd[i+*w].temp = y;
nd[i+*w].kind = ;
nd[i+*w].num = i;
}
int cnt = ;
sort(nd+w+,nd+*w+,cmp1);
for(int i = w+;i<=*w;i++)
{
if(i==w+)
{
int num = nd[i].num;
if(nd[i].kind==)
{
nd[num].x = cnt;
lie[cnt]++;
}else
{
nd[num].y = cnt;
hang[cnt]++;
}
continue;
}
if(nd[i].temp!=nd[i-].temp)cnt++;
int num = nd[i].num;
if(nd[i].kind==)
{
nd[num].x = cnt;
lie[cnt]++;
}else
{
nd[num].y = cnt;
hang[cnt]++;
}
}
}
inline void getC()
{
scanf("%d",&k);
C[][] = ;
for(int i = ;i<=W;i++)
{
for(int j = ;j<=;j++)
{
if(j==)
{
C[i][j] = ;
continue;
}
C[i][j] = (C[i-][j-]+C[i-][j])&mod;
}
}
}
inline int lowbit(int x)
{
return x&(-x);
}
inline void update(int pos,int x)
{
for(int i = pos;i<=w;i+=lowbit(i))
{
tree[i]+=x;
}
}
inline ll getsum(int pos)
{
ll tmp = ;
for(int i = pos;i;i-=lowbit(i))
{
tmp=(tmp+tree[i])&mod;
}
return tmp&mod;
}
inline void solve()
{
sort(nd+,nd++w,cmp);
int tot = ;
for(int i = ;i<=w;i++)
{
if(i==)
{
tot++;
down[nd[i].x]++;
update(nd[i].x,(C[down[nd[i].x]][k]*C[lie[nd[i].x]-down[nd[i].x]][k])&mod);
continue;
}
if(nd[i].y==nd[i-].y)
{
tot++;
if((tot->=k)&&(hang[nd[i].y]-tot+>=k))
{
ll tp = getsum(nd[i].x-)-getsum(nd[i-].x);
tp = tp*C[hang[nd[i].y]-tot+][k]*C[tot-][k]&mod;
ans = (ans+tp)&mod;
}
down[nd[i].x]++;
update(nd[i].x,(C[down[nd[i].x]][k]*C[lie[nd[i].x]-down[nd[i].x]][k]&mod)-(C[down[nd[i].x]-][k]*C[lie[nd[i].x]-down[nd[i].x]+][k]&mod));
}else
{
down[nd[i].x]++;
update(nd[i].x,(C[down[nd[i].x]][k]*C[lie[nd[i].x]-down[nd[i].x]][k]&mod)-(C[down[nd[i].x]-][k]*C[lie[nd[i].x]-down[nd[i].x]+][k]&mod));
tot = ;
}
}
printf("%lld\n",ans&mod);
}
int main()
{
discretizition();
getC();
solve();
return ;
}

 

 

 
 

虔诚的墓主人(BZOJ1227)(洛谷P2154)解题报告的更多相关文章

  1. 洛谷 P1462 解题报告

    P1462 通往奥格瑞玛的道路 题目背景 在艾泽拉斯大陆上有一位名叫歪嘴哦的神奇术士,他是部落的中坚力量 有一天他醒来后发现自己居然到了联盟的主城暴风城 在被众多联盟的士兵攻击后,他决定逃回自己的家乡 ...

  2. 洛谷 P1879 解题报告

    P1879 [USACO06NOV]玉米田Corn Fields 题目描述 农场主\(John\)新买了一块长方形的新牧场,这块牧场被划分成\(M\)行\(N\)列\((1 ≤ M ≤ 12; 1 ≤ ...

  3. 洛谷 P1069 解题报告

    P1069 细胞分裂 题目描述 \(Hanks\)博士是\(BT\) (\(Bio-Tech\),生物技术) 领域的知名专家.现在,他正在为一个细胞实验做准备工作:培养细胞样本. \(Hanks\) ...

  4. 洛谷 P2491 解题报告

    P2491 消防 题目描述 某个国家有n个城市,这n个城市中任意两个都连通且有唯一一条路径,每条连通两个城市的道路的长度为zi(zi<=1000). 这个国家的人对火焰有超越宇宙的热情,所以这个 ...

  5. 洛谷 P2587 解题报告

    P2587 [ZJOI2008]泡泡堂 题目描述 第XXXX届NOI期间,为了加强各省选手之间的交流,组委会决定组织一场省际电子竞技大赛,每一个省的代表队由n名选手组成,比赛的项目是老少咸宜的网络游戏 ...

  6. 洛谷 P1054 解题报告

    P1054 等价表达式 题目描述 明明进了中学之后,学到了代数表达式.有一天,他碰到一个很麻烦的选择题.这个题目的题干中首先给出了一个代数表达式,然后列出了若干选项,每个选项也是一个代数表达式,题目的 ...

  7. 洛谷 P1053 解题报告

    P1053 篝火晚会 题目描述 佳佳刚进高中,在军训的时候,由于佳佳吃苦耐劳,很快得到了教官的赏识,成为了"小教官".在军训结束的那天晚上,佳佳被命令组织同学们进行篝火晚会.一共有 ...

  8. 洛谷 P1057 解题报告

    P1057 传球游戏 题目描述 上体育课的时候,小蛮的老师经常带着同学们一起做游戏.这次,老师带着同学们一起做传球游戏. 游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹 ...

  9. 洛谷 P1430 解题报告

    P1430 序列取数 题目描述 给定一个长为\(n\)的整数序列\((n<=1000)\),由\(A\)和\(B\)轮流取数(\(A\)先取).每个人可从序列的左端或右端取若干个数(至少一个), ...

  10. 洛谷 P1613 解题报告

    P1613 跑路 题目描述 小\(A\)的工作不仅繁琐,更有苛刻的规定,要求小\(A\)每天早上在\(6:00\)之前到达公司,否则这个月工资清零.可是小\(A\)偏偏又有赖床的坏毛病.于是为了保住自 ...

随机推荐

  1. - XML 解析 总结 DOM SAX PULL MD

    目录 目录 XML 解析 总结 DOM SAX PULL MD 几种解析方式简介 要解析的内容 DOM 解析 代码 输出 SAX 解析 代码 输出 JDOM 解析 代码 输出 DOM4J 解析 代码 ...

  2. 配置 Nginx 反向代理 WebSocket

    用Nginx给网站做反向代理和负载均衡是广泛使用的一种Web服务器部署技术.不仅能够保证后端服务器的隐蔽性,还可以提高网站部署灵活性. 今天我们来讲一下,如何用Nginx给WebSocket服务器实现 ...

  3. php 无限极分类,根据父级 找子级

    /** * @param $data 数据(二维数组格式) * @param $adminId 管理员ID * @param $pid 权限的上级ID * @param int $level 水平变量 ...

  4. c# 读取文件目录下的信息

    private void button1_Click(object sender, System.EventArgs e) { //浏览文件夹 this.folderBrowserDialog1.Sh ...

  5. C#中真正的属性

    引言 我们以前课堂上说的类当中的“属性”,其实官方叫法是“字段”或者“域”域(Field).正常使用,把它们当属性理解更加方便快捷,也没有什么问题. 如果要在微软的mvc中充分利用类带来的便利,就有必 ...

  6. 学习笔记之自然语言处理(Natural Language Processing)

    自然语言处理 - 维基百科,自由的百科全书 https://zh.wikipedia.org/wiki/%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E5%A4%84%E7 ...

  7. ssh工具推荐

    FinalShell 看图就知道功能多强大了, 官网地址 http://www.hostbuf.com

  8. socket系统化入门

    1.简单socket完成消息发送与接收 服务端: package com.wfd360.com.socket; import java.io.*; import java.net.ServerSock ...

  9. Java 虚拟机 - GC 垃圾回收机制分析

    Java 垃圾回收(Garbage Collection,GC) Java支持内存动态分配.垃圾自动回收,而 C++ 不支持.我想这可能也是 为什么 Java 脱胎于 C++ 的一个原因吧. GC 的 ...

  10. Prometheus(四):Prometheus+Alertmanager 配置邮件报警

    此处默认已安装Prometheus服务,服务地址:192.168.56.200  一.安装Alertmanager 此处采用源码编译的方式安装.首先下载alertmanager的软件包,下载地址:ht ...