10.7 p.m.小结

T1:问题 A: 松鼠聚会

题目描述

草原上住着一群小松鼠,每个小松鼠都有一个家。时间长了,大家觉得应该聚一聚。但是草原非常大,松鼠们都很头疼应该在谁家聚会才最合理。

每个小松鼠的家可以用一个点 (x,y)表示,两个点的距离定义为点 (x,y)和它周围的 8个点 (x−1,y),(x+1,y),(x,y−1),(x,y+1),(x−1,y+1),(x−1,y−1),(x+1,y+1),(x+1,y−1) 距离为 1。

输入

第一行是一个整数 N,表示有多少只松鼠。接下来 N 行,第 i 行是两个整数 x和 y,表示松鼠 i 的家的坐标。

 

输出

一个整数,表示松鼠为了聚会走的路程和最小是多少。

样例输入

<span style="color:#333333"><span style="background-color:#f5f5f5">6
-4 -1
-1 -2
2 -4
0 2
0 3
5 -2</span></span>

样例输出

<span style="color:#333333"><span style="background-color:#f5f5f5">20</span></span>

提示


样例解释

 


在样例中,松鼠在第二只松鼠家 (−1,−2)聚会;

 


数据范围

 


30% 的数据,0≤N≤1000;

 


100%的数据,0≤N≤10^5,−10^9 <= x, y <=10^9。

题解

先谈谈朴素算法。可以得知:两个点之间的距离为max(|x1-x2|,|y1-y2|)。因此当选取一个点为中心点时,能够以O(n)的效率得到此时的最小距离(两点间走最短距离)。因此合起来就是O(n^2)的效率,能够得到30分。现在来看怎样把暴力应用到100000的数据中。可以确定的是,还是用之前那种方式计算距离和。那么意味着只能搜一些点作为中心。可以想到,越是中间的点,是中心的概率最大。因此先按照x+y排个序,再取中间的一些点。点的数量与n的大小有关,需要分段卡一下时间,不然答案会错。特别注意时间不要太接近1s,要控制在最高都不会超过1s……

参考代码

#include<cstdio>
#include<algorithm>
#define LL long long
using namespace std;
struct node
{
	LL x,y;
}a[100001];
LL n,ans=0,minn=99999999999999999ll;
bool comp1(node p,node q)
{
	return p.x+p.y<q.x+q.y;
}
LL max1(LL p,LL q) { return p>q?p:q; }
LL min1(LL p,LL q) { return p<q?p:q; }
LL abs1(LL p) { return p>0?p:-p; }
int main()
{
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)
	scanf("%lld%lld",&a[i].x,&a[i].y);
	sort(a+1,a+n+1,comp1);
	if(n<=10000)
	{
		for(int i=max1(1,n/2-2000);i<=min1(n,n/2+2000);i++)
		{
			ans=0;
			for(int j=1;j<=n;j++)
			ans+=max1(abs1(a[i].x-a[j].x),abs1(a[i].y-a[j].y));
			minn=min1(minn,ans);
		}	
	}
	else if(n<=30000)
	{
		for(int i=max1(1,n/2-1000);i<=min1(n,n/2+1000);i++)
		{
			ans=0;
			for(int j=1;j<=n;j++)
			ans+=max1(abs1(a[i].x-a[j].x),abs1(a[i].y-a[j].y));
			minn=min1(minn,ans);
		}
	}
	else
	{
		for(int i=n/2-380;i<=n/2+380;i++)
		{
			ans=0;
			for(int j=1;j<=n;j++)
			ans+=max1(abs1(a[i].x-a[j].x),abs1(a[i].y-a[j].y));
			minn=min1(minn,ans);
		}
	}
	printf("%lld",minn);
	return 0;
}

T2:问题 B: 拯救小矮人

题目描述

一群小矮人掉进了一个很深的陷阱里,由于太矮爬不上来,于是他们决定搭一个人梯。即:一个小矮人站在另一小矮人的 肩膀上,知道最顶端的小矮人伸直胳膊可以碰到陷阱口。

对于每一个小矮人,我们知道他从脚到肩膀的高度Ai,并且他的胳膊长度为Bi。陷阱深度为H。

如果我们利用矮人1,矮人2,矮人3,。。。矮人k

搭一个梯子,满足A1+A2+A3+....+Ak+Bk>=H,那么矮人k就可以离开陷阱逃跑了,一旦一个矮人逃跑了,他就不能再搭人梯了。

我们希望尽可能多的小矮人逃跑,问最多可以使多少个小矮人逃跑。

第一行一个整数N, 表示矮人的个数,

接下来N行每一行两个整数Ai和Bi,最后一行是H。(Ai,Bi,H<=10^5)

输入

第一行一个整数N, 表示矮人的个数,

接下来N行每一行两个整数Ai和Bi,最后一行是H。(Ai,Bi,H<=10^5)

输出

一个整数表示对多可以逃跑多少小矮人

样例输入

2
20 10
5 5
30

样例输出

2

提示


30%的数据 N<=200

100%的数据 N<=2000

题解

可以猜到,这道题应该用贪心。如果手能够把小矮人举起的话,一个排序就完了。因此考虑如何排序能更优。对于2个人来说,身高和手长的总和小的肯定要先出去,不然原本有机会出去的就可能因为位置不到位就出不去了。对于总和一样的情况,身子长的肯定适合垫底(如果出不去),因此让身子矮的先出去。综上,可以先对所有的小矮人排一个序。

是否这样就可以得到正确答案?当然不一定,因为小矮人出去的顺序也会影响答案。因此为了确定到底哪些矮人能出,考虑在排序的基础上用背包。

背包有两种方式,一种是dp[i]表示已经出去了i个人,剩下的人中总身高最高为多少,此时dp[0]应该为所有人的身高总和(不包括手长)。另一种为dp[i]表示还剩i个人,这i个人的身高总和最多为多少,此时dp[n]表示所有人的身高总和。这样种方式的转移都是类似的,得保证上一个状态的总身高加上最上面那个人的手长要大于H。注意初始值赋为-1表示未曾取到,最后找的就是临界状态,以下给出两种方式的代码。

参考代码1(方式一)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct node
{
	int x,y;
}a[2001];
int n,dp[2002],h;
bool comp1(node p,node q)
{
	if(p.x+p.y<q.x+q.y) return 1;
	if(p.x+p.y>q.x+q.y) return 0;
	if(p.x<q.x) return 1;
	return 0;
}
int max1(int p,int q) { return p>q?p:q; }
int min1(int p,int q) { return p<q?p:q; }
int main()
{
	scanf("%d",&n);
	memset(dp,-1,sizeof(dp));
	dp[0]=0;
	for(int i=1;i<=n;i++) 
	{
		scanf("%d%d",&a[i].x,&a[i].y);
		dp[0]+=a[i].x;
	}
	scanf("%d",&h);
	sort(a+1,a+n+1,comp1);
	for(int i=1;i<=n;i++)
	{
		for(int j=i;j>=1;j--)
		{
			if(dp[j-1]+a[i].y>=h)
			{
				dp[j]=max1(dp[j],dp[j-1]-a[i].x);
			}
		}
	}
	for(int i=n;i>=0;i--)
	{
		if(dp[i]>=0) 
		{
			printf("%d",i);
			break;
		}
	 } 
	return 0;
}

参考代码2(方式二)

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct node
{
	int x,y;
}a[2001];
int n,dp[2002],h;
bool comp1(node p,node q)
{
	if(p.x+p.y<q.x+q.y) return 1;
	if(p.x+p.y>q.x+q.y) return 0;
	if(p.x<q.x) return 1;
	return 0;
}
int max1(int p,int q) { return p>q?p:q; }
int min1(int p,int q) { return p<q?p:q; }
int main()
{
	scanf("%d",&n);
	memset(dp,-1,sizeof(dp));
	dp[n]=0;
	for(int i=1;i<=n;i++) 
	{
		scanf("%d%d",&a[i].x,&a[i].y);
		dp[n]+=a[i].x;
	}
	scanf("%d",&h);
	sort(a+1,a+n+1,comp1);
	for(int i=1;i<=n;i++)
	{
		for(int j=n-i;j<=n;j++)
		{
			if(dp[j+1]+a[i].y>=h)
			{
				dp[j]=max1(dp[j],dp[j+1]-a[i].x);
			}
		}
	}
	for(int i=0;i<=n;i++)
	{
		if(dp[i]>=0) 
		{
			printf("%d",n-i);
			break;
		}
	 } 
	return 0;
}

T3:问题 C: 最长上升子序列

题目描述

给定一个序列,初始为空。现在我们将1到N的数字插入到序列中,每次将一个数字插入到一个特定的位置。每插入一个数字,我们都想知道此时最长上升子序列长度是多少?

输入

第一行一个整数N,表示我们要将1到N插入序列中。

接下是N个数字,第k个数字Xk,表示我们将k插入到位置Xk(0<=Xk<=k-1,1<=k<=N)

输出

N行,第i行表示i插入Xi位置后序列的最长上升子序列的长度是多少。

样例输入

3
0 0 2

样例输出

1
1
2

提示


100%的数据 n<=100000

题解

听说这道题可以用平衡树或者vector搞定。在下才疏学浅,提供树状数组的方法。显然这道题可以做成离线的题。先用树状数组处理出终态的序列。然后倒着来找dp值,再用一个树状数组维护最大值,最后再依次输出答案即可。注意答案是前i个dp中的最大值,需要特别注意一下。

题解

#include<cstdio>
using namespace std;
int n,maxn=0,pos[1000100],a[1001000],c[1000100],d[1000100],ans[1000100];
int max1(int p,int q) { return p>q?p:q; }
void add(int t,int k)
{
	for(;t<=n;t+=t&-t) c[t]+=k;
}
int query(int t)
{
	int ret=0;
	for(;t;t-=t&-t) ret+=c[t];
	return ret;
}
void add1(int t,int k)
{
	for(;t<=n;t+=t&-t) d[t]=max1(d[t],k);
}
int query1(int t)
{
	int ret=0;
	for(;t;t-=t&-t) ret=max1(d[t],ret);
	return ret;
}
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++) 
	{
		scanf("%d",&pos[i]);
		add(i,1);
	}
	for(int i=n;i>=1;i--)
	{
		int l=1,r=n;
		while(l<r)
		{
			int mid=(l+r+1)/2;
			if(query(mid)>pos[i]+1) r=mid-1;
			else l=mid;
		}
		while(query(l)-query(l-1)==0) l--;
		a[l]=i;
		add(l,-1);
	}
	for(int i=1;i<=n;i++)
	{
		ans[a[i]]=query1(a[i]-1)+1;
		add1(a[i],ans[a[i]]);
	}
	for(int i=1;i<=n;i++) 
	{
		maxn=max1(maxn,ans[i]);
		printf("%d\n",maxn);
	}
	return 0;
} 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值