首页 > 代码库 > [通过一题论优化的重要性](原)

[通过一题论优化的重要性](原)

忽然发现优化这东西特别的美妙!~~~

先给出一个洛谷OnlineJudge上的题目:

P1903 【模板】分块/带修改莫队(数颜色)

题目描述

墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问。墨墨会像你发布如下指令:

1、 Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔。

2、 R P Col 把第P支画笔替换为颜色Col。

为了满足墨墨的要求,你知道你需要干什么了吗?

输入输出格式

输入格式:

 

第1行两个整数N,M,分别代表初始画笔的数量以及墨墨会做的事情的个数。

第2行N个整数,分别代表初始画笔排中第i支画笔的颜色。

第3行到第2+M行,每行分别代表墨墨会做的一件事情,格式见题干部分。

 

输出格式:

 

对于每一个Query的询问,你需要在对应的行中给出一个数字,代表第L支画笔到第R支画笔中共有几种不同颜色的画笔。

 

输入输出样例

输入样例#1:
6 5
1 2 3 4 5 5
Q 1 4
Q 2 6
R 1 2
Q 1 4
Q 2 6
输出样例#1:
4
4
3
4

说明

对于100%的数据,N≤10000,M≤10000,修改操作不多于1000次,所有的输入数据中出现的所有整数均大于等于1且不超过10^6。

来源:bzoj2120

本题数据为洛谷自造数据,使用CYaRon耗时5分钟完成数据制作。

 

这题是莫队模板题?本蒟蒻开始表示很方......后来看看dalao们都是用到带修改莫队,特别是看到了余能dalao......

然而我并不想去学这个东西(太懒了=_=),所以就有了一个依然是离线的想法:

由于题目中已经说了,修改操作不多于1000次,本蒟蒻也就抓住了这个点.我以每次修改将其余的询问分隔开,然后对于每一段内的询问,然后很暴力地写一个莫队+分块.

时间复杂度的话,本蒟蒻也不会算了,大概就在10^8~10^9之间徘徊吧...结果TLE40,据说暴力都有50TAT...

技术分享
 1 #include<cstdio>
 2 #include<cstring>
 3 #include<algorithm>
 4 #include<cmath>
 5 using namespace std;
 6 int n,Q,K,blocks;
 7 int cnt[10005],ans;
 8 struct query{int L,R,tp,index,ans;}a[10005];
 9 struct data{int x,index;}c[10005],cc[10005];
10 inline int read(){
11     int x=0; char ch=getchar();
12     while (ch<0||ch>9) ch=getchar();
13     while (ch>=0&&ch<=9) x=x*10+ch-0,ch=getchar();
14     return x;
15 }
16 bool cmp_x(data u,data v){return u.x<v.x;}
17 bool cmp_index(data u,data v){return u.index<v.index;}
18 bool cmp_blocks(query u,query v){return u.L/blocks==v.L/blocks?u.R<v.R:u.L<v.L;}
19 bool cmp_id(query u,query v){return u.index<v.index;}
20 void remove(int p){cnt[cc[p].x]--,ans-=cnt[cc[p].x]==0;}
21 void add(int p){cnt[cc[p].x]++,ans+=cnt[cc[p].x]==1;}
22 int main(){
23     n=read(),Q=read(),blocks=(int)sqrt(n);
24     for (int i=1; i<=n; i++) c[i].x=read(),c[i].index=i;
25     char s[5]; for (int i=1; i<=Q; i++){scanf("%s",s); a[i].tp=s[0]==R,a[i].L=read(),a[i].R=read(),a[i].index=i;} a[++Q].tp=1;
26     int las=1;
27     for (int K=1; K<=Q; K++) if (a[K].tp){
28         sort(c+1,c+1+n,cmp_x);
29         int cntnew=1;
30         memset(cc,0,sizeof cc),cc[1].x=1;
31         for (int i=2; i<=n; i++) if (c[i].x==c[i-1].x) cc[i].x=cntnew; else cc[i].x=++cntnew;
32         for (int i=1; i<=n; i++) cc[i].index=c[i].index;
33         sort(c+1,c+1+n,cmp_index);
34         sort(cc+1,cc+1+n,cmp_index);
35         
36         sort(a+las,a+1+K-1,cmp_blocks);
37         int curL=1,curR=0;
38         memset(cnt,0,sizeof cnt); ans=0;
39         for (int i=las; i<K; i++){
40             while (curL<a[i].L) remove(curL++);
41             while (curR>a[i].R) remove(curR--);
42             while (curL>a[i].L) add(--curL);
43             while (curR<a[i].R) add(++curR);
44             a[i].ans=ans;
45         }
46         sort(a+las,a+1+K-1,cmp_id);
47         for (int i=las; i<K; i++) printf("%d\n",a[i].ans);
48         las=K+1,c[a[K].L].x=a[K].R;
49     }
50     return 0;
51 }
View Code

然后又表脸地加了O3优化,快了一些,依然TLE40.

技术分享
 1 %:pragma GCC optimize(3)
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cmath>
 6 using namespace std;
 7 int n,Q,K,blocks;
 8 int cnt[10005],ans;
 9 struct query{int L,R,tp,index,ans;}a[10005];
10 struct data{int x,index;}c[10005],cc[10005];
11 inline int read(){
12     int x=0; char ch=getchar();
13     while (ch<0||ch>9) ch=getchar();
14     while (ch>=0&&ch<=9) x=x*10+ch-0,ch=getchar();
15     return x;
16 }
17 bool cmp_x(data u,data v){return u.x<v.x;}
18 bool cmp_index(data u,data v){return u.index<v.index;}
19 bool cmp_blocks(query u,query v){return u.L/blocks==v.L/blocks?u.R<v.R:u.L<v.L;}
20 bool cmp_id(query u,query v){return u.index<v.index;}
21 void remove(int p){cnt[cc[p].x]--,ans-=cnt[cc[p].x]==0;}
22 void add(int p){cnt[cc[p].x]++,ans+=cnt[cc[p].x]==1;}
23 int main(){
24     n=read(),Q=read(),blocks=(int)sqrt(n);
25     for (int i=1; i<=n; i++) c[i].x=read(),c[i].index=i;
26     char s[5]; for (int i=1; i<=Q; i++){scanf("%s",s); a[i].tp=s[0]==R,a[i].L=read(),a[i].R=read(),a[i].index=i;} a[++Q].tp=1;
27     int las=1;
28     for (int K=1; K<=Q; K++) if (a[K].tp){
29         sort(c+1,c+1+n,cmp_x);
30         int cntnew=1;
31         memset(cc,0,sizeof cc),cc[1].x=1;
32         for (int i=2; i<=n; i++) if (c[i].x==c[i-1].x) cc[i].x=cntnew; else cc[i].x=++cntnew;
33         for (int i=1; i<=n; i++) cc[i].index=c[i].index;
34         sort(c+1,c+1+n,cmp_index);
35         sort(cc+1,cc+1+n,cmp_index);
36         
37         sort(a+las,a+1+K-1,cmp_blocks);
38         int curL=1,curR=0;
39         memset(cnt,0,sizeof cnt); ans=0;
40         for (int i=las; i<K; i++){
41             while (curL<a[i].L) remove(curL++);
42             while (curR>a[i].R) remove(curR--);
43             while (curL>a[i].L) add(--curL);
44             while (curR<a[i].R) add(++curR);
45             a[i].ans=ans;
46         }
47         sort(a+las,a+1+K-1,cmp_id);
48         for (int i=las; i<K; i++) printf("%d\n",a[i].ans);
49         las=K+1,c[a[K].L].x=a[K].R;
50     }
51     return 0;
52 }
View Code

后来调试时发现,几个排序的地方(离散这块,如图)就占用了5s的时间,去掉3个排序,就只要0.2s了.

技术分享

但是问题是怎么去掉排序???

还是慢慢来,一个一个改.先发现了,cc这个排序大可不必,完全可以用一个辅助数组ccc来完成,这样这部分的复杂度就从O(nlogn)变成了O(n)了.

结果也好歹多了10分,TLE50.此时极端数据需要3s左右才能跑完.

技术分享
 1 %:pragma GCC optimize(3)
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cmath>
 6 using namespace std;
 7 int n,Q,K,blocks;
 8 int cnt[10005],ans;
 9 struct query{int L,R,tp,index,ans;}a[10005];
10 struct data{int x,index;}c[10005],cc[10005],ccc[10005];
11 inline int read(){
12     int x=0; char ch=getchar();
13     while (ch<0||ch>9) ch=getchar();
14     while (ch>=0&&ch<=9) x=x*10+ch-0,ch=getchar();
15     return x;
16 }
17 bool cmp_x(data u,data v){return u.x<v.x;}
18 bool cmp_index(data u,data v){return u.index<v.index;}
19 bool cmp_blocks(query u,query v){return u.L/blocks==v.L/blocks?u.R<v.R:u.L<v.L;}
20 bool cmp_id(query u,query v){return u.index<v.index;}
21 void remove(int p){cnt[cc[p].x]--,ans-=cnt[cc[p].x]==0;}
22 void add(int p){cnt[cc[p].x]++,ans+=cnt[cc[p].x]==1;}
23 int main(){
24     n=read(),Q=read(),blocks=(int)sqrt(n);
25     for (int i=1; i<=n; i++) c[i].x=read(),c[i].index=i;
26     char s[5]; for (int i=1; i<=Q; i++){scanf("%s",s); a[i].tp=s[0]==R,a[i].L=read(),a[i].R=read(),a[i].index=i;} a[++Q].tp=1;
27     int las=1;
28     for (int K=1; K<=Q; K++) if (a[K].tp){
29         sort(c+1,c+1+n,cmp_x);
30         int cntnew=1;
31         memset(cc,0,sizeof cc),cc[1].x=1;
32         for (int i=2; i<=n; i++) if (c[i].x==c[i-1].x) cc[i].x=cntnew; else cc[i].x=++cntnew;
33         for (int i=1; i<=n; i++) cc[i].index=c[i].index;
34         sort(c+1,c+1+n,cmp_index);
35         for (int i=1; i<=n; i++) ccc[cc[i].index].x=cc[i].x;
36         for (int i=1; i<=n; i++) cc[i].x=ccc[i].x,cc[i].index=c[i].index;
37         
38         sort(a+las,a+1+K-1,cmp_blocks);
39         int curL=1,curR=0;
40         memset(cnt,0,sizeof cnt); ans=0;
41         for (int i=las; i<K; i++){
42             while (curL<a[i].L) remove(curL++);
43             while (curR>a[i].R) remove(curR--);
44             while (curL>a[i].L) add(--curL);
45             while (curR<a[i].R) add(++curR);
46             a[i].ans=ans;
47         }
48         sort(a+las,a+1+K-1,cmp_id);
49         for (int i=las; i<K; i++) printf("%d\n",a[i].ans);
50         las=K+1,c[a[K].L].x=a[K].R;
51     }
52     return 0;
53 }
View Code

继续思考,发现图中的第二个排序也可以省去,因为这一个排序的作用很小,主要是在这里有用(如图).

技术分享

那其实,这里完全可以O(n)查找一下搞定,反而会省去O(nlogn)的排序.结果就TLE80了,很鸡冻.

此时极端数据要1.4s左右能跑完.

技术分享
 1 %:pragma GCC optimize(3)
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cmath>
 6 using namespace std;
 7 int n,Q,K,blocks;
 8 int cnt[10005],ans;
 9 struct query{int L,R,tp,index,ans;}a[10005];
10 struct data{int x,index;}c[10005],cc[10005],ccc[10005];
11 inline int read(){
12     int x=0; char ch=getchar();
13     while (ch<0||ch>9) ch=getchar();
14     while (ch>=0&&ch<=9) x=x*10+ch-0,ch=getchar();
15     return x;
16 }
17 bool cmp_x(data u,data v){return u.x<v.x;}
18 bool cmp_index(data u,data v){return u.index<v.index;}
19 bool cmp_blocks(query u,query v){return u.L/blocks==v.L/blocks?u.R<v.R:u.L<v.L;}
20 bool cmp_id(query u,query v){return u.index<v.index;}
21 void remove(int p){cnt[cc[p].x]--,ans-=cnt[cc[p].x]==0;}
22 void add(int p){cnt[cc[p].x]++,ans+=cnt[cc[p].x]==1;}
23 int main(){
24     n=read(),Q=read(),blocks=(int)sqrt(n);
25     for (int i=1; i<=n; i++) c[i].x=read(),c[i].index=i;
26     char s[5]; for (int i=1; i<=Q; i++){scanf("%s",s); a[i].tp=s[0]==R,a[i].L=read(),a[i].R=read(),a[i].index=i;} a[++Q].tp=1;
27     int las=1;
28     for (int K=1; K<=Q; K++) if (a[K].tp){
29         sort(c+1,c+1+n,cmp_x);
30         int cntnew=1;
31         memset(cc,0,sizeof cc),cc[1].x=1;
32         for (int i=2; i<=n; i++) if (c[i].x==c[i-1].x) cc[i].x=cntnew; else cc[i].x=++cntnew;
33         for (int i=1; i<=n; i++) cc[i].index=c[i].index;
34         for (int i=1; i<=n; i++) ccc[cc[i].index].x=cc[i].x;
35         for (int i=1; i<=n; i++) cc[i].x=ccc[i].x,cc[i].index=c[i].index;
36         
37         sort(a+las,a+1+K-1,cmp_blocks);
38         int curL=1,curR=0;
39         memset(cnt,0,sizeof cnt); ans=0;
40         for (int i=las; i<K; i++){
41             while (curL<a[i].L) remove(curL++);
42             while (curR>a[i].R) remove(curR--);
43             while (curL>a[i].L) add(--curL);
44             while (curR<a[i].R) add(++curR);
45             a[i].ans=ans;
46         }
47         sort(a+las,a+1+K-1,cmp_id);
48         for (int i=las; i<K; i++) printf("%d\n",a[i].ans);
49         las=K+1;
50         for (int i=1; i<=n; i++) if (c[i].index==a[K].L) c[i].x=a[K].R;
51     }
52     return 0;
53 }
View Code

 接下来就是攻克最顶端的排序.这个排序还是有点用的,因此稍稍要考虑充分一点.

考虑到(下图)修改只是修改一个值,那么其他的值的顺序还是不变的,只需要调整被修改的位置与其他的位置的顺序,因此又一个nlogn被改成了n

技术分享

要注意的是,在最开始时,也要先将c数组排成有序的.此时,极端数据只需0.2s了,然后就AC了!~~~

技术分享
 1 %:pragma GCC optimize(3)
 2 #include<cstdio>
 3 #include<cstring>
 4 #include<algorithm>
 5 #include<cmath>
 6 using namespace std;
 7 int n,Q,K,blocks;
 8 int cnt[10005],ans;
 9 struct query{int L,R,tp,index,ans;}a[10005];
10 struct data{int x,index;}c[10005],cc[10005],ccc[10005];
11 inline int read(){
12     int x=0; char ch=getchar();
13     while (ch<0||ch>9) ch=getchar();
14     while (ch>=0&&ch<=9) x=x*10+ch-0,ch=getchar();
15     return x;
16 }
17 bool cmp_x(data u,data v){return u.x<v.x;}
18 bool cmp_index(data u,data v){return u.index<v.index;}
19 bool cmp_blocks(query u,query v){return u.L/blocks==v.L/blocks?u.R<v.R:u.L<v.L;}
20 bool cmp_id(query u,query v){return u.index<v.index;}
21 void remove(int p){cnt[cc[p].x]--,ans-=cnt[cc[p].x]==0;}
22 void add(int p){cnt[cc[p].x]++,ans+=cnt[cc[p].x]==1;}
23 int main(){
24     n=read(),Q=read(),blocks=(int)sqrt(n);
25     for (int i=1; i<=n; i++) c[i].x=read(),c[i].index=i; sort(c+1,c+1+n,cmp_x);
26     char s[5]; for (int i=1; i<=Q; i++){scanf("%s",s); a[i].tp=s[0]==R,a[i].L=read(),a[i].R=read(),a[i].index=i;} a[++Q].tp=1;
27     int las=1;
28     for (int K=1; K<=Q; K++) if (a[K].tp){
29         int cntnew=1;
30         memset(cc,0,sizeof cc),cc[1].x=1;
31         for (int i=2; i<=n; i++) if (c[i].x==c[i-1].x) cc[i].x=cntnew; else cc[i].x=++cntnew;
32         for (int i=1; i<=n; i++) cc[i].index=c[i].index;
33         for (int i=1; i<=n; i++) ccc[cc[i].index].x=cc[i].x;
34         for (int i=1; i<=n; i++) cc[i].x=ccc[i].x,cc[i].index=c[i].index;
35         sort(a+las,a+1+K-1,cmp_blocks);
36         int curL=1,curR=0;
37         memset(cnt,0,sizeof cnt); ans=0;
38         for (int i=las; i<K; i++){
39             while (curL<a[i].L) remove(curL++);
40             while (curR>a[i].R) remove(curR--);
41             while (curL>a[i].L) add(--curL);
42             while (curR<a[i].R) add(++curR);
43             a[i].ans=ans;
44         }
45         sort(a+las,a+1+K-1,cmp_id);
46         for (int i=las; i<K; i++) printf("%d\n",a[i].ans);
47         las=K+1; int p;
48         for (int i=1; i<=n; i++) if (c[i].index==a[K].L){p=i,c[i].x=a[K].R; break;}
49         while (p<n&&c[p].x>c[p+1].x) swap(c[p],c[p+1]),p++;
50         while (p>1&&c[p].x<c[p-1].x) swap(c[p],c[p-1]),p--;
51     }
52     return 0;
53 }
View Code

通过这一题,我们发现,对于某一题,并不是都需要高端算法,有时候在暴力的基础上(莫队和分块也是特殊的暴力)在合适的地方加点优化,会有意想不到的收获!!!

[通过一题论优化的重要性](原)