目前我和渣诚基本确定了这样的分工,那就是他来写书上代码构造成的标程,我来写用STL或者其他方法实现的标程。说实话,作为一个渣渣ACMer,这种感觉挺好……
言归正传。第一次上机成绩非常惨烈,一方面我和渣诚在题目顺序的安排上出了点问题,把最基础的链表操作题目放在了最后,题干最吓人(注意不是最难的)放在了第一道。经过被关导叫去喝茶以及与老师的沟通之后,以后的上机会尽量精简题干,数据量的要求也会降低;难度会略有降低,但不会有大的变化,会更讲求难度梯度的设置。此外练习题难度将高于上机题。
这次上机主要考查了顺序表与链表的基本操作。
A. RPS GAMES
这道题看起来吓人,但操作却很基础。虽然考查了顺性表的操作,但因为有大量的插入和删除节点的操作,用链表也许是更好的方法;以下提供的依然是顺序表的标程。
对于手势相克的理解,其实就是找到圆上两个手势的劣弧,逆时针相克。
#include<cstdio>
#include<cstdlib>
using namespace std;
struct SqList
{
int data[3005],length;
};
void CreateList(SqList *&L,int n)
{
L=(SqList *)malloc(sizeof(SqList));
for(int i=0; i<n; ++i)
scanf("%d",&L->data[i]);
L->length=n;
}
bool Insert(SqList *&L,int x,int y)
{
if(x<1||x>L->length+1)
return false;
--x;
for(int i=L->length; i>x; --i)
L->data[i]=L->data[i-1];
L->data[x]=y;
++L->length;
return true;
}
bool Delete(SqList *&L,int x)
{
int t=0;
while(t<L->length&&L->data[t]!=x)
++t;
if(t>=L->length)
return false;
for(int i=t; i<L->length-1; ++i)
L->data[i]=L->data[i+1];
--L->length;
return true;
}
int Check(SqList *L,int x,int y)
{
int a=0,b=0;
for(int i=0; i<L->length; ++i)
{
if(L->data[i]==x)
a=i+1;
if(L->data[i]==y)
b=i+1;
}
if(a==0||b==0)
return 0;
if(a==b)
return 1;
if((a-b+L->length)%L->length<(b-a+L->length)%L->length)
return 3;
return 2;
}
void DispList(SqList *L)
{
for(int i=0; i<L->length; ++i)
printf("%d ",L->data[i]);
putchar('\n');
}
const char s[4][10]= {"ERROR","TIE","LOSE","WIN"};
int main()
{
int n,m,x,y;
char order[10];
while(~scanf("%d",&n))
{
SqList *L;
CreateList(L,n);
scanf("%d",&m);
while(m--)
{
scanf("%s%d",order,&x);
switch(order[0])
{
case 'I':
scanf("%d",&y);
puts(Insert(L,x,y)?"YEAH":"ERROR");
break;
case 'D':
puts(Delete(L,x)?"YEAH":"ERROR");
break;
case 'C':
scanf("%d",&y);
puts(s[Check(L,x,y)]);
break;
}
}
DispList(L);
free(L);
}
}
#include<cstdio>
#include<vector>
using namespace std;
vector<int> data;
vector<int>::iterator it;
bool Insert(int x,int y)
{
if(x<1||x>data.size()+1)
return false;
data.insert(data.begin()+x-1,y);
return true;
}
bool Delete(int x)
{
for(it=data.begin(); it!=data.end(); ++it)
if(*it==x)
{
data.erase(it);
return true;
}
return false;
}
int Check(int x,int y)
{
int a=0,b=0,l=data.size();
for(int i=0; i<l; ++i)
{
if(data[i]==x)
a=i+1;
if(data[i]==y)
b=i+1;
}
if(a==0||b==0)
return 0;
if(a==b)
return 1;
if((a-b+l)%l<(b-a+l)%l)
return 3;
return 2;
}
const char s[4][10]= {"ERROR","TIE","LOSE","WIN"};
int main()
{
int n,m,x,y;
char order[10];
data.reserve(3005);
while(~scanf("%d",&n))
{
while(n--)
{
scanf("%d",&x);
data.push_back(x);
}
scanf("%d",&m);
while(m--)
{
scanf("%s%d",order,&x);
switch(order[0])
{
case 'I':
scanf("%d",&y);
puts(Insert(x,y)?"YEAH":"ERROR");
break;
case 'D':
puts(Delete(x)?"YEAH":"ERROR");
break;
case 'C':
scanf("%d",&y);
puts(s[Check(L,x,y)]);
break;
}
}
for(it=data.begin(); it!=data.end(); ++it)
printf("%d ",*it);
putchar('\n');
data.clear();
}
}
B. Disappeared
最简单的思路,是排序,然后统计每个编号出现的次数,如果是奇数次则为答案。统计过程是O(n)的,排序如果用O(n^2)的能得到0.6分,用O(nlogn)的能得到0.8分。
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=100005;
int child[MAXN];
int main()
{
int n;
while(~scanf("%d",&n))
{
for(int i=0; i<n; ++i)
scanf("%d",&child[i]);
sort(child,child+n);
int cnt=1,ans;
for(int i=1; i<n; ++i)
if(child[i]==child[i-1])
++cnt;
else if(cnt&1)
{
ans=child[i-1];
break;
}
else
cnt=1;
printf("%d\n",ans);
}
}
此外还可以利用map做为hash,直接记录每个数字出现的次数(如果直接开数组记录会RE);map的内部构造是一种平衡二叉树(红黑树),目前并不推荐童鞋们使用。总体复杂度同样为O(nlogn),能得到0.8分。
#include<cstdio>
#include<map>
using namespace std;
map<int,int> child;
map<int,int>::iterator it;
int main()
{
int n;
while(~scanf("%d",&n))
{
int tmp;
for(int i=0; i<n; ++i)
{
scanf("%d",&tmp);
++child[tmp];
}
for(it=child.begin(); it!=child.end(); ++it)
if(it->second&1)
{
printf("%d\n",it->first);
break;
}
child.clear();
}
}
满分用到的是位运算中的异或运算,异或运算满足交换性是前提。这种做法也只适用于有且只有一个编号出现奇数次的情况下。
#include<cstdio>
using namespace std;
int main()
{
int n;
while(~scanf("%d",&n))
{
int ans=0,tmp;
while(n--)
{
scanf("%d",&tmp);
ans^=tmp;
}
printf("%d\n",ans);
}
}
C. 成绩统计
这个题考查的是多个有序数列的归并,没有规定数据结构。事实上这道题最水的办法是直接读入全体序列然后重排序,复杂度O(nlogn),也确实有很多人在禁用了sort之后手写了归并排序或者快排,事实上也要比一个个序列归并要快。考虑到归并排序中也考查了归并操作,所以也就不深究了。
//by trashLHC
#include<iostream>
#include<cstdio>
#include<cstdlib>
#define MAXMAX 100001
#define MAXN 101
using namespace std;
typedef struct{
int *data;
int length;
}SqList;
SqList *LA,*LB,*LT;
void InitList(int marker){
if(marker==1){
LA=new SqList;
LA->data=new int[MAXMAX];
LA->length=0;
}
else{
LT=new SqList;
LT->data=new int[MAXMAX];
LT->length=0;
}
}
void CreateList(int n){
LB=new SqList;
LB->data=new int[MAXN];
for(int i=0;i<n;i++)
scanf("%d",&LB->data[i]);
LB->length=n;
}
void DestroyList(int marker){
switch(marker){
case 1:
free(LA->data);
free(LA);
break;
case 2:
free(LB->data);
free(LB);
break;
case 3:
free(LT->data);
free(LT);
break;
}
}
void UnionList(){
int i=0,j=0,k=0;
InitList(2);
while(i<LA->length&&j<LB->length){
if(LA->data[i]<LB->data[j])
LT->data[k++]=LA->data[i++];
else
LT->data[k++]=LB->data[j++];
}
while(i<LA->length)
LT->data[k++]=LA->data[i++];
while(j<LB->length)
LT->data[k++]=LB->data[j++];
for(int l=0;l<k;l++)
LA->data[l]=LT->data[l];
LA->length=k;
DestroyList(2);
DestroyList(3);
}
void DispList(){
for(int i=0;i<LA->length;i++)
printf("%d ",LA->data[i]);
printf("\n");
}
int main(){
int n,m;
while(~scanf("%d",&n)){
InitList(1);
for(int i=0;i<n;i++){
scanf("%d",&m);
CreateList(m);
UnionList();
}
DispList();
DestroyList(1);
}
}
然后这道题实在可以用很多种数据结构来实现,所以我非常无聊地用STL写了四种,其中三种都会TLE,但不是因为复杂度,而是因为STL比较慢。随便看看得了,意义不大。
//by wjfwzzc
//using vector
#include<cstdio>
#include<vector>
using namespace std;
vector<int> a,b,tmp;
vector<int>::iterator p,q;
int main()
{
int n,m,x;
while(~scanf("%d",&n))
{
while(n--)
{
scanf("%d",&m);
while(m--)
{
scanf("%d",&x);
b.push_back(x);
}
p=a.begin();
q=b.begin();
while(p!=a.end()||q!=b.end())
if(p==a.end()||(q!=b.end()&&*p>*q))
tmp.push_back(*(q++));
else
tmp.push_back(*(p++));
a=tmp;
b.clear();
tmp.clear();
}
for(p=a.begin(); p!=a.end(); ++p)
printf("%d ",*p);
putchar('\n');
a.clear();
}
}
//by wjfwzzc
//using list
#include<cstdio>
#include<list>
using namespace std;
list<int> ans,tmp;
list<int>::iterator p,q;
int main()
{
int n,m,x;
while(~scanf("%d",&n))
{
while(n--)
{
scanf("%d",&m);
while(m--)
{
scanf("%d",&x);
tmp.push_back(x);
}
for(p=ans.begin(),q=tmp.begin(); q!=tmp.end(); ++p)
if(p==ans.end())
ans.push_back(*(q++));
else if(*p>=*q)
ans.insert(p--,*(q++));
tmp.clear();
}
for(p=ans.begin(); p!=ans.end(); ++p)
printf("%d ",*p);
putchar('\n');
ans.clear();
}
}
//by wjfwzzc
//using queue
#include<cstdio>
#include<queue>
using namespace std;
queue<int> a,b,tmp;
int main()
{
int n,m,x;
while(~scanf("%d",&n))
{
while(n--)
{
scanf("%d",&m);
while(m--)
{
scanf("%d",&x);
b.push(x);
}
while(!a.empty()||!b.empty())
if(a.empty()||(!b.empty()&&a.front()>b.front()))
{
tmp.push(b.front());
b.pop();
}
else
{
tmp.push(a.front());
a.pop();
}
a=tmp;
while(!tmp.empty())
tmp.pop();
}
while(!a.empty())
{
printf("%d ",a.front());
a.pop();
}
putchar('\n');
}
}
//by wjfwzzc
//using list+queue
#include<cstdio>
#include<list>
#include<queue>
using namespace std;
list<int> ans;
list<int>::iterator it;
queue<int> que;
int main()
{
int n,m,tmp;
while(~scanf("%d",&n))
{
while(n--)
{
scanf("%d",&m);
while(m--)
{
scanf("%d",&tmp);
que.push(tmp);
}
for(it=ans.begin(); !que.empty(); ++it)
{
tmp=que.front();
if(it==ans.end())
{
ans.push_back(tmp);
que.pop();
}
else if(*it>=tmp)
{
ans.insert(it--,tmp);
que.pop();
}
}
}
for(it=ans.begin(); it!=ans.end(); ++it)
printf("%d ",*it);
putchar('\n');
ans.clear();
}
}
D. “码农”的数据列
陈题,看中它的一大原因是这题是彻头彻尾的链表题。几种操作,唯一要注意的是,对于指向两个节点中间的“手”,我用两个指向其位置右侧节点的指针表示,此时假如“手”R指向最后一个节点的右侧,则指针R指向NULL;也可用指向“手”所在位置左侧节点的指针表示,这样指向头节点的指针表示位置1。
#include<cstdio>
#include<cstdlib>
#include<cstring>
using namespace std;
struct DLinkList
{
int data;
struct DLinkList *prior,*next;
} *L,*R;
void CreateList(DLinkList *&List,int n)
{
DLinkList *s,*r;
List=(DLinkList *)malloc(sizeof(DLinkList));
r=List;
for(int i=0; i<n; ++i)
{
s=(DLinkList *)malloc(sizeof(DLinkList));
scanf("%d",&s->data);
r->next=s;
s->prior=r;
r=s;
}
r->next=NULL;
}
void InitList(DLinkList *&List,int l,int r)
{
L=R=List;
for(int i=0; i<l; ++i)
L=L->next;
for(int i=0; i<r; ++i)
R=R->next;
}
void MoveLeft(char c)
{
switch(c)
{
case 'L':
L=L->prior;
break;
case 'R':
R=R->prior;
break;
}
}
void MoveRight(char c)
{
switch(c)
{
case 'L':
L=L->next;
break;
case 'R':
R=R->next;
break;
}
}
void Insert(DLinkList *&List,char c,int x)
{
DLinkList *p;
p=(DLinkList *)malloc(sizeof(DLinkList));
p->data=x;
switch(c)
{
case 'L':
p->prior=L->prior;
L->prior->next=p;
p->next=L;
L->prior=p;
L=p;
break;
case 'R':
p->prior=R->prior;
R->prior->next=p;
p->next=R;
R->prior=p;
break;
}
}
void Delete(DLinkList *&List,char c)
{
DLinkList *p;
switch(c)
{
case 'L':
L=L->prior;
p=L->next;
L->next=p->next;
L->next->prior=L;
free(p);
L=L->next;
break;
case 'R':
p=R->prior;
R->prior=p->prior;
R->prior->next=R;
free(p);
break;
}
}
void DispList(DLinkList *List)
{
while(L!=R)
{
printf("%d ",L->data);
L=L->next;
}
putchar('\n');
}
void DestroyList(DLinkList *&List)
{
DLinkList *pre=L,*p=List->next;
while(p)
{
free(pre);
pre=p;
p=pre->next;
}
free(pre);
}
int main()
{
int t,cas=0,n,l,r,m,parameter;
char instruct[15],hand;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
DLinkList *List;
CreateList(List,n);
scanf("%d%d%d",&l,&r,&m);
InitList(List,l,r);
while(m--)
{
scanf("%s %c",instruct,&hand);
if(strcmp(instruct,"MoveLeft")==0)
MoveLeft(hand);
else if(strcmp(instruct,"MoveRight")==0)
MoveRight(hand);
else if(strcmp(instruct,"Insert")==0)
{
scanf("%d",¶meter);
Insert(List,hand,parameter);
}
else if(strcmp(instruct,"Delete")==0)
Delete(List,hand);
}
printf("Case %d:\n",++cas);
DispList(List);
DestroyList(List);
}
}
E. Arthur的收藏夹
无非是给两个集合A和B,求A-B。排序,然后有些类似于归并的操作,只不过只取A集合存在但B集合不存在的元素罢了。
//by trashLHC
#include<iostream>
#include<fstream>
#include<ctime>
#include<cmath>
#include<algorithm>
#include<cstdio>
#include<cstdlib>
#define MAX 100001
using namespace std;
typedef struct{
int data[MAX];
int length;
}SqList;
void InitList(SqList *&L){
L=new SqList;
L->length=0;
}
void CreateList(SqList *&L,int n){
L=new SqList;
for(int i=0;i<n;i++)
scanf("%d",&L->data[i]);
L->length=n;
}
void DispList(SqList *L){
if(L->length==0){
printf("LOSER!\n");
return;
}
printf("%d\n",L->length);
for(int i=0;i<L->length;i++)
printf("%d ",L->data[i]);
printf("\n");
return;
}
void DestroyList(SqList *&L){
free(L);
}
void Subtraction(SqList *&LA,SqList *&LB){
int i=0,j=0,k=0;
SqList *L;InitList(L);
sort(LA->data,LA->data+LA->length);
sort(LB->data,LB->data+LB->length);
while(i<LA->length&&j<LB->length){
if(LA->data[i]<LB->data[j])
L->data[L->length++]=LA->data[i++];
else if(LA->data[i]==LB->data[j])
i++;
else
j++;
}
while(i<LA->length)
L->data[L->length++]=LA->data[i++];
DispList(L);
DestroyList(L);
DestroyList(LA);
DestroyList(LB);
}
int main(){
int t,m,n;
scanf("%d",&t);
while(t--){
SqList *LA,*LB;
scanf("%d%d",&m,&n);
CreateList(LA,m);CreateList(LB,n);
Subtraction(LA,LB);
}
}
//by wjfwzzc
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
vector<int> a,b,ans;
vector<int>::iterator p,q;
int main()
{
int t,m,n,tmp;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&m,&n);
a.reserve(m);
b.reserve(n);
while(m--)
{
scanf("%d",&tmp);
a.push_back(tmp);
}
while(n--)
{
scanf("%d",&tmp);
b.push_back(tmp);
}
sort(a.begin(),a.end());
sort(b.begin(),b.end());
p=a.begin();
q=b.begin();
while(p!=a.end())
if(q==b.end()||*p<*q)
ans.push_back(*(p++));
else if(*p==*q)
{
++p;
++q;
}
else
++q;
if(ans.size()>0)
{
printf("%d\n",ans.size());
for(int i=0; i<ans.size(); ++i)
printf("%d ",ans[i]);
putchar('\n');
}
else
puts("LOSER!");
a.clear();
b.clear();
ans.clear();
}
}
也可以对B集合排序,然后针对每个A集合中的元素,在B集合中二分查找。
//by wjfwzzc
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
vector<int> a,b,ans;
vector<int>::iterator p;
int main()
{
int t,m,n,tmp;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&m,&n);
a.reserve(m);
b.reserve(n);
while(m--)
{
scanf("%d",&tmp);
a.push_back(tmp);
}
while(n--)
{
scanf("%d",&tmp);
b.push_back(tmp);
}
sort(b.begin(),b.end());
for(p=a.begin(); p!=a.end(); ++p)
if(!binary_search(b.begin(),b.end(),*p))
ans.push_back(*p);
sort(ans.begin(),ans.end());
if(ans.size()>0)
{
printf("%d\n",ans.size());
for(int i=0; i<ans.size(); ++i)
printf("%d ",ans[i]);
putchar('\n');
}
else
puts("LOSER!");
a.clear();
b.clear();
ans.clear();
}
}
也可以用map来记录。
//by wjfwzzc
#include<cstdio>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
vector<int> a,ans;
vector<int>::iterator it;
map<int,bool> b;
int main()
{
int t,m,n,tmp;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&m,&n);
a.reserve(m);
while(m--)
{
scanf("%d",&tmp);
a.push_back(tmp);
}
while(n--)
{
scanf("%d",&tmp);
b[tmp]=true;
}
for(it=a.begin(); it!=a.end(); ++it)
if(!b[*it])
ans.push_back(*it);
sort(ans.begin(),ans.end());
if(ans.size()>0)
{
printf("%d\n",ans.size());
for(int i=0; i<ans.size(); ++i)
printf("%d ",ans[i]);
putchar('\n');
}
else
puts("LOSER!");
a.clear();
b.clear();
ans.clear();
}
}
F. Chem is A Brand New Try!
这是一道好题,给两个序列,进行任意次的子段交换操作,最终输出两个序列。数据量故意给得看不出用什么数据结构更好,但显然用链表可以做得很漂亮。这里有个很重要的地方是对指针的理解,如果足够透彻的话,应该明白我们要做的就是交换子段首尾的指针。这样写出来的程序就会很易懂很优雅。
#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
struct LinkList
{
int data;
struct LinkList *next;
};
void CreateList(LinkList *&L,int n)
{
LinkList *s,*r;
L=(LinkList *)malloc(sizeof(LinkList));
r=L;
while(n--)
{
s=(LinkList *)malloc(sizeof(LinkList));
scanf("%d",&s->data);
r->next=s;
r=s;
}
r->next=NULL;
}
void DispList(LinkList *L)
{
LinkList *p=L->next;
while(p)
{
printf("%d ",p->data);
p=p->next;
}
putchar('\n');
}
void DestroyList(LinkList *&L)
{
LinkList *pre=L,*p=L->next;
while(p)
{
free(pre);
pre=p;
p=pre->next;
}
free(pre);
}
int main()
{
int m,n,k,l1,r1,l2,r2;
while(~scanf("%d%d%d",&m,&n,&k))
{
LinkList *L1,*L2,*p,*q;
CreateList(L1,m);
CreateList(L2,n);
while(k--)
{
scanf("%d%d%d%d",&l1,&r1,&l2,&r2);
p=L1;
q=L2;
for(int i=1; i<l1; ++i)
p=p->next;
for(int i=1; i<l2; ++i)
q=q->next;
swap(p->next,q->next);
for(int i=l1; i<=r1; ++i)
q=q->next;
for(int i=l2; i<=r2; ++i)
p=p->next;
swap(p->next,q->next);
}
DispList(L1);
DispList(L2);
DestroyList(L1);
DestroyList(L2);
}
}
G. 越是简单的题目标题就要越长才能表明诚意阿鲁!
这道题,诚意十足……没什么好说的,把删除一个节点改写成删除一段连续的节点,数据又没有任何track。
//by trashLHC
#include<cstdio>
#include<cstdlib>
typedef struct node{
int data;
node *next;
}LinkList;
void CreateList(LinkList *&L,int n){
LinkList *p,*q;
L=new LinkList;
q=L;
for(int i=0;i<n;i++){
p=new LinkList;
scanf("%d",&p->data);
q->next=p;
q=p;
}
q->next=NULL;
}
void DeleteSubList(LinkList *&L,int l,int r){
LinkList *p=L,*q,*s;
for(int i=1;i<l;i++)
p=p->next;
s=q=p->next;
for(int i=l;i<=r;i++){
q=s->next;
free(s);
s=q;
}
p->next=q;
}
void DispList(LinkList *L){
LinkList *p=L->next;
while(p!=NULL){
printf("%d ",p->data);
p=p->next;
}
printf("\n");
}
void DestroyList(LinkList *&L){
LinkList *p,*pre=L;
do{
p=pre->next;
free(pre);
pre=p;
}while(p!=NULL);
}
int main(){
int n,l,r;
while(~scanf("%d",&n)){
LinkList *LA;
CreateList(LA,n);
scanf("%d%d",&l,&r);
DeleteSubList(LA,l,r);
DispList(LA);
DestroyList(LA);
}
}
//by wjfwzzc
#include<cstdio>
#include<list>
using namespace std;
list<int> data;
list<int>::iterator it;
int main()
{
int n,tmp,l,r;
while(~scanf("%d",&n))
{
while(n--)
{
scanf("%d",&tmp);
data.push_back(tmp);
}
scanf("%d%d",&l,&r);
it=data.begin();
for(int i=1; i<l; ++i)
++it;
for(int i=l; i<=r; ++i)
data.erase(it++);
for(it=data.begin(); it!=data.end(); ++it)
printf("%d ",*it);
putchar('\n');
data.clear();
}
}
最后想说,题目其实都没有超出书上的范围,只要一个理解并熟练掌握了顺序表和链表的基本操作的人,艹掉4题应该不是很困难的事情。如果做不到,请思考自己是否做到了我所说的前提:“理解并掌握”。
此外,数据结构上机的普遍现象是,代码量相较于上学期的C++上机多了很多,这是不可避免的。所以助教虽然不推荐,但也不反对携带电子模板。但注意,只限于平时上机,期末上机是严禁电子模板的。