迷惑的激光炸弹

本文讲述了作者解决激光炸弹问题的过程,最初尝试枚举和宽搜,后来发现可以使用动态规划和容斥原理优化算法。通过前缀和记录矩阵分值,状态转移方程简化问题,最终实现解决方案。
摘要由CSDN通过智能技术生成

激光炸弹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;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值