【SinGuLaRiTy-1011】 Copyright (c) SinGuLaRiTy 2017. All Rights Reserved.
G2019级信息奥赛专项训练
题目 |
程序名 |
时间 |
内存 |
测试点 |
原子核研究 |
atomic.cpp |
2s |
256MB |
10 |
球星 |
star.cpp |
2s |
256MB |
10 |
跳跃 |
jump.cpp |
2s |
256MB |
10 |
原子核研究(atomic.cpp)
题目描述
最近物理学家正在研究一些新的原子,称为X族。他们发现该族中的元素非常相近,但是其质量都不相同。质量越接近的越容易相互转化。现在,物理学家已经发明了一种设备,可以对X族的元素来进行以下三种操作:
1.generate M 产生质量为M的一个元素,如果已经存在,则该操作不产生作用。
2.romove M 销掉质量为M的元素,如果不存在质量为M的元素,则该操作不产生作用。
3.query 查询X族中质量最接近的两种元素的质量差的绝对值。如果此时X族中的元素个数小于2,则输出-1.
现在,请你写一个程序来模拟该设备的操作。
输入
输入包含多组测试数据,第一行一个整数t,表示测试数据的组数。对于每一组数据,第一行是一个整数n,表示有n条指令。接下来n行,每行表示一条指令,如上所述。M是一个正整数,不超过100000.
输出
对于每组测试数据,对于其中的每一个查询操作,都要输出一个结果。注意:每一组测试数据后要输出一个空行。
样例数据
样例输入 |
样例输出 |
1 |
-1
6
3
2
4
|
STD Code
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> using namespace std; #define MAXN 100000 #define MAXM 100000 #define INF 0x3f3f3f3f typedef long long int LL; void Read(int &x) { x=0;char c=getchar();bool flag=0; while(c<'0'||'9'<c){if(c=='-')flag=1;c=getchar();} while('0'<=c&&c<='9'){x=x*10+c-'0';c=getchar();} if(flag)x=-x; } int L[MAXM*3],R[MAXM*3]; int len[MAXM*3]; int lval[MAXM*3],rval[MAXM*3],minval[MAXM*3]; int sz; void updata(int x) { lval[x]=lval[x<<1]; if(lval[x<<1]==len[x<<1]) lval[x]+=lval[x<<1|1]; rval[x]=rval[x<<1|1]; if(rval[x<<1|1]==len[x<<1|1]) rval[x]+=rval[x<<1]; minval[x]=min(minval[x<<1],minval[x<<1|1]); if(rval[x<<1]!=len[x<<1]&&lval[x<<1|1]!=len[x<<1|1]) minval[x]=min(minval[x],rval[x<<1]+lval[x<<1|1]); } void build(int l,int r,int x) { L[x]=l,R[x]=r; len[x]=r-l+1; lval[x]=rval[x]=len[x]; minval[x]=INF; if(L[x]==R[x]) return; int mid=(l+r)>>1; build(l,mid,x<<1); build(mid+1,r,x<<1|1); updata(x); } void insert(int pos,int x) { if(L[x]==R[x]) { lval[x]=rval[x]=0; minval[x]=INF; return; } int mid=(L[x]+R[x])>>1; if(pos<=mid) insert(pos,x<<1); else insert(pos,x<<1|1); updata(x); } void del(int pos,int x) { if(L[x]==R[x]) { lval[x]=rval[x]=1; minval[x]=INF; return; } int mid=(L[x]+R[x])>>1; if(pos<=mid) del(pos,x<<1); else del(pos,x<<1|1); updata(x); } int query() { if(sz<2) return -1; else return minval[1]+1; } bool has[MAXM+10]; int main() { int T,n; Read(T); while(T--) { Read(n); sz=0; build(1,MAXM,1); memset(has,0,sizeof(has)); char str[10]; int x; for(int i=1;i<=n;++i) { scanf("%s",str); if(str[0]=='g') { Read(x); if(!has[x]) { has[x]=1,++sz; insert(x,1); } } else if(str[0]=='r') { Read(x); if(has[x]) { has[x]=0; --sz; del(x,1); } } else printf("%d ",query()); } putchar(10); } return 0; }
题目分析
首先根据题目中涉及查询,插入,删点等操作,就可以大致判断出要用树形存储结构来做。
对于插入与删点这两个操作来说,基本就是"打板",与普通的平衡树没有什么差别。只是题目中提及“可能会删去‘不存在’的元素或插入‘存在’的元素”,最开始我没怎么在意,觉得只要在Delete函数与Insert函数里面修改一下判断条件就行了,后来却始终没有成功,调了足足有40多分钟(也就是这里耽误了太多时间)——后来发现问题在于对树的高度的更新存在问题:只要进行插入或删除操作就会更改平衡树的高度,而这显然是错误的。 后来才“迫不得已”用了一个“蠢办法”,就是设置一个bool型的has数组,去判断一个值是否存储在树中,来规避重复加/删点的问题。(当然,这个方法在最后被证明是正解)
对于查询操作,当时其实并不觉得写起来有多大的困难:在updata函数里面顺带加一个判断去更新minval数组就行了。
球星(star.cpp)
题目描述
给出球星们的能力值、年份、名字,有很多个查询,每个查询给出一个年份的范围,求出这个范围里能力值从高到低排列的前11名球员,如果能力值相同则按年份从低到高排,如果年份仍然相同,则按名字的字典序排。如果不足11个球员,就用XXX代替输出凑够11行。
输入
输入数据:
第一行包含1个整数N(1<=N<=50000),表示球星的总数,接下来N行,每行描述了1个球星(year,name,ability)。0<=year<=1000000000,name不超过15个字母,0<=ability<=1000000000.
假设没有两个人的名字相同。接下来有一个整数R,表示有R个查询。接下来R行,每行描述了一个产寻,每个查询包含2个整数(x,y),表示从第x年到第y年。(0<=x<=y<=1000000000)
输出
输出数据:对于每组数据,按上面描述的顺序输出最靠前的11个球员的名字,每个球员占一行。如果不足11行,则用XXX代替,凑够11行。每组数据后都有一个空行。
样例数据
样例输入 |
样例输出 |
5 1 a 1 2 b 2 2 c 6 5 e 50 5 d 50 2 1 2 5 5 |
c
b
a
XXX
XXX
XXX
XXX
XXX
XXX
XXX
XXX
d
e
XXX
XXX
XXX
XXX
XXX
XXX
XXX
XXX
XXX
|
STD Code
#include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #include<algorithm> #define maxn 50000 using namespace std; struct node { int l,r; int f[12]; }tree[4*maxn+10]; int f[12]; struct d { int y,a; char n[20]; bool operator <(const d &b)const { if(a==b.a) { if(y==b.y) { int len=max(strlen(n),strlen(b.n)); for(int i=0;i<len;++i) { if(n[i]==b.n[i]) continue; return n[i]>b.n[i]; } } return y>b.y; } return a<b.a; } }p[maxn+10]; int n; bool cmp(const d &a,const d &b) { return a.y<b.y; } void uptree(int i) { int lson=i<<1,rson=(i<<1)|1; int cl=0,cr=0,c=0; while((tree[lson].f[cl]||tree[rson].f[cr])&&c<11) { if(!tree[lson].f[cl]) { tree[i].f[c]=tree[rson].f[cr]; c++;cr++; } else if(!tree[rson].f[cr]) { tree[i].f[c]=tree[lson].f[cl]; c++;cl++; } else if(p[tree[lson].f[cl]]<p[tree[rson].f[cr]]) { tree[i].f[c]=tree[rson].f[cr]; c++;cr++; } else { tree[i].f[c]=tree[lson].f[cl]; c++;cl++; } } return; } void Build_tree(int i,int l,int r) { tree[i].l=l; tree[i].r=r; if(l==r) { tree[i].f[0]=l; return; } int m=(l+r)>>1; Build_tree(i<<1,l,m); Build_tree((i<<1)|1,m+1,r); uptree(i); } void Ask(int i,int l,int r) { if(tree[i].l>r||tree[i].r<l) return; if(l<=tree[i].l&&r>=tree[i].r) { int tf[12]; memcpy(tf,f,sizeof f); memset(f,0,sizeof f); int c1=0,c2=0,c=0; while((tf[c1]||tree[i].f[c2])&&c<11) { if(!tf[c1]) { f[c]=tree[i].f[c2]; c++;c2++; } else if(!tree[i].f[c2]) { f[c]=tf[c1]; c++;c1++; } else if(p[tf[c1]]<p[tree[i].f[c2]]) { f[c]=tree[i].f[c2]; c++;c2++; } else { f[c]=tf[c1]; c++;c1++; } } return; } Ask(i<<1,l,r); Ask((i<<1)|1,l,r); return; } int Find(int i,bool flag) { int L=1,R=n; if(!flag) { while(L<R) { int M=(L+R)>>1; if(p[M].y<i) L=M+1; else R=M; } return L; } else { while(L<R) { int M=(L+R+1)>>1; if(p[M].y>i) R=M-1; else L=M; } return L; } } int main() { p[0].a=-1; while(cin>>n) { for(int i=1;i<=n;++i) scanf("%d%s%d",&p[i].y,p[i].n,&p[i].a); sort(p+1,p+n+1,cmp); Build_tree(1,1,n); int q; cin>>q; for(int i=1;i<=q;++i) { int l,r; scanf("%d%d",&l,&r); l=Find(l,0); r=Find(r,1); Ask(1,l,r); for(int j=0;j<11;++j) { if(f[j]) printf("%s ",p[f[j]].n); else printf("XXX "); } if(i!=q) printf(" "); memset(f,0,sizeof f); } memset(tree,0,sizeof tree); } return 0; }
题目分析
刚看到这道题的时候,唯一的反应就是“EXM?”实在是看不懂应该用那种算法好不好?想了想,觉得既然每个节点包含了那么多种信息,我就写个线段树+Splay好了,于是乎......写到一半发现自己写得思路已经不知所踪,那棵树呀就在转呀转呀......到了考试最后,我绝望的打了一个暴力,安慰自己:”至少,根据经验,至少能水个30分吧......“ 测评时,发现竟只有一个数据点——是的,超级大数据,果断爆零。
下来发现,似乎并没有自己想的那么复杂,用一个线段树+结构体排序,或是一个线段树+优先队列就行了。也就是说,对于线段树中的每一个节点,都塞一个排了序的数组(优先队列也是可以的)进去,维护一下,这道题就可以解决啦。(在上面的代码中我用的是结构体)
跳跃(jump.cpp)
事情出奇的顺利,自从高考研讨会过后,z 同学专心准备 noip2010 。可能是被 z 同学的 潇洒、帅气、阳关所吸引,zn 主动约到了 z 同学,就这样,zn 与 z 同学走到了一起~!那是 一次放假,众所周知,hz 的放假,对于不回家的同学就是能走出封闭的 hz 大门好好玩上3 个小时。Zn 刚与 z 同学吃完 kfc,他们来到了衡水唯一还算比较浪漫的地方(貌似叫什么人 民公园吧,ms 了~~)。
题目描述
公园中有许多木桩,每个木桩都有一个高度,活泼的小 z 同学喜欢从一个木桩跳到另 一个木桩上,zn 说太危险了,于是 z 同学让 zn 算出每一次跳跃的危险系数。小 z 每一次跳 跃的范围是一个 k*k 的矩形,危险系数为这个矩形内最高的木桩高度减去最小的。身为 oier, 你能比学数奥的 zn 算的快吗?
输入
第一行三个整数 n(木桩为 n*n 的矩阵)、k、b(小 zz 准备跳跃的次数) 接下来为 n*n 的矩阵,描述木桩的高度
接下来 b 行;每行两个整数 x,y(表示 z 同学跳跃的范围的左上角为第 x 行第 y 列), 保证跳跃范围不超过矩阵的范围
样例数据
样例输入 |
样例输出 |
5 3 1 5 1 2 6 3 1 3 5 2 7 7 2 4 6 1 9 9 8 6 5 0 6 9 3 9 1 2 |
5 |
数据范围
对于30%的数据
0<k<=n<=250 0<b<=100
对于100%的数据
0<k<-n<=250 0<b<=1000 000
STD Code
#include <iostream> #include <cstdio> #include <cstring> using namespace std; int f[310][310][11]; int g[310][310][11]; int N,M,K; int min(int a,int b){return a<b?a:b;} int max(int a,int b){return a>b?a:b;} int log_2(int n){ int t=0; while ((1 << (t+1))<n) t++; return t; } int main(){ memset(f,63,sizeof(f)); memset(g,0,sizeof(g)); scanf("%d%d%d",&N,&M,&K); for (int i=1;i<=N;i++){ for (int j=1;j<=N;j++){ scanf("%d",&f[i][j][0]); g[i][j][0]=f[i][j][0]; } } int w=log_2(M); for (int k=1;k<=w;k++){ for (int i=1;i<=N;i++){ for (int j=1;j<=N;j++){ if (i+(1 << k)-1<=N && j+(1 << k)-1<=N){ f[i][j][k]=min(f[i][j][k-1],f[i+(1 << (k-1))][j][k-1]); f[i][j][k]=min(f[i][j][k],f[i][j+(1 << (k-1))][k-1]); f[i][j][k]=min(f[i][j][k],f[i+(1 << (k-1))][j+(1 << (k-1))][k-1]); g[i][j][k]=max(g[i][j][k-1],g[i+(1 << (k-1))][j][k-1]); g[i][j][k]=max(g[i][j][k],g[i][j+(1 << (k-1))][k-1]); g[i][j][k]=max(g[i][j][k],g[i+(1 << (k-1))][j+(1 << (k-1))][k-1]); } } } } for (int t=1;t<=K;t++){ int i,j; scanf("%d%d",&i,&j); int t1=i+M-1; int t2=j+M-1; int mi,ma; mi=min(f[i][j][w],f[t1-(1 << w)+1][t2-(1 << w)+1][w]); mi=min(mi,f[i][t2-(1 << w)+1][w]); mi=min(mi,f[t1-(1 << w)+1][j][w]); ma=max(g[i][j][w],g[t1-(1 << w)+1][t2-(1 << w)+1][w]); ma=max(ma,g[i][t2-(1 << w)+1][w]); ma=max(ma,g[t1-(1 << w)+1][j][w]); printf("%d ",ma-mi); } return 0; }
题目分析
“最大的”减去“最小的”,这不是一个区间求最值的RMQ问题吗?建一个线段树不就行了?再仔细一看,“n*n的矩阵”,Oh No,这明摆着是要你写一个树套树(矩形树)嘛! 由于此思路的代码非常“绞”,再加上时间不够,最后果断放弃去写第二题的暴力了。
下来后,经过两天的冥思苦想,我终于想出了“还算好写”的矩形树代码,将大矩阵分解成为多个边长为2的小正方形矩阵(即2^k*2^k),在这些小矩阵中做一个RMQ问题求出最值,在随后的数据中,每输入一个矩形,就进行一次“分割为小矩形”的操作,直接比较求最值,交上去,令人感动的AC!
Time:2017-03-25