第1节 枚举(尺取法、前缀和、差分等)、贪心

2020牛客算法竞赛入门班

第1节 枚举(尺取法、前缀和、差分等)、贪心

1.2 糖糖别胡说,我真的不是签到题目
题目:

在这里插入图片描述

示例1

输入

1 
4 3 
0 3 
1 2 
0 3 
1 1 
1 
3 
4

输出

3
题目大意:

(题面真的垃圾,连中文语法都不会)

有n个人,每个人属于第0组或第1组,且有一个能力值b,他们站成一排,第i秒时,第i个人可以消灭所有排在他前面且能力值比他小且与它不同组的人。某人的爸爸会操作m次,比如操作c,则在第c秒结束后,b1,b2 … c都增加1,求最后有多少人存活。

思路:

模拟,技巧。

对人们分组,然后模拟一遍,不过在进行m的操作时,不是前c个人都+1,而是用一个变量记录前面都加了多少,然后让后面的数减去这个值,这样就可以保证相对大小不变了。

坑点:m次操作可能会有重复。

代码:
#include<iostream>
#include<cstring>
#include<queue>
#define int long long
using namespace std;
const int N=1e6+7;

int num[2][N];
int d[N];

priority_queue<int,vector<int>,greater<int>>p1;
priority_queue<int,vector<int>,greater<int>>p0;

signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int t;
	cin>>t;
	while(t--)
	{
		int n,m;
		int add=0;
		cin>>n>>m;
		for(int i=1;i<=n;++i)
		{
			int a,b;
			cin>>a>>b;
			num[a][i]=b;
		}
		for(int i=1;i<=m;++i)
		{
			int a;
			cin>>a;
			d[a]+=1;//坑点:有可能重复
		}
		int temp=0;
		for(int i=1;i<=n;++i)
		{
			if(num[1][i]!=0)
			{
				p1.push(num[1][i]-add);
				while((!p0.empty())&&p0.top()<num[1][i]-add)
				{
					p0.pop();
					++temp;
				}
			}
			else
			{
				p0.push(num[0][i]-add);
				while((!p1.empty())&&p1.top()<num[0][i]-add)
				{
					p1.pop();
					++temp;
				}
			}
			if(d[i])add+=d[i];
		}
		cout<<n-temp<<endl;
		while(!p0.empty())p0.pop();
		while(!p1.empty())p1.pop();
		memset(d,0,sizeof(d));
		memset(num,0,sizeof(num));
	}
	return 0;
}
1.3 奇♂妙拆分

在遥远的米♂奇♂妙♂妙♂屋里住着一群自然数,他们没事就喜欢拆♂开自己来探♂究。现在他们想知道自己最多能被拆分成多少个不同的自然数,使得这些自然数相乘的值等于被拆分的数。

输入描述:

第1行输入一个整数T,代表有T组数据。
第2-T+1行,每行输入一个整数n,代表需要被拆分的数。
数据保证:0<T≤100,0<n≤10^9。

输出描述:

输出一共T行,第i行输出一个整数,代表第i行输入的n最多可以被拆分成多少个不同的自然数。

示例1

输入

3
1
4
12

输出

1
2
3

说明

1可以被拆分为:1
4可以被拆分为:1*4(1*2*2是不允许的,因为有重复的数)
12可以被拆分为:1*2*6或1*3*4

示例2

输入

1
114514

输出

4

说明

114514可以被拆分为:1*2*31*1847
题目大意:

计算一个正整数最多可以由几个不同的正整数相乘得来。

思路:

数学,枚举。

n==1时,答案是1.

n!=1时,1*a==a,所以1肯定算一个长度,枚举1-sqrt(n)(不包含sqrt(n),因为不能有相同的数字,4不能表示成2 *2),判断是否为n的约数,是则除去,答案+1,除到最后n的值肯定>=sqrt(n),所以这个数是之前没有出现过的,所以最后答案再+1。

//一开始看错题了,还以为是求方案数,然后看到12应该也可以2 *6,3 *4,就不止3个了。

代码:
#include<iostream>
#include<algorithm>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e5+7;
const int INF=0x3f3f3f3f;

signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int t;
	cin>>t;
	while(t--)
	{
		int a;
		cin>>a;
		int ans=0;
		if(a==1)ans=1;//特判
		for(int i=1;i*i<a;++i)//不能取等
		{
			if(a%i==0)
			{
				a/=i;
				++ans;
			}
		}
		if(a>1)++ans;//最后肯定要+1的,if可以不写
		cout<<ans<<endl;
	}
	return 0;
}
1.4 数学考试

今天qwb要参加一个数学考试,这套试卷一共有n道题,每道题qwb能获得的分数为ai,qwb并不打算把这些题全做完,
他想选总共2k道题来做,并且期望他能获得的分数尽可能的大,他准备选2个不连续的长度为k的区间,
即[L,L+1,L+2,…,L+k-1],[R,R+1,R+2,…,R+k-1](R >= L+k)。

输入描述:
第一行一个整数T(T<=10),代表有T组数据
接下来一行两个整数n,k,(1<=n<=200,000),(1<=k,2k <= n)
接下来一行n个整数a1,a2,...,an,(-100,000<=ai<=100,000)
输出描述:
输出一个整数,qwb能获得的最大分数

示例1

输入

2
6 3
1 1 1 1 1 1
8 2
-1 0 2 -1 -1 2 3 -1

输出

6
7
题目大意:

选两个不相交的长度为k的区间,使得俩区间的和最大,求这个和。

思路:

前缀和,dp。

先考虑最朴素的做法,暴力枚举两个长度为k的区间并求和,取最大的和,这样的时间复杂度为O(n^3)。

用前缀和优化求和部分,优化为O(n^2)。

可是这还是不行啊,n有200000之大.

我们在枚举区间的时候,先定好左边区间的右端点,然后让右边的区间往右边移动,查找右边>=i+k部分的最大值,查完一遍之后,左边的区间向右移一步,然后右边的区间接着查找右边的最大值,可以发现,其实右边很多很小的数是没有必要再次查询的,因为它会被后面的大数更新掉,所以可以用一个类似d p的数组记录>=i部分的最大值,就可以O(1)查询右边最大值了。

然后就可以优化到O(n)了

坑点:

1.取最大值的时候初始值必须足够小,我之前取的是-INF,其实是不够的,可能出现的最小值是-n*a,数量级为2 *10^10,随意最小值要取的很小,比如-1 e 18.

2.可能是我自己写法的问题,下面有个数组写成Max[i+k],i最大可能是n,也就是说如果数组只开到2 e 5的话,这里可能会越界,但实际上题目没有卡到这里,也可能是我想多了。

代码:
#include<iostream>
#include<cstring>
#define int long long
#define endl '\n'
using namespace std;
const int N=3e5+7;
const int INF=0x3f3f3f3f;

int a[N];
int d[N];
int Max[N];

signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int t;
	cin>>t;
	while(t--)
	{
		//memset(Max,-INF,sizeof(Max));//应该无所谓
		int n,k;
		cin>>n>>k;
		for(int i=1;i<=n;++i)
		{
			cin>>a[i];
			a[i]+=a[i-1];//前缀和
		}
		int m=0;
		for(int i=k;i<=n;++i)
		{
			d[m++]=a[i]-a[i-k];//离散
		}
		Max[m-1]=d[m-1];
		for(int i=m-2;i>=0;--i)
		{
			Max[i]=max(Max[i+1],d[i]);//dp思想,表示>=i部分的最大值,后缀最大值
		}
		int temp=-1e18;//坑点
		for(int i=0;i<m;++i)
		{
			if(d[i]+Max[i+k]>temp)
			{
				temp=d[i]+Max[i+k];
			}
		}
		cout<<temp<<endl;
	}
	return 0;
}
1.5 国王的游戏

恰逢 H 国国庆,国王邀请 n 位大臣来玩一个有奖游戏。首先,他让每个大臣在左、右手上面分别写下一个整数,国王自己也在左、右手上各写一个整数。然后,让这 n 位大臣排成一排,国王站在队伍的最前面。排好队后,所有的大臣都会获得国王奖赏的若干金币,每位大臣获得的金币数分别是:排在该大臣前面的所有人的左手上的数的乘积除以他自己右手上的数,然后向下取整得到的结果。
国王不希望某一个大臣获得特别多的奖赏,所以他想请你帮他重新安排一下队伍的顺序,使得获得奖赏最多的大臣,所获奖赏尽可能的少。注意,国王的位置始终在队伍的最前面。

输入描述:
第一行包含一个整数 n ,表示大臣的人数。
第二行包含两个整数 a 和 b ,之间用一个空格隔开,分别表示国王左手和右手上的整数。
接下来 n 行,每行包含两个整数 a 和 b ,之间用一个空格隔开,分别表示每个大臣左手和右手上的整数。
输出描述:
一个整数,表示重新排列后的队伍中获奖赏最多的大臣所获得的金币数。

示例1

输入

3 
1 1 
2 3 
7 4 
4 6

输出

2

说明

按 1 、 2 、 3 这样排列队伍,获得奖赏最多的大臣所获得金币数为 2 ;
按 1 、 3 、 2 这样排列队伍,获得奖赏最多的大臣所获得金币数为 2 ;
按 2 、 1 、 3 这样排列队伍,获得奖赏最多的大臣所获得金币数为 2 ;
按 2 、 3 、 1 这样排列队伍,获得奖赏最多的大臣所获得金币数为 9 ;
按 3 、 1 、 2 这样排列队伍,获得奖赏最多的大臣所获得金币数为 2 ;
按 3 、 2 、 1 这样排列队伍,获得奖赏最多的大臣所获得金币数为 9 。
因此,奖赏最多的大臣最少获得 2 个金币,答案输出 2 。
备注:
对于 20%的数据,有 1≤ n≤ 10,0 < a,b < 8 ;
对于 40%的数据,有 1≤ n≤20,0 <a,b<8 ;
对于 60%的数据,有 1≤ n≤100 ;
对于 60%的数据,保证答案不超过 109 ;对于 100%的数据,有 1 ≤ n ≤1,000,0 < a,b < 10000 。
题目大意:

每个人获得的金币是前面所有人左手上的数相乘再除以自己右手上的数的数,重新排序这n个人,使得金币最多的人的金币尽量少。

思路:

贪心,排序,高精度。

这是一道很经典的贪心题。

假设第i个人左手Li,右手Ri,第i+1个人左手Li+1,右手Ri+1,

1.第i个人在前面,第i+1个人在后面:

那么第i个人的金币数量为Π/Ri(Π为他所有前面的人左手上数的乘积),第i+1个人的金币数量为(Π *Li)/Ri+1.

交换i和i+1两个人的位置,那么i的金币数量变为(Π *Li+1)/Ri, i+1的金币数量变为Π/Ri+1.

假设i在i+1的前面时,为最优解,此时只需保证(Π *Li+1)/Ri>=(Π *Li)/Ri+1 (因为(Π *Li+1)/Ri>Π/Ri,(Π *Li)/Ri+1>Π/Ri+1

即(Li+1) *(Ri+1)>=Li *Ri.

坑点:

1.数据范围,a最大为10000,n最大为10000,当所有的a都取最大时,这个乘积为10000^10000,所以需要一个40000位的数组存大数据,要用到高精度。

2.虽然最后的结论是要求(Li+1) *(Ri+1)>=Li *Ri,但是在sort的时候一定不要写等号,会发生段错误,具体原因可能跟sort有关,就比如sort不能只写一个return true一样。

代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e5+7;
const int INF=0x3f3f3f3f;

int na[N],nb[N];
int answer[N*2];

//我自己写的垃圾高精度,跑的贼慢
string mul(string a,string b)
{
	string ans;
	memset(na,'\0',sizeof(na));
	memset(nb,'\0',sizeof(nb));
	na[0]=nb[0]=0;
	memset(answer,'\0',sizeof(answer));
    answer[0]=0;
	int la=a.size(),lb=b.size();
	int lmax=la*lb;
	for(int i=0;i<la;++i)
	{
		na[la-i-1]=a[i]-'0';//反向
	}
	for(int i=0;i<lb;++i)
	{
		nb[lb-i-1]=b[i]-'0';//反向
	}
	for(int i=0;i<la;++i)
	{
		for(int j=0;j<lb;++j)
		{
			answer[i+j]+=na[i]*nb[j];
		}
	}
	for(int i=0;i<lmax;++i)
	{
		answer[i+1]+=answer[i]/10;
		answer[i]%=10;
	}
	while(!answer[lmax]&&lmax>0)
		--lmax;
	for(int i=lmax;i>=0;--i)
		ans+=answer[i]+'0';
	return ans;
}

string div(string a,int b)
{
	string ans;
	memset(na,'\0',sizeof(na));
	memset(nb,'\0',sizeof(nb));
	na[0]=nb[0]=0;
	int la=a.size();
	int lmax=la;
	for(int i=0;i<la;++i)
	{
		na[la-i-1]=a[i]-'0';//反向
	}
	int yushu=0;
	for(int i=lmax-1;i>=0;--i)
	{
		nb[i]=(yushu*10+na[i])/b;//答案存到nb里面
		yushu=(yushu*10+na[i])%b;
	}
	while(!nb[lmax-1]&&lmax>1)
		--lmax;
	for(int i=lmax-1;i>=0;--i)
		ans+=nb[i]+'0';
	return ans;
}

bool cmp2(string a,string b)//比较a是否大于等于b
{
	if(a[0]=='-'&&b[0]!='-')return false;
	if(a[0]!='-'&&b[0]=='-')return true;
    if(a.size()==b.size())return a>=b;
    return a.size()>b.size();
}

struct node
{
	int a,b;
}d[N];

bool cmp(node a,node b){return a.a*a.b<b.a*b.b;}
//要用到大数
signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int n;
	cin>>n;
	cin>>d[0].a>>d[0].b;
	for(int i=1;i<=n;++i)
	{
		cin>>d[i].a>>d[i].b;
	}
	sort(d+1,d+1+n,cmp);
	string ans="-20000";
	string temp=to_string(d[0].a);
	for(int i=1;i<=n;++i)
	{
		string now=div(temp,d[i].b);
		if(!cmp2(ans,now))
		{
			ans=now;
		}
		temp=mul(temp,to_string(d[i].a));
	}
	cout<<ans<<endl;
	return 0;
}
1.6 铺地毯

为了准备一个独特的颁奖典礼,组织者在会场的一片矩形区域(可看做是平面直角坐标系的第一象限)铺上一些矩形地毯。一共有n张地毯,编号从1到n。现在将这些地毯按照编号从小到大的顺序平行于坐标轴先后铺设,后铺的地毯覆盖在前面已经铺好的地毯之上。地毯铺设完成后,组织者想知道覆盖地面某个点的最上面的那张地毯的编号。注意:在矩形地毯边界和四个顶点上的点也算被地毯覆盖。

输入描述:
第一行,一个整数n,表示总共有n张地毯。
接下来的n行中,第i+1行表示编号i的地毯的信息,包含四个正整数a,b,g,k,每两个整数之间用一个空格隔开,分别表示铺设地毯的左下角的坐标(a,b)以及地毯在x轴和y轴方向的长度。
第n+2行包含两个正整数x和y,表示所求的地面的点的坐标(x,y)。
输出描述:
输出共1行,一个整数,表示所求的地毯的编号;若此处没有被地毯覆盖则输出-1。

示例1

输入

3
1 0 2 3
0 2 3 3
2 1 3 3
2 2

输出

3

说明

如下图,1号地毯用实线表示,2号地毯用虚线表示,3号用双实线表示,覆盖点(2,2)的最上面一张地毯是3号地毯。

img

备注:
对于30%的数据,有n≤2;
对于50%的数据,有0≤a,b,g,k≤100;
对于100%的数据,有0≤n≤10,000,0≤a,b,g,k≤100,000。
题目大意:

在一个平面直角坐标系中放几个可能会重叠的矩形,问某个位置上最上面的矩阵是第几个。

思路:

模拟水题。

按顺序放矩形,看该点最后一个矩形是哪个就行了。

代码:
#include<iostream>
#include<algorithm>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e4+7;
const int INF=0x3f3f3f3f;

struct node
{
	int a,b,g,k;
}d[N];

signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int n;
	cin>>n;
	for(int i=0;i<n;++i)
	{
		cin>>d[i].a>>d[i].b>>d[i].g>>d[i].k;
	}
	int x,y;
	cin>>x>>y;
	int ans=-1;
	for(int i=0;i<n;++i)
	{
		if(x>=d[i].a&&y>=d[i].b&&x<=d[i].a+d[i].g&&y<=d[i].b+d[i].k)ans=i+1;
	}
	cout<<ans<<endl;
	return 0;
}
1.7 纪念品分组

元旦快到了,校学生会让乐乐负责新年晚会的纪念品发放工作。为使得参加晚会的同学所获得 的纪念品价值相对均衡,他要把购来的纪念品根据价格进行分组,但每组最多只能包括两件纪念品, 并且每组纪念品的价格之和不能超过一个给定的整数。为了保证在尽量短的时间内发完所有纪念品,乐乐希望分组的数目最少。
你的任务是写一个程序,找出所有分组方案中分组数最少的一种,输出最少的分组数目。

输入描述:
第 1 行包括一个整数 w,为每组纪念品价格之和的上限。
第 2 行为一个整数n,表示购来的纪念品的总件数。
第 3 ~ n+2 行每行包含一个正整数 pi ( 5 ≤ pi ≤ w ) ,表示所对应纪念品的价格。
输出描述:
包含一个整数,即最少的分组数目。

示例1

输入

100
9
90
20
20
30
50
60
70
80
90

输出

6
备注:
50%的数据满足:1 ≤ n ≤ 15
100%的数据满足:1 ≤ n ≤ 30000, 80 ≤ w ≤ 200
题目大意:

取两个数为一组,和不能大于w,求最少组数。

思路:

贪心,排序。

每次取最大的与最小的之和不大于w就可以分到一组,否则就让最大的单独一组,模拟一遍,最后l==r的时候单独特判一下。

证明这个贪心的正确性:

一个非递减的序列 a b c d e (a<=b<=c<=d<=e).

1.如果a+e<=w,那么肯定也有a+d<=w

2.如果a+e<=w,但是b+e>w, 此时b+d的情况比e+d更优(更小),所以优先把大的数去掉为最优策略

代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#define int long long
#define endl '\n'
using namespace std;
const int N=3e4+7;
const int INF=0x3f3f3f3f;

int a[N];

signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int w;
	cin>>w;
	int n;
	cin>>n;
	for(int i=0;i<n;++i)
	{
		cin>>a[i];
	}
	sort(a,a+n);
	int ans=0;
	int l=0,r=n-1;
	while(l<r)
	{
		if(a[l]+a[r]<=w)
		{
			++l;
			--r;
			++ans;
		}
		else
		{
			--r;
			++ans;
		}
	}
	if(l==r)++ans;
	cout<<ans<<endl;
	return 0;
}
1.8 校门外的树

某校大门外长度为L的马路上有一排树,每两棵相邻的树之间的间隔都是1米。我们可以把马路看成一个数轴,马路的一端在数轴0的位置,另一端在L的位置;数轴上的每个整数点,即0,1,2,……,L,都种有一棵树。

由于马路上有一些区域要用来建地铁。这些区域用它们在数轴上的起始点和终止点表示。已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。现在要把这些区域中的树(包括区域端点处的两棵树)移走。你的任务是计算将这些树都移走后,马路上还有多少棵树。

输入描述:

第一行有两个整数:L(1 <= L <= 10000)和 M(1 <= M <= 100),L代表马路的长度,M代表区域的数目,L和M之间用一个空格隔开。接下来的M行每行包含两个不同的整数,用一个空格隔开,表示一个区域的起始点和终止点的坐标。

输出描述:

包括一行,这一行只包含一个整数,表示马路上剩余的树的数目。

示例1

输入

500 3
150 300
100 200
470 471

输出

298

备注:

对于20%的数据,区域之间没有重合的部分;
对于其它的数据,区域之间有重合的情况。

题目大意:

每次覆盖一个区间,求没有被覆盖的区间长度之和

思路:

模拟水题。

数据很小,直接O(n*m)模拟即可。

如果数据再大一点,就用前缀和+差分,判断最后为正数的数量。

数据再大一点就用离散化的方法。

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e4+7;
const int INF=0x3f3f3f3f;

bool a[N];

signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int L,M;
	cin>>L>>M;
	for(int i=0;i<M;++i)
	{
		int l,r;
		cin>>l>>r;
		for(int i=l;i<=r;++i)
		{
			a[i]=1;
		}
	}
	int ans=0;
	for(int i=0;i<=L;++i)
	{
		if(!a[i])++ans;
	}
	cout<<ans<<endl;
	return 0;
}
1.9 明明的随机数

明明想在学校中请一些同学一起做一项问卷调查,为了实验的客观性,他先用计算机生成了N个1到1000之间的随机整数(N ≤ 100),对于其中重复的数字,只保留一个,把其余相同的数去掉,不同的数对应着不同的学生的学号。然后再把这些数从小到大排序,按照排好的顺序去找同学做调查。请你协助明明完成“去重”与“排序”的工作。

输入描述:

输入有2行,第1行为1个正整数,表示所生成的随机数的个数:N
第2行有N个用空格隔开的正整数,为所产生的随机数。

输出描述:

输出2行,第1行为1个正整数M,表示不相同的随机数的个数。
第2行为M个用空格隔开的正整数,为从小到大排好序的不相同的随机数。

示例1

输入

10
20 40 32 67 40 20 89 300 400 15

输出

8
15 20 32 40 67 89 300 400

题目大意:

对一个数组排序+去重。

思路:

硬干。

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#define int long long
using namespace std;
const int N=1e5+7;

int a[103];

signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int n;
	cin>>n;
	for(int i=1;i<=n;++i)
	{
		cin>>a[i];
	}
	sort(a+1,a+n+1);
	int ans=0;
	for(int i=1;i<=n;++i)
	{
		if(a[i]==a[i-1])continue;
		++ans;
	}
	cout<<ans<<endl;
	for(int i=1;i<=n;++i)
	{
		if(a[i]==a[i-1])continue;
		cout<<a[i]<<' ';
	}
	return 0;
}
1.10 拼数

设有n个正整数(n ≤ 20),将它们联接成一排,组成一个最大的多位整数。
例如:n=3时,3个整数13,312,343联接成的最大整数为:34331213
又如:n=4时,4个整数7,13,4,246联接成的最大整数为:7424613

输入描述:

第一行,一个正整数n。
第二行,n个正整数。

输出描述:

一个正整数,表示最大的整数

示例1

输入

3
13 312 343

输出

34331213

题目大意:

给几个数字,求首位拼接成的最大的数

思路:

贪心+排序。

如何判断两个数字哪个放前面是最优的?比如a和b,只要比较一下ab和ba的大小就行了。如果ab>ba,那么a放前面最优。且a和b的交换不会影响前面和后面的数字的排列。

注意:并不是看高位谁大谁就放前面,比如3 31 32,最大为33231.

代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#define int long long
using namespace std;
const int N=1e5+7;
const int INF=0x3f3f3f3f;
bool cmp(string a,string b)
{
	return a+b>b+a;
}
string a[23];
signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int n;
	cin>>n;
	for(int i=0;i<n;++i)
	{
		cin>>a[i];
	}
	sort(a,a+n,cmp);
	for(int i=0;i<n;++i)cout<<a[i];
	return 0;
}
1.11 激光炸弹

一种新型的激光炸弹,可以摧毁一个边长为R的正方形内的所有的目标。

现在地图上有n(N ≤ 10000)个目标,用整数Xi,Yi(其值在[0,5000])表示目标在地图上的位置,每个目标都有一个价值。

激光炸弹的投放是通过卫星定位的,但其有一个缺点,就是其爆破范围,即那个边长为R的正方形的边必须和x,y轴平行。

若目标位于爆破正方形的边上,该目标将不会被摧毁。

输入描述:

输入文件的第一行为正整数n和正整数R,接下来的n行每行有3个正整数,分别表示 xi,yi ,vi 。

输出描述:

输出文件仅有一个正整数,表示一颗炸弹最多能炸掉地图上总价值为多少的目标(结果不会超过32767)。

示例1

输入

2 1
0 0 1
1 1 1

输出

1

在这里插入图片描述

题目大意:

求最大的边长为r的正方形内的数字之和。

思路:

二维前缀和。

暴力枚举每一个位置,O(n^2).

注意:i+r可能会越界。

代码:
#include<iostream>
#include<algorithm>
#include<cstring>
//#define int long long
#define endl '\n'
using namespace std;
const int N=5e3+7;
const int INF=0x3f3f3f3f;

int Map[N][N];
int sum[N][N];

signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int n,r;
	cin>>n>>r;
	for(int i=0;i<n;++i)
	{
		int x,y,v;
		cin>>x>>y>>v;
		Map[x+1][y+1]+=v;
	}
	for(int i=1;i<=5e3+2;++i)
	{
		for(int j=1;j<=5e3+2;++j)
		{
			sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+Map[i][j];
		}
	}
	int ans=0;
	for(int i=1;i<=5e3+2-r;++i)
	{
		for(int j=1;j<=5e3+2-r;++j)
		{
			ans=max(ans,sum[i+r-1][j+r-1]-sum[i+r-1][j-1]-sum[i-1][j+r-1]+sum[i-1][j-1]);
		}
	}
	cout<<ans<<endl;
	return 0;
}
1.12 值周

题目描述

JC内长度为L的马路上有一些值周同学,每两个相邻的同学之间的间隔都是1米。我们可以把马路看成一个数轴,马路的一端在数轴0的位置,另一端在L的位置;数轴上的每个整数点,即0,1,2,…L,都有一个值周同学。 由于水宝宝有用一些区间来和ssy搞事情,所以为了避免这种事走漏风声,水宝宝要踹走一些区域的人。这些区域用它们在数轴上的起始点和终止点表示。已知任一区域的起始点和终止点的坐标都是整数,区域之间可能有重合的部分。现在要把这些区域中的人(包括区域端点处的两个人)赶走。你的任务是计算将这些人都赶走后,马路上还有多少个人。

输入描述:
第一行有2个整数L和M,L代表马路的长度,M代表区域的数目,L和M之间用一个空格隔开。 接下来的M行每行包含2个不同的整数,用一个空格隔开,表示一个区域的起始点和终止点的坐标
输出描述:
1个整数,表示马路上剩余的人的数目。

示例1

输入

500 3
150 300
100 200
470 471

输出

298

说明

对于所有的数据,1≤L≤100000000
对于10%的数据,1<=M<=100
对于20%的数据,1<=M<=1000
对于50%的数据,1<=M<=100000
对于100%的数据,1<=M<=1000000
题目大意:

同1.8

思路:

其实就是1.8的数据加强版,用离散化的方法。

虽然L很大,最大为100000000,但M只有1000000,所以可以把这些区间存起来排序,然后计算不连续的部分。

代码:
#include<iostream>
#include<algorithm>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e6+7;
const int INF=0x3f3f3f3f;

struct node
{
	int l,r;
}a[N];

signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int L,M;
	cin>>L>>M;
	for(int i=0;i<M;++i)
	{
		cin>>a[i].l>>a[i].r;
	}
	sort(a,a+M,[](node a,node b){if(a.l==b.l)return a.r<b.r;return a.l<b.l;});
	int ans=0;
	int left=-1;
	for(int i=0;i<M;++i)
	{
		if(a[i].l<=left)
		{
			left=max(a[i].r,left);
			continue;
		}
		ans+=a[i].l-left-1;
		left=a[i].r;
	}
	ans+=L-left;
	cout<<ans<<endl;
	return 0;
}
1.13 Selfish Grazing

Each of Farmer John’s N (1 <= N <= 50,000) cows likes to graze in a certain part of the pasture, which can be thought of as a large one-dimeensional number line. Cow i’s favorite grazing range starts at location Si and ends at location Ei (1 <= Si < Ei; Si < Ei <= 100,000,000).
Most folks know the cows are quite selfish; no cow wants to share any of its grazing area with another. Thus, two cows i and j can only graze at the same time if either Si >= Ej or Ei <= Sj. FJ would like to know the maximum number of cows that can graze at the same time for a given set of cows and their preferences.

Consider a set of 5 cows with ranges shown below:
  ... 1    2    3    4    5    6    7    8    9   10   11   12   13 ...
  ... |----|----|----|----|----|----|----|----|----|----|----|----|----
Cow 1:      <===:===>          :              :              :
Cow 2: <========:==============:==============:=============>:
Cow 3:          :     <====>   :              :              :
Cow 4:          :              :     <========:===>          :
Cow 5:          :              :     <==>     :              :

These ranges represent (2, 4), (1, 12), (4, 5), (7, 10), and (7, 8), respectively.
For a solution, the first, third, and fourth (or fifth) cows can all graze at the same time. If the second cow grazed, no other cows could graze. Also, the fourth and fifth cows cannot graze together, so it is impossible for four or more cows to graze.
输入描述:
* Line 1: A single integer: N
* Lines 2..N+1: Line i+1 contains the two space-separated integers: Si and Ei
输出描述:
* Line 1: A single integer representing the maximum number of cows that can graze at once.

示例1

输入

5 
2 4 
1 12 
4 5 
7 10 
7 8 

输出

3
题目大意:

求最大不覆盖的区间数量。

思路:

贪心。区间覆盖。

证明:在能选的区间里,选一个结束时间早的,不比别的方案差。因为结束时间早,后面留的空间就多。

代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#define int long long
#define endl '\n'
using namespace std;
const int N=5e4+7;
const int INF=0x3f3f3f3f;

struct node
{
	int l,r;
}a[N];

signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int n;
	cin>>n;
	for(int i=0;i<n;++i)
	{
		cin>>a[i].l>>a[i].r;
	}
	sort(a,a+n,[](node a,node b){return a.r<b.r;});
	int right=0;
	int ans=0;
	for(int i=0;i<n;++i)
	{
		if(a[i].l>=right)
		{
			right=a[i].r;
			++ans;
		}
	}
	cout<<ans<<endl;
	return 0;
}
1.14 切长条

你可以在某一行的任意两个数之间作一条竖线,从而把这个长条切开,并可能切开其他长条。问至少要切几刀才能把每一根长条都切开。

输入描述:
Line 1: A single integer, N(2 <= N <= 32000)
Lines 2..N+1: Each line contains two space-separated positive integers that describe a leash. The first is the location of the leash's stake; the second is the length of the leash.(1 <= length <= 1e7)
输出描述:
Line 1: A single integer that is the minimum number of cuts so that each leash is cut at least once.

示例1

输入

7
2 4
4 7
3 3
5 3
9 4
1 5
7 3

输出

2
题目大意:

求有重合的区间的集合数。

思路:

贪心。

按左端点排序,因为每个区间都要考虑,所以可以先从最左的区间开始,判断与它有重合的区间,取它们最小的右端点为下一个基准点。

代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#define int long long
#define endl '\n'
using namespace std;
const int N=5e4+7;
const int INF=0x3f3f3f3f;

pair<int,int>a[N];

signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int n;
	cin>>n;
	for(int i=0;i<n;++i)
	{
		int c,d;
		cin>>c>>d;
		a[i].first=c;
		a[i].second=c+d;
	}
	sort(a,a+n);
	int right=0;
	int ans=0;
	for(int i=1;i<n;++i)
	{
		if(right>a[i].first)
		{
			right=min(right,a[i].second);
		}
		else
		{
			++ans;
			right=a[i].second;
		}
	}
	cout<<ans<<endl;
	return 0;
}
1.15「土」巨石滚滚

帕秋莉掌握了一种土属性魔法

她使用这种魔法建造了一个大型的土球,并让其一路向下去冲撞障碍

土球有一个稳定性x,如果x < 0,它会立刻散架

每冲撞一个障碍,土球会丧失ai的稳定性,冲撞之后,又会从障碍身上回馈bi的稳定性

帕秋莉想知道,如果合理的安排障碍的顺序,在保证土球不散架的情况下,是否可以将障碍全部撞毁呢?

输入描述:
输入一个整数T,代表T组数据,每组数据中:
前一行两个整数n , m,表示障碍个数和土球的稳定性
接下来一行两个整数,分别表示障碍的ai和bi
输出描述:
若可以,输出“Yes”(不含引号),否则输出“No”(不含引号)

示例1

输入

1
5 50
49 49
52 0
5 10
26 24
70 70

输出

No
备注:
Σn <= 500000, 1<=m<=100000,0<=a,b<=100000
题目大意:

初始值为m,每次减去ai加上bi,问排序之后能不能保证数值一直不为负。

思路:

贪心。

跟1.13很像

先把b>=a的部分让a小到大排序,最后肯定就是整个过程的最大值,然后考虑b<a的部分。

这里其实就是之前的区间问题,数值一直减小对应数轴从左到右,选b小的在前面对应选区间右端点靠前的在前面。

代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#define int long long
#define endl '\n'
using namespace std;
const int N=5e5+7;
const int INF=0x3f3f3f3f;

struct node
{
	int l,r;
}a[N],b[N];

signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int t;
	cin>>t;
	while(t--)
	{
		int n,m;
		int numl=0;
		int numr=0;
		cin>>n>>m;
		for(int i=0;i<n;++i)
		{
			int l,r;
			cin>>l>>r;
			if(l<=r)
			{
				a[numl].l=l;
				a[numl].r=r;
				++numl;
			}
			else
			{
				b[numr].l=l;
				b[numr].r=r;
				++numr;
			}
		}
		sort(a,a+numl,[](node c,node d){return c.l<d.l;});
		sort(b,b+numr,[](node c,node d){return c.r>d.r;});
		int temp=m;
		bool check=1;
		for(int i=0;i<numl;++i)
		{
			if(temp-a[i].l<0)
			{
				check=0;
				break;
			}
			temp+=a[i].r-a[i].l;
		}
		for(int i=0;i<numr;++i)
		{
			if(temp-b[i].l<0)
			{
				check=0;
				break;
			}
			temp+=b[i].r-b[i].l;
		}
		if(!check)cout<<"No"<<endl;
		else cout<<"Yes"<<endl;
	}
	return 0;
}
1.16 Flip Game

Flip game is played on a rectangular 4x4 field with two-sided pieces placed on each of its 16 squares. One side of each piece is white and the other one is black and each piece is lying either it’s black or white side up. Each round you flip 3 to 5 pieces, thus changing the color of their upper side from black to white and vice versa. The pieces to be flipped are chosen every round according to the following rules:

  1. Choose any one of the 16 pieces.
  2. Flip the chosen piece and also all adjacent pieces to the left, to the right, to the top, and to the bottom of the chosen piece (if there are any).

imgConsider the following position as an example:

bwbw
wwww
bbwb
bwwb
Here “b” denotes pieces lying their black side up and “w” denotes pieces lying their white side up. If we choose to flip the 1st piece from the 3rd row (this choice is shown at the picture), then the field will become:

bwbw
bwww
wwwb
wwwb
The goal of the game is to flip either all pieces white side up or all pieces black side up. You are to write a program that will search for the minimum number of rounds needed to achieve this goal.

输入描述:
  The input consists of 4 lines with 4 characters "w" or "b" each that denote game field position. 
输出描述:
  Write to the output file a single integer number - the minimum number of rounds needed to achieve the goal of the game from the given position. If the goal is initially achieved, then write 0. If it's impossible to achieve the goal, then write the word "Impossible" (without quotes). 

示例1

输入

bwwb
bbwb
bwwb
bwww

输出

4
题目大意:

在一个4*4的网格上有白和黑,对一个格子操作之后可以使得该格子和上下左右的格子变色,问最少步数使得所有格子都变成黑色或白色。

思路:

典型的枚举题。

1.每个格子最多操作一次。

2.第一列的操作确定之后,后面的操作也随之确定。

可以用一个整形表示黑白的状态,即状态压缩。

代码:

写得稀烂

#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
#define int long long
using namespace std;
const int N=1e5+7;
const int INF=0x3f3f3f3f;

char b[5][5];
char d[5][5];
void cal(int x,int y)
{
	if(d[x][y]=='w')d[x][y]='b';
	else d[x][y]='w';
	if(x-1>=0)
	{
		if(d[x-1][y]=='w')d[x-1][y]='b';
		else d[x-1][y]='w';
	}
	if(x+1<4)
	{
		if(d[x+1][y]=='w')d[x+1][y]='b';
		else d[x+1][y]='w';
	}
	if(y-1>=0)
	{
		if(d[x][y-1]=='w')d[x][y-1]='b';
		else d[x][y-1]='w';
	}
	if(y+1<4)
	{
		if(d[x][y+1]=='w')d[x][y+1]='b';
		else d[x][y+1]='w';
	}
}
bool check()
{
	for(int i=0;i<4;++i)
	{
		for(int j=0;j<4;++j)
		{
			if(d[i][j]=='b')return 0;
		}
	}
	return 1;
}
bool check2()
{
	for(int i=0;i<4;++i)
	{
		for(int j=0;j<4;++j)
		{
			if(d[i][j]=='w')return 0;
		}
	}
	return 1;
}
void init()
{
	for(int i=0;i<4;++i)
	{
		for(int j=0;j<4;++j)
		{
			d[i][j]=b[i][j];
		}
	}
}
signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	for(int i=0;i<4;++i)
	{
		for(int j=0;j<4;++j)
		{
			cin>>b[i][j];
		}
	}
	int ans=INF;//--1111 15
	for(int temp=0;temp<16;++temp)//枚举第一类的操作
	{
		init();
		int cnt=0;
		for(int i=0;i<4;++i)
		{
			if((temp>>i)&1)
			{
				cal(i,0);
				++cnt;
			}
		}
		for(int i=0;i<3;++i)
		{
			for(int j=0;j<4;++j)
			{
				if(d[j][i]=='b')//全白的情况
				{
					cal(j,i+1);
					++cnt;
				}
			}
		}
		if(check())ans=min(ans,cnt);
	}

	for(int temp=0;temp<16;++temp)
	{
		init();
		int cnt=0;
		for(int i=0;i<4;++i)
		{
			if((temp>>i)&1)
			{
				cal(i,0);
				++cnt;
			}
		}
		for(int i=0;i<3;++i)
		{
			for(int j=0;j<4;++j)
			{
				if(d[j][i]=='w')//全黑的情况
				{
					cal(j,i+1);
					++cnt;
				}
			}
		}
		if(check2())ans=min(ans,cnt);
	}

	if(ans==INF)cout<<"Impossible"<<endl;
	else cout<<ans<<endl;
	return 0;
}
1.17 Subsequence
题目大意:

在一个数组中,求连续子串和不小于S的最小长度。

思路:

尺取法/双指针。

板子题。

从左边让虫子吃,移动头部r,吃到饱的时候更新一下身体长度最小值,然后从尾部l拉出来继续往前吃。

代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e5+7;
const int INF=0x3f3f3f3f;

int a[N];

signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int t;
	cin>>t;
	while(t--)
	{
		int n,m;
		cin>>n>>m;
		for(int i=0;i<n;++i)
		{
			cin>>a[i];
		}
		int ans=INF;
		int sum=0;
		int l=0,r=0;
		while(1)
		{
			while(sum<m&&r<n)
			{
				sum+=a[r++];
			}
			if(sum<m)break;
			ans=min(ans,r-l);
			sum-=a[l++];
		}
		if(ans==INF)cout<<0<<endl;
		else cout<<ans<<endl;
	}
	return 0;
}
1.18 矩阵消除游戏

在这里插入图片描述

输入描述:
第一行三个整数
接下来行每行个整数表示矩阵中各个单元格的权值。
输出描述:
输出一个整数表示牛妹能获得的最大分数。

示例1

输入

3 3 2
101 1 102
1 202 1
100 8 100

输出

414
备注:

在这里插入图片描述

题目大意:

一个n*m矩阵,可以选择其中的一行或一列,并加入和,之后该行或列变为0,最多选k个,求最大和。

思路:

枚举,贪心,前缀和。

当k>=min(n,m)时,总是可以把全部的数加入到和,一次此时答案就是sum。

先只考虑取的都是行或列时的情况,把最大的前k个加起来去一次最大。

再考虑有行和有列的情况,求出各行各列的前缀和,每次优先取出最大的那一行或一列。(个人觉得会有点问题)

代码:
#include<iostream>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
const int N=5e6+7;

int a[20][20];
int l[20];
int up[20];
int temp[20];

signed main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	int n,m,k;
	cin>>n>>m>>k;
	int sum=0;
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=m;++j)
		{
			cin>>a[i][j];
			sum+=a[i][j];
			l[i]+=a[i][j];
			up[j]+=a[i][j];
		}
	}
	if(k>=min(n,m))cout<<sum<<endl;
	else
	{
		int ans=0;
		for(int i=1;i<=n;++i)
		{
			temp[i]=l[i];
		}
		sort(temp+1,temp+1+n,greater<int>());
		for(int i=1;i<=n;++i)
		{
			temp[i]+=temp[i-1];
		}
		ans=max(ans,temp[k]);
		for(int i=1;i<=m;++i)
		{
			temp[i]=up[i];
		}
		sort(temp+1,temp+1+m,greater<int>());
		for(int i=1;i<=m;++i)
		{
			temp[i]+=temp[i-1];
		}
		ans=max(ans,temp[k]);
		int ans2=0;
		while(k--)
		{
			int Max=0;
			int pos=0;
			bool flag=0;
			for(int i=1;i<=n;++i)
			{
				if(Max<l[i])
				{
					Max=l[i];
					pos=i;
					flag=0;
				}
			}
			for(int i=1;i<=m;++i)
			{
				if(Max<up[i])
				{
					Max=up[i];
					pos=i;
					flag=1;
				}
			}
			if(flag)
			{
				up[pos]=0;
				for(int i=1;i<=n;++i)
				{
					l[i]-=a[i][pos];
				}
			}
			else
			{
				l[pos]=0;
				for(int i=1;i<=m;++i)
				{
					up[i]-=a[pos][i];
				}
			}
			ans2+=Max;
		}
		cout<<max(ans,ans2)<<endl;
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值