COCI 2010.03.06 T5「PROGRAM」 题解
洛谷题号P5190,这是学校考试的时候出的一道原题,考场上写了一个自以为是优雅的暴力的做法,结果还a了,效率还较高。事后仔细一想得知原因,才肯定自己写的是正解。。。
先上题面
MIRKO 写了一段程序,并按如下方法运行这个程序。首先开了一个大小为N的数组,并赋值为零。接下来运行他的程序。程序如下:
C代码:
void something( int jump ) {
int i = 0;
while( i < N ) {
seq[i] = seq[i] + 1;
i = i + jump;
}
}
Pascal代码:
procedure something( jump: longint );
var i : longint;
begin
i := 0;
while i < N do
begin
seq[i] := seq[i] + 1;
i := i + jump;
end;
end;
每运行一次代码,他都会传入一个JUMP的值。一共运行了K次。传入的JUMP序列可表示为X1 X2 X3... Xk 。接下来,他通过Q个询问来来测试自己的程序是否正常工作。询问格式如下:L,R,表示输出数组中L到R的和,包括(L,R),你的程序要完成的就是顺次输出这Q的询问的值。
输入:
第一行两个正整数 N (1 ≤ N ≤ 10^6), 表示数组的大小,and K (1 ≤ K ≤ 10^6), 表示运行代码的次数。
第二行K个整数,表示传入的参数值: X1X2X3 ... Xk, (1 ≤ Xi < N).
接下来一行一个整数,Q (1 ≤ Q ≤ 10^6), 表示询问的次数。
接下来的Q行表示询问的边界Li Ri(0 ≤ Li≤ Ri < N)。
输出:
共Q 行,每行一个数,表示对应的和。
样例:
Input:
10 4
1 1 2 1
3
0 9
2 6
7 7
Output:
35
18
3
Input:
11 3
3 7 10
3
0 10
2 6
7 7
Output:
8
2
1
Input:
1000000 6
12 3 21 436 2 19
2
12 16124
692 29021
Output:
16422
28874
样例说明:
样例1传入的参数是 1, 1, 2, 1.
运行后数组为{4, 3, 4, 3, 4, 3, 4, 3, 4, 3}2 to 6 的和为4+3+4+3+4=18.
样例2的数组为。{3, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1}.
分析题面得到思路
首先,这个奇妙的题给了我们一个函数,那么这个函数是干嘛的呢?
先来关注下标i,i初值为0,每次加上jump,说白了i=1jump , 2jump , 3* jump...njump (n jump<给定的N)
说白了就是给seq数组中 下标为小于N的 jump的倍数 的元素的值 增加1(为了防止读者被我绕晕,自行断句)
分析样例得知可能会有相同的数字重复出现的情况,而相同的数字在N以内的倍数是一定的,也就是说会有重复计算拖累时间。而相同的数字是可以统一处理的,只需要在seq数组中的 以相同的那个数字在N以内的倍数为下标的那几个元素 加上那个数字重复出现的次数 (我又来主动断句了), 便能得到和逐个计算相同的效果。(因为计算了n次,那些元素就加了n次,做n次+1为何不直接做一次加n?)然后开一个vis数组记录哪些数字已经被操作过,避免重复计算(第一次操作了那个数就已经相当于是操作了所有重复出现的那个数了,后面的就直接忽略了),数据保证每一个元素都小于等于10^6意味着这个方案是可行的。
因为要询问区间的和,我就第一时间想到了维护前缀和数组,这样比较省时间嘛,构造一个n查询就只有1了。
标程+详细注解
我觉得我的注解是真的写的很良心了。。。
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;//我自己都觉得这个typedef写的很蠢,下面就用过一次longlong,懒得改了
int n,k,q;
int seq[1000005];
ll qzh[1000005];//等价于long long qzh[1000005];
short vis[1000005];//毕竟只有0和1,省点空间。。
int jump[1000005];
int cnt[1000005];
inline int read()//快读,感兴趣的可以记一下,但是确实没几个题会卡你快读
{
char ch=getchar();
int l=0;
while(ch<'0'||ch>'9'){
ch=getchar();
}
while(ch>='0'&&ch<='9'){
l=l*10+ch-'0';
ch=getchar();
}
return l;
}
inline bool comp(int a,int b)//sort的comp函数
{
return a<b;
}
int main()
{
//scanf("%d%d",&n,&k);
n=read(),k=read();//这一句等价于上面那一句,下面的所有带有read函数的都是这样
for(int i=1;i<=k;i++)
{
//scanf("%d",&jump);
jump[i]=read();
cnt[jump[i]]++;//这个就是我提到的记录每一个数字出现了多少次的数组
/* */
}
for(int i=1;i<=k;i++){
if(vis[jump[i]]==0){//VIS数组就是我提到的记录哪些数字已经被操作过,避免重复计算的数组
for(int j=1;j<=n/jump[i];j++){
seq[jump[i]*j]+=cnt[jump[i]];//计算了n次,那些元素就加了n次,做n次+1为何不直接做一次加n
}
vis[jump[i]]=1;//这个数字已经被计算过了,打上标记
}
}
seq[0]=k,qzh[0]=k;//题目小坑,无论是什么数,seq[0]都会被加一次
for(int i=1;i<=n;i++){//前缀和数组构造
qzh[i]=qzh[i-1]+seq[i];
}
// scanf("%d",&q);
q=read();
/* for(int i=0;i<=n;i++){
printf("%d ",seq[i]);
}*/
for(int i=1;i<=q;i++){
int l,r;
// scanf("%d%d",&l,&r);
l=read(),r=read();
if(l==0){
printf("%lld\n",qzh[r]);
continue;
}
printf("%lld\n",qzh[r]-qzh[l-1]);//注意是l-1千万不要写成l了!
}
}
为什么不会TLE
我最开始也是想,这玩意不是少了几次的n^2嘛?能过?
那是因为约数个数性质
O(N+N\2+N\3+......+N\N)=O(Nlog N)
所以这本质上是NlogN级别的,会TLE有鬼叫(嗷嗷嗷~~~~)
写在最后
还是感谢您能看到这里!
最近考试会比较多可能写题解也会比较多啦。。
挖的坑。。可能不打算补了。。