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)的效率得到此时的最小距离(两点间走最短距离)。因此合起来就是的效率,能够得到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;
}