T1:问题 A: Team Queue
题目描述
有n个小组要排成一个队列,每个小组中有若干人。
当一个人来到队列时,如果队列中已经有了自己小组的成员,他就直接插队排在自己小组成员的后面,否则就站在队伍的最后面。
请你编写一个程序,模拟这种小组队列。
输入
输入将包含一个或多个测试用例。
对于每个测试用例,第一行输入小组数量t。
接下来t行,每行输入一个小组描述,第一个数表示这个小组的人数,接下来的数表示这个小组的人的编号。
编号是0到999999范围内的整数。
一个小组最多可包含1000个人。
最后,命令列表如下。 有三种不同的命令:
1、ENQUEUE x - 将编号是x的人插入队列;
2、DEQUEUE - 让整个队列的第一个人出队;
3、STOP - 测试用例结束
每个命令占一行。
当输入用例t=0时,代表停止输入。
需注意:测试用例最多可包含200000(20万)个命令,因此小组队列的实现应该是高效的:
入队和出队都需要使用常数时间。
输出
对于每个测试用例,首先输出一行“Scenario #k”,其中k是测试用例的编号。
然后,对于每个DEQUEUE命令,输出出队的人的编号,每个编号占一行。
在每个测试用例(包括最后一个测试用例)输出完成后,输出一个空行。
样例输入
2
3 101 102 103
3 201 202 203
ENQUEUE 101
ENQUEUE 201
ENQUEUE 102
ENQUEUE 202
ENQUEUE 103
ENQUEUE 203
DEQUEUE
DEQUEUE
DEQUEUE
DEQUEUE
DEQUEUE
DEQUEUE
STOP
2
5 259001 259002 259003 259004 259005
6 260001 260002 260003 260004 260005 260006
ENQUEUE 259001
ENQUEUE 260001
ENQUEUE 259002
ENQUEUE 259003
ENQUEUE 259004
ENQUEUE 259005
DEQUEUE
DEQUEUE
ENQUEUE 260002
ENQUEUE 260003
DEQUEUE
DEQUEUE
DEQUEUE
DEQUEUE
STOP
0
样例输出
Scenario #1
101
102
103
201
202
203
Scenario #2
259001
259002
259003
259004
259005
260001
提示
【数据范围】
1≤t≤1000
题解
对于这道题,先别说多了,注意输出有一个“Scenario #”,不管这道题怎么做,先把这个给搞定了(我因为这个错了n次~~)。现在来细看本题,很容易想到队列的知识,只是有人可以插队就有点不好整。对于一个队列来说,突然插一个人是很难实现的。但是本题也有提示,即一个人只会插队到他自己的组,否则就直接队尾。因此可以大胆想象,能不能建一个2维队列,q[0]表示“队”进了多少以及顺序是什么,而从q[1到n]则表示每一组中的内部顺序,每次出队就是把q[0]的队首的队首出队,如果队列为空就把q[0]中的当前队首出队。入队插队也就很容易实现了。
参考代码
#include<cstdio>
#include<queue>
using namespace std;
int n,num[10000001];
queue<int>q[2000];
char s[200];int tot=0;
int main()
{
while(1)
{
scanf("%d",&n);
if(n==0) break;
if(tot>0)
printf("\n");
tot++;
printf("Scenario #%d\n",tot);
for(int i=1;i<=n;i++)
{
int p;
scanf("%d",&p);
for(int j=1;j<=p;j++)
{
int k;
scanf("%d",&k);
num[k]=i;
}
}
while(1)
{
scanf("%s",s);
if(s[0]=='S') break;
if(s[0]=='E')
{
int nus;scanf("%d",&nus);
if(!q[num[nus]].size())
q[0].push(num[nus]);
q[num[nus]].push(nus);
}
else if(s[0]=='D')
{
int ans=q[q[0].front()].front();
printf("%d\n",ans);
q[q[0].front()].pop();
if(!q[q[0].front()].size())
q[0].pop();
}
}
while(q[0].size())
{
while(q[q[0].front()].size())
q[q[0].front()].pop();
q[0].pop();
}
}
return 0;
}
T2:问题 B: 绝对值最小和
题目描述
给出n个数a1,a2, ..., an,求两个数相加的绝对值的最小值,即求|ai+aj|的最小值,其中i不等于j。
输入
第1行1个正整数n。
接下来1行n个整数,表示a1,a2,..., an,每两个数之间用一个空格隔开。
输出
一行一个整数,表示答案。
样例输入
5
-2 6 7 7 -8
样例输出
1
提示
【数据规模】
对于40%的数据满足:n≤103,-106≤ai≤106。
对于80%的数据满足:n≤105,-106≤ai≤106。
对于100%的数据满足:n≤106,-106≤ai≤106。
题解
这道题的暴力分还是可以,加点随机可能就能过5、6个点了。当然,作为正义的使者,还是看看正解。
先来考虑特殊情况。全是负数(或全是正数),此时排个序就可以解决(建议桶排,保险)。那么剩下的一定就是既有负数又有正数。毫无疑问,答案一定是一正一负的结合,关键在于如何找,还要高效的找!其实可以这么考虑:先不考虑符号,然后升序排序,得到一个线性表,取出中间任意一个数,能够有机会作为答案的条件为:与左边最后一个符号相反的相减或者与右边第一个符号相反的数相反,一定是包含这个数的最优解。显而易见吧,左边第二个符号相反的数绝对值会小一些,那么相加出来的答案会大一些(平衡的少了),右边也是这样。
总的来说,这样找到2个数绝对值相近且符号相反,就可能是答案。因此从头扫描一遍,如果找到了负数就看前面编号最大的正数和后边编号最小的正数,如果是正数就相反。因此就把问题转化为,对于一个点,如何找到前面最后一个正数(负数)的编号(如果只往前找,完全可以不扫2遍,也可以保证答案正确,后面的点总有机会在往前找的时候找到当前这个点),因此用2个变量存一下就可以了(本人比较笨,考试的时候用的树状数组维护这个,骗分),这样就可以O(n)扫描解决答案。(这里就给出树状数组的那种,更有意义,树状数组达到的效果是双向找)。
参考代码
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct node
{
int val,op;
}a[1000001];
int n,c[1000001],min1=999999999;
int abs1(int p) { return p<0?-p:p; }
bool comp1(node p,node q)
{ return p.val<q.val; }
void add(int t,int k)
{
for(;t<=1000000;t+=t&-t) c[t]+=k;
}
int query(int t)
{
int ret=0;
for(;t;t-=t&-t) ret+=c[t];
return ret;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int k;
scanf("%d",&k);
a[i].op=1;
if(k<0)
{
a[i].op=0;
k=-k;
}
a[i].val=k;
}
sort(a+1,a+n+1,comp1);
for(int i=1;i<=n;i++)
if(a[i].op==1) add(i,i-query(i));
for(int i=1;i<=n;i++)
if(a[i].op==0)
{
int nus=query(i);
if(nus)
if(min1>abs1(a[nus].val-a[i].val))
min1=abs1(a[nus].val-a[i].val);
}
memset(c,0,sizeof(c));
for(int i=1;i<=n;i++)
if(a[i].op==0) add(i,i-query(i));
for(int i=1;i<=n;i++)
if(a[i].op==1)
{
int nus=query(i);
if(nus)
if(min1>abs1(a[i].val-a[nus].val))
min1=abs1(a[i].val-a[nus].val);
}
if(min1==999999999||abs1(a[1].val+a[2].val)<min1)
printf("%d",abs1(a[1].val+a[2].val));
else printf("%d",min1);
return 0;
}
T3:问题 C: Power Strings
题目描述
给定若干个长度 ≤106 的字符串,询问每个字符串最多是由多少个相同的子字符串重复连接而成的。如:ababab 则最多有 3 个 ab 连接而成。
输入
输入若干行,每行有一个字符串,字符串仅含英语字母。特别的,字符串可能为 . 即一个半角句号,此时输入结束。
输出
若干行,每行一个整数,对应一个字符串。
样例输入
abcd
aaaa
ababab
.
样例输出
1
4
3
题解
我怀疑,这道题的数据可能有点水……我这解法纯暴力,优化都没优化,竟然就全A了。首先能够由重复的字符串拼接,必然这个“重复的字符串”的长度是总长度的因数,因此直接O(n)枚举因数,然后以第一组重复的字符串作为基准往后扫描,每次都是O(n)的,但是可能由于每次的非答案都无法达到比较大的数量级就退出了,才A的。特别注意一点,如果枚举的i已经大于n/2了,直接输出1就可以了。
参考代码
#include<cstdio>
#include<cstring>
using namespace std;
char s[1000001];
int len;
bool figure(int k)
{
for(int i=k;i<len;i++)
if(s[i]!=s[i%k]) return false;
return true;
}
int main()
{
while(1)
{
scanf("%s",s);
len=strlen(s);
if(s[0]=='.') break;
for(int i=1;i<=len;i++)
{
if(len%i!=0) continue;
if(figure(i))
{
printf("%d\n",len/i);
break;
}
}
}
return 0;
}
T4:问题 D: 查字典
题目描述
小明正在复习全国英语四级考试,他手里有一本词典, 现在有很多单词要查。请编写程序帮助他快速找到要查的单词所在的页码。
输入
第1行1个正整数N,N≤10000,表示字典中一共有多少单词。
接下来每两行表示一个单词,其中:
第1行是一个长度小于或等于100的字符串,表示这个单词,全部小写字母,单词不会重复。
第2行是1个整数,表示这个单词在字典中的页码。
接下来的一行是1个整数M,M≤10000,表示要查的单词数。
接下来的M行,每行一个字符串,表示要查的单词,保证在字典中存在。
输出
M行,每行一个正整数,表示第i个单词在字典中的页码。
样例输入
2
scan
10
word
15
2
scan
word
样例输出
10
15
题解
听说这道题标程是哈希映射,也很简单。但是字典树板子题明显更简单呗。只不过在字典树上的单词末尾存一下页数,然后直接访问就可以了。总的来说,就是考字典树,只不过要改一改。见代码:
参考代码
#include<cstdio>
#include<cstring>
using namespace std;
struct trie
{
int son[27];
int num;
}a[1000001];
int n,m;
char x[20000];
int t=0,p,len;
void make_tree(int k)
{
p=0;len=strlen(x);
for(int i=0;i<len;i++)
{
if(!a[p].son[x[i]-'a']) a[p].son[x[i]-'a']=++t;
p=a[p].son[x[i]-'a'];
if(i==len-1) a[p].num=k;
}
}
int search()
{
p=0;len=strlen(x);
for(int i=0;i<len;i++)
{
p=a[p].son[x[i]-'a'];
if(i==len-1) return a[p].num;
}
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%s",x);
int t;scanf("%d",&t);
make_tree(t);
}
scanf("%d",&m);
while(m--)
{
scanf("%s",x);
printf("%d\n",search());
}
return 0;
}