暑期集训(1) 枚举,模拟;前缀和与差分

14 篇文章 0 订阅
12 篇文章 0 订阅

一.枚举

枚举在于尽可能的拿到可以拿到的数据范围内的分数
对于已给出并不是较大的范围内,合理计算时间复杂度
在于保留列举所有可能的情况

例题
=
这道题题目分析完全后,就可以从大到小依次枚举
但远没有这么简单
见到这个数,我们可以将他分解成2进制表示的数
统计其中2进制中的“1”出现的频率
易得:
“1”的个数需小于枚举的数
同时,有一个易错的point
特判统计的次数“ans”<=n即可解决

核心代码:

转二进制
int de1(int k)
{
	int temp=0;
	while(k)
	{
		if(k%2==1) temp++;
		k/=2;
	}
	return temp;
}
cin>>n>>p;
	for(int i=n;i>=1;i--)
	{
		ans++,n-=p;
		if((de1(n)<=ans)&&(ans<=n))
		{
			cout<<ans;
			return 0;
		}
	}
	cout<<-1;

二.模拟

模拟在于从图中易得的信息开数组,结构体等方式,顺着题目的意思求解即可
没有什么高难度的技巧,就在于一些少分的细节与一般普通的思路

例题
在这里插入图片描述
一道比较磨人的题目
其实看到题可以得到比较准确的思路
开一个时间数组(注意c++中数组变量名不能为time,不然莫名其妙的报错)
统计灯泡亮的时间
tim【i】++即可
但加的同时要注意分类讨论
当刚开始灯泡亮或者不亮
我习惯开一个结构体
其中存储着时间与状态
但在这道题题目里面可能过于繁琐

先上核心代码:

void work(bool flag,int x,int y)
{
	if(flag==true)
	{
		for(j=1;j<=y-1;j++)
		tim[j]++;
		
		for(j=x+y;j<=2*x+y-1;j++)
		tim[j]++;
		
		for(j=3*x+y;j<=maxn;j+=2*x)
		 for(k=j;k<=j+x-1;k++)
		 {
		 	tim[k]++;
		 }
	}
	
	if(flag==false)
	{
//		cout<<"hyx"<<endl;
		for(j=y;j<=x+y-1;j++)
		tim[j]++;
		
		for(j=2*x+y;j<=maxn;j+=2*x)
		 for(k=j;k<=j+x-1;k++)
		 {
		 	tim[k]++;
		 }
	}
}

bool mycmp(int p,int q)
{
	return p>q;
}

注意这里摘录的是在时间轴上判断灯光的代码
要注意的是:特判,题目中给定亮的从0开始,一直到bi(就是y),还要再判断从y到x+y 两个point
而不亮的只需要如上判断一个即可
还有浪费时间的点是输入的是字符串,我们判断“真假”需要带上 “ ”
从i=1,开始,判断字符串用i-1


前缀和的主要作用体现在与二分连用,同时对已超时的数据进行优化
而表示差的值即为(a[i]-a[j-1])
我们在学习差分后可以用一些常见的数组来表示

1).原数组 a[i]
2) .前缀和 b[i]
3).差分 s[i]
b[][]数组是a[][]数组的前缀和数组,那么a[][]是b[][]的差分数组

同时我们可以其中的一个从而推出其他的
众所周知

原数组=>前缀和
我们设一个数组为s[1……n],然后对于我们现在已知一个a数组,那么s数组的定义就是

s[i]=a[1]+a[2]+a[3]+……+a[i-1]+a[i]

根据他的性质,我们可以发现
s[i]=s[i-1]+a[i];通过这个式子我们可以O(n)的时间复杂度内求出s数组,那么这个数组就被称为原数组的前缀和数组。

原数组=>差分

对于一个给定的数列A,他的差分数列B定义为:

B[1]= A[1] ,B[i]=A[i]-A[i-1](2≤i≤n)

容易发现,“前缀和”与“差分”是一对互逆运算,差分序列B的前缀和序列和序列就是原序列A,前缀和序列S的差分序列也是原序列A
把序列A的区间[L,R]加d(即把A[L],A[L+1]……A[R]都加上d),其差分序列B的变化为B[L]加d,B[R+1]减d,其他位置不变。这有助于我们在许多题目中,把原序列上的“区间操作”转化为差分序列上的“单点操作”转化为差分序列上的“单点操作”进行计算,降低求解难度。

二维讨论
当我们解决了一维数组的区间求和的问题,那么我们来思考一下那对于二维数组所存的矩阵类型的元素呢?
抛出一个问题
输入一个n行m列的整数矩阵,再输入q个询问,每个询问包含四个整数x1, y1, x2, y2,表示一个子矩阵的左上角坐标和右下角坐标
对于每个询问输出子矩阵中所有数的和。

我们根据之前学过的东西很容易想到,如果再构建一个二维前缀和数组s[i][j],来表示a[1][1]+a[1][2]+……+a[1][j]+a[2][1]+a[2][2]+……+a[2][j]+……a[i][j]
那么会对于我们这道题目有很好的帮助。
一维前缀和我们只需要通过s[i]=s[i-1]+a[i]即可求出,那么二维前缀和又如何求出呢?

二维前缀和的预处理
先给公式:s[i][j]=s[i-1][j]+s[i][j-1]-s[i-1][j-1]+a[i][j];
那么我们是如何得到这个公式的呢?
我们通常二维循环都是这样的式子
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
……(我们需要进行的操作)
}
而对于这样的循环来说,我们可以知道在我们求s[i][j]时,所有的s[x][y]都已经求出了当x<i且y<j时,那么我们利用已经求出的s数组来计算所需。

二维矩阵元素和的计算
那对于我们之前的问题如何来解决呢?
其实类似于我们之前预处理s数组一样
对于已经给定的(x1,y1),(x2,y2)作为左上端点和右下端点的矩阵,我们要求该矩阵的元素之和,我们只需要求
s[x2][y2]-s[x1-1][y2]-s[x2][y1-1]+s[x1-1][y1-1]便是我们所求的答案。

便于理解,从大佬那里扒图看一下

前缀和

在这里插入图片描述

一维前缀和

在这里插入图片描述

一维前缀和公式

s[i]=s[i-1]+a[i]

二维前缀和

在这里插入图片描述

在这里插入图片描述

二维前缀和预处理公式

s[i] [j] = s[i-1][j] + s[i][j-1 ] + a[i] [j] - s[i-1][ j-1]

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二维前缀和元素和公式

(x1, y1)为左上角,(x2, y2)为右下角的子矩阵的和为:

s[x2, y2] - s[x1 - 1, y2] - s[x2, y1 - 1] + s[x1 - 1, y1 - 1]

差分

在这里插入图片描述

一维差分

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一维差分公式

因此我们得出一维差分结论:给a数组中的[ l, r]区间中的每一个数都加上c,只需对差分数组b做 b[l] + = c, b[r+1] - = c。时间复杂度为O(1), 大大提高了效率。

二维差分

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二维差分操作公式

b[i][j]=a[i][j]−a[i−1][j]−a[i][j−1]+a[i−1][j−1]

二维差分构造变化数值公式


void insert(int x1,int y1,int x2,int y2,int c)
{     
//对b数组执行插入操作,等价于对a数组中的(x1,y1)到(x2,y2)之间的元素都加上了c
    b[x1][y1]+=c;
    b[x2+1][y1]-=c;
    b[x1][y2+1]-=c;
    b[x2+1][y2+1]+=c;
}

题目练习

AcWing 798. 差分矩阵 输入一个n行m列的整数矩阵,再输入q个操作,每个操作包含五个整数x1, y1, x2, y2,
c,其中(x1, y1)和(x2, y2)表示一个子矩阵的左上角坐标和右下角坐标。 每个操作都要将选中的子矩阵中的每个元素的值加上c。
请你将进行完所有操作后的矩阵输出。 输入格式 第一行包含整数n,m,q。 接下来n行,每行包含m个整数,表示整数矩阵。
接下来q行,每行包含5个整数x1, y1, x2, y2, c,表示一个操作。 输出格式 共 n 行,每行 m
个整数,表示所有操作进行完毕后的最终矩阵。 数据范围 1≤n,m≤1000, 1≤q≤100000, 1≤x1≤x2≤n,
1≤y1≤y2≤m, −1000≤c≤1000, −1000≤矩阵内元素的值≤1000 输入样例: 3 4 3 1 2 2 1 3 2 2
1 1 1 1 1 1 1 2 2 1 1 3 2 3 2 3 1 3 4 1 输出样例: 2 3 4 1 4 3 4 1 2 2 2 2

AC代码:

include<iostream>
#include<cstdio>
using namespace std;
const int N=1e3+10;
int a[N][N],b[N][N];
void insert(int x1,int y1,int x2,int y2,int c)
{
    b[x1][y1]+=c;
    b[x2+1][y1]-=c;
    b[x1][y2+1]-=c;
    b[x2+1][y2+1]+=c;
}
int main()
{
  int n,m,q;
  cin>>n>>m>>q;  
  for(int i=1;i<=n;i++)
   for(int j=1;j<=m;j++)
    cin>>a[i][j];
  for(int i=1;i<=n;i++)
  {
      for(int j=1;j<=m;j++)
      {
          insert(i,j,i,j,a[i][j]);    //构建差分数组
      }
  }
  while(q--)
  {
      int x1,y1,x2,y2,c;
      cin>>x1>>y1>>x2>>y2>>c;
      insert(x1,y1,x2,y2,c);
  }
  for(int i=1;i<=n;i++)
  {
      for(int j=1;j<=m;j++)
      {
          b[i][j]+=b[i-1][j]+b[i][j-1]-b[i-1][j-1];
      }
  }
  for(int i=1;i<=n;i++)
  {
      for(int j=1;j<=m;j++)
      {
          printf("%d ",b[i][j]);
      }
      printf("\n");
  }
  return 0;
}

————————————————

总结

其实无论是前缀和还是差分的二维形式不在于死公式,而是根据excel或者手推图形,数形结合求解其中的数值
而搞清数组a,b,s等变化后的关系,空间换时间,也是重中之重

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值