您的位置:首页 > 其它

【20150911】NOIP模拟套题02 day1 题解 & 总结

2015-09-24 17:42 246 查看

前言

  最近套题的难度越来越高了……应该说随着难度的提升,题目质量也在提升,至少已经没有一眼题了(不过这一套题仍在放水)。虽然说比赛结束时看到自己的低分总觉得些许不爽,但是实力摆在那里,再多刷刷题吧!

T1_任务

  题目大意:有 n 个星球 ( 1≤n≤1051\le n\le 10^5 ),第 ii 个星球上储存了 A[i]A[i] 单位的燃料(燃料不会再生),从其他星球到达这个星球都需要消耗 B[i]B[i] 的燃料。现在你在第 stst 个星球,燃料初始值为 0,问以任意顺序到任意星球旅行后最大剩余燃料值,以及在此前提下能够到达的最多星球数量。

  考试时我定睛一看——不是吧,这么简单?当然我反复看了几遍之后,终于确认这是一道不折不扣的送分题,想必出题人不希望太多人吃鸭蛋?只要将所有星球按照 A[i]−B[i]A[i]-B[i] 从小到大排序,然后尽量去 A[i]>B[i]A[i]>B[i] 的星球(也即尽量赚取燃料差)即可,时间复杂度 O(nlogn)O(n\log n)。

Code

#include<cstdio>
#include<iostream>
#include<algorithm>
#define fir first
#define sec second
#define fo(i,x,y) for (int i=x;i<=y;++i)
using namespace std;

typedef pair<int,int> P;

const int maxn=100000+10;

int n,st;
P a[maxn];

int main(){
freopen("mission.in","r",stdin);
freopen("mission.out","w",stdout);
scanf("%d%d",&n,&st);
int m=0, now;
fo(i,1,n){
int x,y; scanf("%d%d",&x,&y);
if (i==st) now=x, x=0;
if (x<y) continue;
++m; a[m].fir=y, a[m].sec=x-y;
}
sort(a+1,a+m+1);
int cnt=1;
fo(i,1,m){
if (now<a[i].fir) break;
++cnt, now+=a[i].sec;
}
printf("%d\n%d\n",now,cnt);
return 0;
}


T2_补给站

  题目大意:平面坐标系上有 n 个点 ( 1≤n≤2000001\le n\le 200000 ),还有 2 个补给站(位置固定)。给出 m 个询问,每个询问给出第 1 个补给站的半径 r1r_1、第 2 个补给站的半径 r2r_2,问 2 个补给站一共覆盖了多少个点。

  一看这道题,就有种亲切的感觉——这一类型的题目好像至少做了5遍吧?可悲的是我仍然不能一眼看出想法……比赛时慢慢琢磨,终于想出题解了。

  首先看到多组询问而且没有强制在线,那么就可以离线处理了。先把题目简化,如果只有一个补给站的话,解法就会变得非常明显——把所有点按照该点到补给站的距离排序,对于每个询问只需要二分查询即可,甚至都不需要用到离线算法。

  拓展这个问题,现在有两个补给站。一个朴素的想法是排序的时候按照点到两个补给站的距离的较小值排序,但是这样做会丢失信息——如果一个补给站的半径很大,另一个补给站的半径很小,这样做就会GG。不妨模仿前一个问题的解法,将点分别按照与第 1、第 2 个补给站的距离排序,那么通过二分就可以知道两个补给站分别覆盖了多少个点。当然这样做仍然有问题——有些点能被两个补给站覆盖,这些点重复计算了。因此问题变成了如何找出被重复计算的点。

  这时候询问可以离线的特点就可以排上用场了。先假设将点按照与第 1 个补给站距离排序后存入的数组为 PP,按照与第 2 个补给站距离排序后存入数组 QQ。将所有询问按照第 1 个补给站的半径从小到大排序。如果按顺序遍历这些询问的话,对于每个询问 AskiAsk_i 我们可以动态得知 PP 数组中,有前 xix_i 个点被第 1 个补给站覆盖。显然 xi−1≤xix_{i-1}\le x_i,即当前询问比上一个询问可能多覆盖了 xi−xi−1x_i-x_{i-1} 个点。这时候如果给这些新覆盖的点打上标记,同时查询 QQ 数组中距离在指定范围内、且没有被标记的点的个数,不就避免了重复计数的情况吗?

  而实际上这个查询的距离范围一定是 [0,y][0,y],如果知道距离小于等于 yy 的点在 QQ 中的下标,就变成了一个区间查询问题。至于查找下标,普通的二分就能做到;如此有特征的区间查询,正是树状数组的长处。至此问题解决,注意实现时的一些细节就好了。时间复杂度 O(nlogn+mlogn)O(n\log n+m\log n)。

Code

#include<cstdio>
#include<iostream>
#include<algorithm>
#define fir first
#define sec second
#define fo(i,x,y) for (int i=x;i<=y;++i)
using namespace std;

typedef long long LL;
typedef pair<LL,LL> P;

const int maxn=200000+10, maxm=100000+10;

struct node{
LL x,y;
int id;
} b[maxn],q[maxm];

int n,m,cd,x1,y1,x2,y2,lk[maxn],dy[maxn],ans[maxm];
LL dx[maxn];
P a[maxn];

struct BIT{
int a[maxn];
void add(int v){ while (v<=n) ++a[v], v+=v&(-v); }
int query(int v){
int ret=0;
while (v>0) ret+=a[v], v-=v&(-v);
return ret;
}
} cnt;

bool cmp(node a,node b){ return (a.x<b.x || (a.x==b.x && a.y<b.y)); }

int main(){
freopen("supply.in","r",stdin);
freopen("supply.out","w",stdout);
scanf("%d%d%d%d%d%d",&n,&m,&x1,&y1,&x2,&y2);
fo(i,1,n){
int x,y;
scanf("%d%d",&x,&y);
a[i].fir=1LL*(x-x1)*(x-x1)+1LL*(y-y1)*(y-y1);
a[i].sec=1LL*(x-x2)*(x-x2)+1LL*(y-y2)*(y-y2);
}
sort(a+1,a+n+1);
fo(i,1,n) b[i].x=a[i].sec, b[i].y=a[i].fir, b[i].id=i;
sort(b+1,b+n+1,cmp);
LL lst=0; b[0].x=dx[0]=-1; cd=0;
fo(i,1,n){
lk[b[i].id]=i;
if (b[i].x>b[lst].x){
dy[cd]=i-1;
dx[++cd]=b[i].x;
lst=i;
}
}
dy[cd]=n;
fo(i,1,m){
scanf("%d%d",&q[i].x,&q[i].y);
q[i].x*=q[i].x, q[i].y*=q[i].y;
q[i].id=i;
}
sort(q+1,q+m+1,cmp);
int now=0, pt=1;
fo(i,1,m){
while (pt<=n && a[pt].fir<=q[i].x){
++now;
cnt.add(lk[pt]);
++pt;
}
int l=0, r=cd;
while (l<r){
int md=(l+r)/2+1;
if (dx[md]<=q[i].y) l=md; else r=md-1;
}
ans[q[i].id]=now+dy[l]-cnt.query(dy[l]);
}
fo(i,1,m) printf("%d\n",ans[i]);
return 0;
}


T3_池塘

  题目大意:有两只青蛙要过池塘。池塘可以表示为一个 n×mn\times m 的矩阵 ( 1≤n,m≤501\le n,m\le 50 ),其中有些地方没有荷叶,有些地方的荷叶只可以被踩 1 次且只能承受 1 只青蛙的重量,有些地方的荷叶可以同时承受 2 只青蛙的重量且没有次数限制。现在一只青蛙在池塘的最上边任意一片荷叶上,要到最下边;另一只在池塘的最下边任意一片荷叶上,要到最上边。已知青蛙只能面对他要去的那一边方向跳,有 5 个不同的方向如下图。现询问两只青蛙行走路线的方案数 mod 109+710^9+7。



  其实一眼看上去这题很像一道路径覆盖的题目,但因为两条路径允许重复,并不能这么做。比赛时除了暴力我没有任何想法,还是万能的 WWT(Tsinghua)WWT_{(Tsinghua)} 给的题解。

  首先能够发现的是任意一条路径可逆,只需要考虑两只青蛙从同一个方向出发的情况。如果新增一个起点和终点,起点和最上(下)边的一排点连边,终点和最下(上)边的一排点连边,矩阵就变成了一幅图。给所有的点按顺序编好号后,神奇的DP就出现了——设 F[u]F[u] 表示两只青蛙从起点一起到达点 uu、且在到点 uu 之前满足题设条件(也即点 uu 不一定能承受 2 只青蛙)的方案数,g[u][v]g[u][v] 表示两只青蛙从点 uu 一起到达点 vv 且完全不考虑题设条件的方案数。

  由于“合法的方案数=总方案数-不合法方案数”,而且如果一条路径上两只青蛙到达了一片会超载的荷叶后,之后的路怎么走都不会合法。所以我们先设 F[u]=g[0][u]F[u]=g[0][u],枚举到达点 uu 前到达的那片超载的荷叶最早为 vv,就有F[u]=F[u]−(F[v]∗g[v][u])F[u]=F[u]-(F[v]\ast g[v][u])

  先预处理出 gg 后按照上述方程DP即可。时间复杂度 O(n2m2)O(n^2m^2)。

Code

#include<cstdio>
#include<iostream>
#define fo(i,x,y) for (int i=x;i<=y;++i)
using namespace std;

const int maxn=50+5, mod=1000000007;
const int fx[5][2]={{-1,-2},{-1,2},{-2,-1},{-2,1},{-3,0}};

int n,m,v[maxn*maxn],a[maxn*maxn],b[maxn*maxn*5][2],c,g[maxn*maxn][maxn*maxn],f[maxn*maxn];

void add(int x,int y){ b[++c][0]=y; b[c][1]=a[x]; a[x]=c; }
int sqr(int v){ return 1LL*v*v%mod; }

int main(){
freopen("pool.in","r",stdin);
freopen("pool.out","w",stdout);
scanf("%d%d",&n,&m);
fo(i,1,n)
fo(j,1,m){
int t; scanf("%d",&t);
v[(i-1)*m+j]=t;
}
c=0;
fo(i,1,m)
if (v[i]) add(i,0);
fo(i,2,n)
fo(j,1,m){
int X=(i-1)*m+j; if (!v[X]) continue;
fo(k,0,4){
int x=i+fx[k][0], y=j+fx[k][1], Y=(x-1)*m+y;
if (x<1 || y<1 || y>m) continue;
if (v[Y]) add(X,Y);
}
}
fo(i,1,m)
if (v[n*m-m+i]) add(n*m+1,n*m-m+i);
v[0]=v[n*m+1]=2;
fo(i,0,n*m+1){
if (!v[i]) continue;
g[i][i]=1;
fo(j,i+1,n*m+1)
for (int z=a[j];z;z=b[z][1]) g[i][j]=(g[i][j]+g[i][b[z][0]])%mod;
}
f[0]=1;
fo(i,1,n*m+1){
f[i]=sqr(g[0][i]);
fo(j,1,i-1)
if (v[j]==1) f[i]=(f[i]+mod-(1LL*f[j]*sqr(g[j][i]))%mod)%mod;
}
printf("%d\n",f[n*m+1]);
return 0;
}


总结

  这套题难度实际上算一般,但是质量很高。虽然T1在放水,但是T2花了我绝大部分时间思考题解(虽然题型老),T3的解法更是我在考试时无法想出的。总体来说,虽然拿了200分,但是很不容易;不够时间打T3的暴力算是个小遗憾,但是考试策略和实现上没出大问题,该对拍的也对拍了,所以发挥稳定。感觉对常用的线段树和树状数组的运用水平稍稍提升了一些。等待day2的难题……
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: