NOIP临近,刷刷题。
这场比赛下来明显觉得自己代码能力不够,比如第五题stogovi很明显的LCA,也想到是用LCA,然而并木有写出来,对模板的应用不够熟练。在思维方面不够严谨,比如第四题coci,没有意识到题目给的选手分数范围的意义,也可能是因为读题不够仔细吧。
在时间分配上还存在严重问题,前面的水题不能保证正确性,想错或者在某个地方手抽打错,在调试上花了很多时间以至于想后面的题时有一点慌。这也可能是因为码代码的速度不够快吧。
第一题: strojopis
打表模拟,水题一道。
#include <iostream>
#include <cstdio>
using namespace std;
int num[105] ;
char a ;
int cnt[10] ;
int main()
{
num[49]=num[81]=num[65]=num[90]=1 ;
num[50]=num[87]=num[83]=num[88]=2 ;
num[51]=num[69]=num[68]=num[67]=3 ;
num[52]=num[82]=num[70]=num[86]=num[53]=num[84]=num[71]=num[66]=4 ;
num[54]=num[89]=num[72]=num[55]=num[85]=num[74]=num[77]=num[78]=5 ;
num[56]=num[73]=num[75]=num[44]=6 ;
num[57]=num[79]=num[76]=num[46]=7 ;
num[48]=num[80]=num[59]=num[47]=num[45]=num[91]=num[39]=num[61]=num[93]=8 ;
while(~scanf("%c",&a))
++cnt[num[a]] ;
for(int i=1;i<=8;++i)
printf("%d\n",cnt[i]);
return 0;
}
第二题: dom
不说了,水题一道。
但蒟蒻在写代码的时候习惯不好,明明可以用while循环写的,却手抽用递归,爆栈不解释。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#define MAXN 100010
using namespace std;
int n ,m ,p ,a ,b ,next[MAXN] ,ans ;
bool vis[MAXN] ;
int main()
{
scanf("%d%d%d",&n,&m,&p);
for(int i=1;i<=n;++i)
{
scanf("%d%d",&a,&b);
if(!next[b])
next[b]=a;
}
vis[p]=1;
while(next[p])
{
++ans;
p=next[p];
if(vis[p])
{
ans=-1;
break;
}
vis[p]=1;
}
printf("%d\n",ans);
return 0;
}
第三题: silueta
模拟水题一道,但蒟蒻竟然分都不分 %>_<%
枚举每一列,寻找到最高位置。由于该建筑物的边框只会是连续的一段,所有最高位置就是边框的终点。比较它和前一列的高度,周长肯定要加上它俩的高度差以及这一列的宽度(就是 1 )。如果出现了这一列比前一列低,就应该去填前一列。所以在代码的 31 行进行分类。
#include <iostream>
#include <cstdio>
#include <cstring>
#define abs(a) ((a)>0?(a):-(a))
#define MAXN 1000
#define MAXM 10005
using namespace std;
int ans ,n ,l[MAXM] ,r[MAXM] ,h[MAXM] ,maxh[MAXM] ,tmp1 ,tmp2 ,tmp3 ;
char map[MAXN+5][MAXN+5] ;
int main()
{
scanf("%d",&n);
memset(map,'.',sizeof map);
tmp1=MAXM ;
for(int i=1;i<=n;++i)
{
scanf("%d%d%d",&l[i],&r[i],&h[i]);
--r[i];
tmp1=min(tmp1,l[i]) ,tmp2=max(tmp2,r[i]) ;
}
for(int i=tmp1;i<=tmp2+1;++i)
{
for(int j=1;j<=n;++j)
if(l[j]<=i&&i<=r[j])
maxh[i]=max(maxh[i],h[j]);
ans+=abs(maxh[i]-maxh[i-1]);
if(maxh[i]>0)++ans;
int end=max(maxh[i],maxh[i-1]);
if(maxh[i]<maxh[i-1])
for(int j=min(maxh[i],maxh[i-1]);j<=end;++j)
map[j][i-1]='#';
else
for(int j=min(maxh[i],maxh[i-1]);j<=end;++j)
map[j][i]='#';
map[maxh[i]][i]='#';
tmp3=max(tmp3,maxh[i]);
}
printf("%d\n",ans);
for(int i=tmp3;i>0;--i)
{
for(int j=tmp1;j<=tmp2;++j)
printf("%c",map[i][j]);
puts("");
}
for(int j=tmp1;j<=tmp2;++j)
printf("*");
puts("");
return 0;
}
第四题: honi
题目大意:给你 n 个选手的前两场比赛的分数,计算出他的总排名的范围。若选手A的前两场的分数都比选手B高,那么A第三场的分数也会比B高。分数的范围为 [0,650] 。
蒟蒻一开始以为分数的范围并没有用,就用树状数组乱YY了一种方法wa了 4 个点。事实上分数的范围是有用的,见下面的例子。
2
650 1
0 1
若没有分数范围,那么两个人的排名都为 [1,2] ,但答案为 [1,1] 和 [1,2] ,因为即使第一个人第三场得 0 分,第二个人得 650 分,第一个人也还是第一名。
正解:令 s[i][j] 表示第一场得分 i 第二场得分 j 总人数。令 num[i][j] 表示第一场得分 x<=i 并且第二场得分 y<=j 的总人数。那么他的最高排名就是比他得分都高的总人数,最低排名就是 ans=n− 比他得分低的总人数。在算最低排名的时候要特判得分为最高分的情况(原因见上面的例子)。
#include <iostream>
#include <cstdio>
#define MAXN 500005
#define MAXM 650
using namespace std;
int n ,a[MAXN] ,b[MAXN] ,ans ;
int num[MAXM+5][MAXM+5] ,s[MAXM+5][MAXM+5] ;
int solve(int x1,int y1,int x2,int y2)
{
if(x2<0||y2<0)
return 0;
int ans=num[x2][y2] ;
if(y1>0)
ans-=num[x2][y1-1];
if(x1>0)
ans-=num[x1-1][y2] ;
if(x1>0&&y1>0)
ans+=num[x1-1][y1-1] ;
return ans;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d%d",&a[i],&b[i]);
++s[a[i]][b[i]] ;
}
for(int i=0;i<=MAXM;++i)
for(int j=0;j<=MAXM;++j)
{
num[i][j]=s[i][j];
if(i>0)
num[i][j]+=num[i-1][j];
if(j>0)
num[i][j]+=num[i][j-1];
if(i>0&&j>0)
num[i][j]-=num[i-1][j-1] ;
}
for(int i=1;i<=n;++i)
{
printf("%d ",solve(a[i]+1,b[i]+1,MAXM,MAXM)+1);
ans=n-solve(0,0,a[i]-1,b[i]-1) ;
if(a[i]==MAXM)ans-=s[0][b[i]];
if(b[i]==MAXM)ans-=s[a[i]][0];
printf("%d\n",ans);
}
return 0;
}
第五题: stogovi
很明显的LCA。
如果是加入元素 i 入栈,那么 i 号节点的父亲就是他自己。
而对于操作b,即弹出栈顶元素, i 的父亲就变为 v 的父亲,同时 i 的祖先也要跟着改变。(这个转化当时木有想到就木有写出来%>_<%)
而对于操作c,就找它俩的最近公共祖先,输出祖先的长度即可。
#include <iostream>
#include <cstdio>
#define MAXN 300005
#define MAXK 20
using namespace std;
int n ,fa[MAXN] ,f[MAXN][MAXK] ,v ,w ,size[MAXN] ;
char word[5] ;
void adjust(int &u,int d)
{
for(int j=MAXK-1;j>=0;--j)
if(size[f[u][j]]>=d)
u=f[u][j] ;
}
int lca(int u,int v)
{
if(size[u]<size[v])
swap(u,v);
if(size[u]>size[v])
adjust(u,size[v]);
if(u==v)
return u;
for(int j=MAXK-1;j>=0;--j)
if(f[u][j]!=f[v][j])
u=f[u][j] ,v=f[v][j] ;
return f[u][0] ;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%s%d",word,&v);
v=fa[v] ;
fa[i]=v ;
if(word[0]=='a')
{
fa[i]=i ;
size[i]=size[v]+1 ;
f[i][0]=v ;
for(int j=1;j<MAXK;++j)
f[i][j]=f[f[i][j-1]][j-1] ;
}
else if(word[0]=='b')
{
printf("%d\n",fa[i]);
fa[i]=f[fa[i]][0] ;
}
else
{
scanf("%d",&w);
w=fa[w] ;
printf("%d\n",size[lca(v,w)]);
}
}
return 0;
}
第六题: kamioni
当时在考场上就想过将所有询问保存起来,然后所有卡车一起跑,处理卡车掉头的地方,每一次都对询问处理一次。然而蒟蒻却更新了所有的卡车,就发现还没爆搜跑得快,于是果断写暴力= =
正解:将每一辆卡车的掉头地点、时间以及方向存起来,按时间排序。对于一个询问,只需要处理一个卡车就行了。所以将这个询问放在掉头数小的那辆车上。更新跟它有询问的车就行了。
接下来分析时间复杂度。
若一辆卡车的掉头数超过了 sqrt(k) ,那么它的询问肯定不会超过 sqrt(k) 。假设它的询问超过了 sqrt(k) ,那么那些对应的卡车的掉头次数超过了 sqrt(k) ,总和就已经超过了 k ,矛盾。
若一辆卡车的掉头数少于了 sqrt(k) ,虽然它的询问可能会超过 sqrt(k) ,但是掉头数少于 sqrt(k) 。
这样均摊下来,时间复杂度为 O(k∗sqrt(k))
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
#include <vector>
#define abs(a) ((a)>0?(a):-(a))
#define MAXN 100005
#define MAXK 300005
#define pii pair<int,int>
#define LL long long int
using namespace std;
map<pii,vector<int> >sameask;
int n ,m ,a ,b ,pos1 ;
int num[MAXN] ,dir[MAXN] ,ans[MAXN] ;
LL ti ;
struct Car
{
int id ,pos ,dir ;
LL t;
bool operator < (const Car &a)const
{
if(t!=a.t)return t<a.t;
return dir>a.dir;
}
}car[MAXK] ,truck[MAXN] ;
struct node
{
int id ,v ;
node *next ;
}edge[MAXN] ,*adj[MAXN] ,*code=edge ;
int check_left(const Car &a,const Car &b,LL t,int dir)
{
if(b.dir==0&&t>=b.t)return 0;
int apos=a.pos ,bpos=b.pos ;
if(b.dir==0)
apos+=(a.t-b.t)*(-dir);
else bpos+=(a.t-b.t)*b.dir;
if(apos<bpos)
return 1;
return -1;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
{
scanf("%d%d",&num[i],&a);
ti=0;
for(int j=1;j<num[i];++j)
{
scanf("%d",&b);
Car e={i,a,a<b?1:-1,ti};
if(j==1)
truck[i]=e;
else car[pos1++]=e;
ti+=abs(a-b) ,a=b ;
}
Car e={i,a,0,ti};
car[pos1++]=e;
}
for(int i=0;i<m;++i)
{
scanf("%d%d",&a,&b);
if(a>b)swap(a,b);
sameask[pii(a,b)].push_back(i);
if((int)sameask[pii(a,b)].size()>1)continue;
if(num[a]>num[b])swap(a,b);
code->v=b ,code->id=i ,code->next=adj[a] ;
adj[a]=code ;
++code ;
dir[i]=truck[a].pos<truck[b].pos?1:-1;
}
sort(car,car+pos1);
int id ,temp ,dir1 ;
for(int i=0;i<pos1;++i)
{
id=car[i].id ;
ti=truck[id].t ,dir1=truck[id].dir ;
truck[id]=car[i];
for(node *p=adj[id];p!=NULL;p=p->next)
{
temp=check_left(truck[id],truck[p->v],ti,dir1);
ans[p->id]+=(dir[p->id]*temp==-1);
dir[p->id]=temp;
}
}
int res ;
for(map<pii,vector<int> >::iterator p=sameask.begin();p!=sameask.end();p++)
{
res=ans[p->second[0]];
for(int i=1;i<p->second.size();++i)
ans[p->second[i]]=res;
}
for(int i=0;i<m;++i)
printf("%d\n",ans[i]);
return 0;
}