激光炸弹laserbomb
—— 可爱的数学之容斥原理
话不多说先看题
【问题描述】
一种新型的激光炸弹,可以摧毁一个边长为R的正方形内的所有的目标。现在地图上有n(N<=10000)个目标,用整数Xi,Yi(其值在[0,5000])表示目标在地图上的位置,每个目标都有一个价值。激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆破范围,即那个边长为R的正方形的边必须和x,y轴平行。若目标位于爆破正方形的边上,该目标将不会被摧毁。
【输入格式】
输入文件的第一行为正整数n和正整数R,接下来的n行每行有3个正整数,分别表示xi,yi,vi。
【输出格式】
输出文件仅有一个正整数,表示一颗炸弹最多能炸掉地图上总价值为多少的目标(结果不会超过32767)。
初步审完题,首先想起了宽搜(随即在脑海里义正言辞地否决了自己╭(╯^╰)╮),然后想到了枚举(说心里话感觉和宽搜效率大同小异),抱着能骗几分是几分的不思进取之心态开始码代码。
先信心满满准备去搞定了输入输出
for(int i=1;i<=n;i++)
{
x=Read();
y=Read();
v1=Read();
map[x][y]=v1;
}
(Read是快读函数)
输入输出刚新鲜出炉正准备搞核心代码时,忽然发现根本没有矩阵大小的数据Σ(⊙▽⊙”
为自己根本没仔细看题忏悔OTZ
所幸仔细看完题后随即想到了如何记录矩阵大小(^o ^)/~
用endx和endy记录了矩阵的大小(其初始值为零)
for(int i=1;i<=n;i++)
{
x=Read();
y=Read();
v1=Read();
map[x][y]=v1;
if(x>endx)endx=x;
if(y>endy)endy=y;
}
然后就没有然后了…
完全想不出怎么枚举好嘛(ノ`Д)ノ啊啊啊
难不成在大矩阵里不断圈边长为r的正方形出来?“若目标位于爆破正方形的边上,该目标将不会被摧毁。”又是什么玩意ε=ε=ε=(#>д<)ノ
脑子里咆哮一通后忽然发现歪打正着~刚才的废话思想里好像有思路╰(*°▽° *)╯
“在大矩阵里不断圈边长为r的正方形出来”
于是当机立断搞了两组for循环开始瞎搞(至今未明白这种“没准会死猫撞上瞎耗子的”诡异自信从何而来)
果然瞎搞并没有实现,于是迫于无奈终于开始画图分析(一只懒不拉几的咸鱼的本性呐…)
把人家的的坐标轴改成了表格开始画图,于是仍旧对“若目标位于爆破正方形的边上,该目标将不会被摧毁。”一头雾水 。
然后稀里糊涂地按照自己的理解开始手推(后来证明是错的)
但是当时恍然大悟想明白了如何一个一个正方形的枚举并记录其值
迅速地又加了一重循环,确定了核心代码,
rmap[i1][j1]+=map[i][j];
然后在三重循环(就是三个for)里瞎捣鼓(货真价实的瞎捣鼓,包括但不限于把手头的数据往里面加加减减地乱塞)
依稀记得当时灵机一动又加了一重循环,但是忘了是什么了
接着出乎意料ヽ( ̄▽ ̄ )ノ
代码就出来了!就出来了!样例过了!自己搞的数据也过了!!这是什么神鬼莫测的运气!
当场兴奋成一只窜天猴好嘛(^ o^)/~
然后冷静了一下,嗯…这好像…是一个…O(n^4)算法…
陷入了沉默呃呃呃(⊙﹏⊙)…
但作为一只胸无大志志向远大的咸鱼,怀着对骗几分就好了的期待决定就这样吧哼唧哼唧 (* ̄0 ̄)′
以下是代码…
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<iostream>
using namespace std;
const int maxn=10001;
long long n,r,endx,endy;
int ans,map[10001][10001],rmap[maxn][maxn],x,y,v1;
inline long long int Read();
int main()
{
n=Read();
r=Read();
for(int i=1;i<=n;i++)
{
x=Read();
y=Read();
v1=Read();
map[x][y]=v1;
if(x>endx)endx=x;
if(y>endy)endy=y;
}
endx++;
endy++;
for(int i1=0;i1<=endx-r;i1++)
for(int j1=0;j1<=endy-r;j1++)
for(int i=0+i1;i<r+i1;i++)
{
for(int j=0+j1;j<r+j1;j++)
{
rmap[i1][j1]+=map[i][j];
}
ans=max(ans,rmap[i1][j1]);
}
printf("%d",ans);
return 0;
}
inline long long int Read()
{
long long int p = 0;
bool isNeg = false;
char c = getchar();
while (!isdigit(c) && c != '-') c = getchar();
if (c == '-') {isNeg = true; c = getchar();}
while (isdigit(c)) {p = p * 10 + c - '0'; c = getchar();}
return isNeg ? -p : p;
}
接着怀着莫名的自信暗搓搓期待了一下评测结果
然后全错(⊙﹏⊙),说“内存超出限制”
在老师的提醒下吧int改称了short,然后十组数据过了两个,
剩下的全部超时…
果然菜鸡要想投机取巧只会遭雷劈o(╥﹏╥)o,连枚举都干不了还不如一条咸鱼┭┮﹏┭┮
于是颓废地等待老师讲解
然后就发现这道题怎么简单啊啊w(゚Д゚)w
枚举个寂寞枚举w(゚Д゚)w,动规它不香吗,最基础的容斥原理啊啊
我怀疑我傻…
首先用一个前缀和嘛,从左上角开始,从左上角延伸出的所有矩阵分值(包括大矩阵自己)挨个记录
这里用一下容斥就好了
for(i=1;i<=endx;i++)
for(j=1;j<=endy;j++)
f[i][j]=f[i-1][j]+f[i][j-1]-f[i-1][j-1]+a[i][j];
f嘛,就是从上往下从左往右几列几行的矩阵分值啊,状态转移方程不要太好推啊啊,上面的和左边的加起来,减去重叠的,加上新增的(就右下角那一个)就好了啊
再然后挨个推边长为r的小矩阵嘛
for(int i=r;i<=endx;i++)
{
for(int j=r;j<=endy;j++)
{
ans=max(ans,f[i][j]-f[i-r][j]-f[i][j-r]+f[i-r][j-r]);
}
}
装得下r开始,一个一个枚举啊,两重循环的事啊ε=(´ο`*)))之前我搞那么麻烦绝对是脑子抽了┭┮﹏┭┮
动态转移方程同样容斥原理推啊
自己减上面的减左边的加上多减的(就重叠减那部分)不就剩下边长为r的小框框了嘛
老师还教了一个小技巧,再输入上(以上代码在此小技巧前提下)
for(i=1;i<=n;i++)
{
x=Read();
y=Read();
z=Read();
x++,y++;//小技巧,方便计算,此举原因详见题面(其坐标位置从零开始) (在后期算法中不会处理到边上的点(省事))
a[x][y]=z;
endx=max(endx,x); endy=max(endy,y);//记录大矩形大小
}
其作用都注释了嗯
最后就是完整代码
#include<cstdio>
#include<iostream>
#include<ctime>
#include<cmath>
#include<queue>
#include<algorithm>
#include<cstring>
using namespace std;
const int asd=5005;
int n,r,ans,x,y,v,endx,endy;
short a[asd][asd];
int f[asd][asd];
inline long long Read();
int main()
{
freopen("laserbomb.in","r",stdin);
freopen("laserbomb.out","w",stdout);
n=Read();
r=Read();
for(int i=1;i<=n;i++)
{
x=Read();
y=Read();
v=Read();
x++;
y++;
a[x][y]=v;
endx=max(endx,x);
endy=max(endy,y);
}
for(int i=1;i<=endx;i++)
{
for(int j=1;j<=endy;j++)
{
f[i][j]=f[i-1][j]+f[i][j-1]-f[i-1][j-1]+a[i][j];
}
}
for(int i=r;i<=endx;i++)
{
for(int j=r;j<=endy;j++)
{
ans=max(ans,f[i][j]-f[i-r][j]-f[i][j-r]+f[i-r][j-r]);
}
}
printf("%d",ans);
return 0;
}
inline long long Read()
{
long long p=0;
bool fu=false;
char c=getchar();
while(!isdigit(c)&&c!='-')c=getchar();
if(c=='-'){fu=true;c=getchar();}
while(isdigit(c)){p=p*10+c-'0';c=getchar();}
return fu ? -p : p;
}