一般情况下,程序运行消耗时间主要与时间复杂度有关,超时与否取决于算法是否正确。

但对于某些题目,时间复杂度正确的程序也无法通过,这时我们就需要卡常数,即通过优化一些操作的常数因子减少时间消耗。

比如这道题 P5309 [Ynoi2011] 初始化

这道题目的做法我写在另一篇博客里,这里主要研究卡常方式。

#include<bits/stdc++.h>
using namespace std;
const int h=200010;
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {
if(ch == '-') w= -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
s = s * 10 + ch - '0';
ch = getchar();
}
return s * w;
}
int mod=1e9+7;
inline void update(int &x, int y) {
x += y;
if(x >= mod) x -= mod;
} int n,m;
int a[h];
int b_sum[h];
int len;
int pre[2010][2010];
int suf[2010][2010];
int get_pos(int x){
return (x-1)/len+1;
}
int main(){
n=read(),m=read();
len=135;
for(int i=1;i<=n;i++)
a[i]=read(),update(b_sum[get_pos(i)],a[i]);
int op,x,y,z;
for(int i=1;i<=m;i++){
op=read(),x=read(),y=read();
if(op==1){
z=read();
if(x>=len)
for(int j=y;j<=n;j+=x)
update(a[j],z),update(b_sum[get_pos(j)],z);
else{
for(int j=y;j<=x;j++)
update(pre[x][j],z);
for(int j=1;j<=y;j++)
update(suf[x][j],z);
}
}
else{
int l=x,r=y,lb=get_pos(x),rb=get_pos(y);
int ans=0;
if(lb==rb)
for(int j=l;j<=r;j++)
update(ans,a[j]);
else{
for(int j=l;j<=lb*len;j++)
update(ans,a[j]);
for(int j=lb+1;j<=rb-1;j++)
update(ans,b_sum[j]);
for(int j=(rb-1)*len+1;j<=r;j++)
update(ans,a[j]); } for(int j=1;j<len;j++){
lb=(l-1)/j+1,rb=(r-1)/j+1;
if(lb==rb)
ans-=pre[j][(l-1)%j],ans%=mod,ans+=pre[j][(r-1)%j+1],ans%=mod;
else
ans=(ans+1ll*(rb-lb-1)*pre[j][j]+pre[j][(r-1)%j+1]+suf[j][(l-1)%j+1])%mod;
}
ans = ans % mod + mod;
ans = (ans >= mod) ? ans - mod : ans;
printf("%d\n",ans);
}
} return 0;
}

20分超时代码

技巧一:手写常数因子大的运算

c++中取模运算常数因子很大,于是我们改用更快的运算组合表示取模。

inline void update(int &x, int y) {
x += y;
if(x >= mod) x -= mod;
}

经此修改,可以拿到95分。

#include<bits/stdc++.h>
using namespace std;
const int h=200010;
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {
if(ch == '-') w= -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
s = s * 10 + ch - '0';
ch = getchar();
}
return s * w;
}
int mod=1e9+7;
inline void update(int &x, int y) {
x += y;
if(x >= mod) x -= mod;
} int n,m;
int a[h];
int b_sum[h];
int len;
int pre[2010][2010];
int suf[2010][2010];
int get_pos(int x){
return (x-1)/len+1;
}
int main(){
n=read(),m=read();
len=120;
for(int i=1;i<=n;i++)
a[i]=read(),update(b_sum[get_pos(i)],a[i]);
int op,x,y,z;
for(int i=1;i<=m;i++){
op=read(),x=read(),y=read();
if(op==1){
z=read();
if(x>=len)
for(int j=y;j<=n;j+=x)
update(a[j],z),update(b_sum[get_pos(j)],z);
else{
for(int j=y;j<=x;j++)
update(pre[x][j],z);
for(int j=1;j<=y;j++)
update(suf[x][j],z);
}
}
else{
int l=x,r=y,lb=get_pos(x),rb=get_pos(y);
int ans=0;
if(lb==rb)
for(int j=l;j<=r;j++)
update(ans,a[j]);
else{
for(int j=l;j<=lb*len;j++)
update(ans,a[j]);
for(int j=lb+1;j<=rb-1;j++)
update(ans,b_sum[j]);
for(int j=(rb-1)*len+1;j<=r;j++)
update(ans,a[j]); } for(int j=1;j<len;j++){
lb=(l-1)/j+1,rb=(r-1)/j+1;
if(lb==rb)
ans-=pre[j][(l-1)%j],ans%=mod,ans+=pre[j][(r-1)%j+1],ans%=mod;
else
ans=(ans+1ll*(rb-lb-1)*pre[j][j]+pre[j][(r-1)%j+1]+suf[j][(l-1)%j+1])%mod;
}
ans = ans % mod + mod;
ans = (ans >= mod) ? ans - mod : ans;
printf("%d\n",ans);
}
} return 0;
}

95分优化取模

技巧二:块长乱搞

这道题目需要使用分块。

而分块的时间复杂度往往随着块长而剧烈波动。

于是我们不断尝试新的块长。(为了节约评测机资源可以二分寻找)

得出最优块长在135左右,通过了这道题,耗时为5.93s。

#include<bits/stdc++.h>
using namespace std;
const int h=200010;
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {
if(ch == '-') w= -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
s = s * 10 + ch - '0';
ch = getchar();
}
return s * w;
}
int mod=1e9+7;
inline void update(int &x, int y) {
x += y;
if(x >= mod) x -= mod;
} int n,m;
int a[h];
int b_sum[h];
int len;
int pre[2010][2010];
int suf[2010][2010];
int get_pos(int x){
return (x-1)/len+1;
}
int main(){
n=read(),m=read();
len=135;
for(int i=1;i<=n;i++)
a[i]=read(),update(b_sum[get_pos(i)],a[i]);
int op,x,y,z;
for(int i=1;i<=m;i++){
op=read(),x=read(),y=read();
if(op==1){
z=read();
if(x>=len)
for(int j=y;j<=n;j+=x)
update(a[j],z),update(b_sum[get_pos(j)],z);
else{
for(int j=y;j<=x;j++)
update(pre[x][j],z);
for(int j=1;j<=y;j++)
update(suf[x][j],z);
}
}
else{
int l=x,r=y,lb=get_pos(x),rb=get_pos(y);
int ans=0;
if(lb==rb)
for(int j=l;j<=r;j++)
update(ans,a[j]);
else{
for(int j=l;j<=lb*len;j++)
update(ans,a[j]);
for(int j=lb+1;j<=rb-1;j++)
update(ans,b_sum[j]);
for(int j=(rb-1)*len+1;j<=r;j++)
update(ans,a[j]); } for(int j=1;j<len;j++){
lb=(l-1)/j+1,rb=(r-1)/j+1;
if(lb==rb)
ans-=pre[j][(l-1)%j],ans%=mod,ans+=pre[j][(r-1)%j+1],ans%=mod;
else
ans=(ans+1ll*(rb-lb-1)*pre[j][j]+pre[j][(r-1)%j+1]+suf[j][(l-1)%j+1])%mod;
}
ans = ans % mod + mod;
ans = (ans >= mod) ? ans - mod : ans;
printf("%d\n",ans);
}
} return 0;
}

100分块长优化

技巧三:按照题目内容实行特定优化

以下内容参考自原题目讨论版

不同的题目有不同的步骤,这道题里这样一个步骤耗时巨大。

for(int j=1;j<len;j++){
    lb=(l-1)/j+1,rb=(r-1)/j+1;
    if(lb==rb)
        ans-=pre[j][(l-1)%j],ans%=mod,ans+=pre[j][(r-1)%j+1],ans%=mod;
    else
        ans=(ans+1ll*(rb-lb-1)*pre[j][j]+pre[j][(r-1)%j+1]+suf[j][(l-1)%j+1])%mod;
}

这个地方运算很慢(因为一些原因套不了取模优化),而且当这个因数没有修改的时候,这些运算完全没有必要。

于是,如果这个因数没有进行修改,我们跳过后面的运算步骤。

for(int j=1;j<len;j++){
if(!pre[j][j])
continue;
lb=(l-1)/j+1,rb=(r-1)/j+1;
if(lb==rb)
ans-=pre[j][(l-1)%j],ans%=mod,ans+=pre[j][(r-1)%j+1],ans%=mod;
else
ans=(ans+1ll*(rb-lb-1)*pre[j][j]+pre[j][(r-1)%j+1]+suf[j][(l-1)%j+1])%mod;
}

以及在没有修改的情况下,弄一个前缀和,查询区间和直接调用。

耗时3.06s。

#include<bits/stdc++.h>
using namespace std;
const int h=200010;
inline int read() {
int s = 0, w = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {
if(ch == '-') w= -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
s = s * 10 + ch - '0';
ch = getchar();
}
return s * w;
}
int mod=1e9+7;
inline void update(int &x, int y) {
x += y;
if(x >= mod) x -= mod;
} int n,m;
int a[h];
int b_sum[h];
int len;
int pre[2010][2010];
int suf[2010][2010];
int sum[h];
int get_pos(int x){
return (x-1)/len+1;
}
int main(){
n=read(),m=read();
len=150;
for(int i=1;i<=n;i++)
a[i]=read(),update(b_sum[get_pos(i)],a[i]),update(sum[i],a[i]+sum[i-1]);
int op,x,y,z;
bool fl=0;
for(int i=1;i<=m;i++){
op=read(),x=read(),y=read();
if(op==1){
z=read();
if(x>=len)
for(int j=y;j<=n;j+=x)
fl|=1,update(a[j],z),update(b_sum[get_pos(j)],z);
else{
for(int j=y;j<=x;j++)
update(pre[x][j],z);
for(int j=1;j<=y;j++)
update(suf[x][j],z);
}
}
else{ int l=x,r=y,lb=get_pos(x),rb=get_pos(y);
int ans=0;
if(!fl) ans+=sum[r]-sum[l-1];
else
if(lb==rb)
for(int j=l;j<=r;j++)
update(ans,a[j]);
else{
for(int j=l;j<=lb*len;j++)
update(ans,a[j]);
for(int j=lb+1;j<=rb-1;j++)
update(ans,b_sum[j]);
for(int j=(rb-1)*len+1;j<=r;j++)
update(ans,a[j]); } for(int j=1;j<len;j++){
if(!pre[j][j])
continue;
lb=(l-1)/j+1,rb=(r-1)/j+1;
if(lb==rb)
ans-=pre[j][(l-1)%j],ans%=mod,ans+=pre[j][(r-1)%j+1],ans%=mod;
else
ans=(ans+1ll*(rb-lb-1)*pre[j][j]+pre[j][(r-1)%j+1]+suf[j][(l-1)%j+1])%mod;
}
ans = ans % mod + mod;
ans = (ans >= mod) ? ans - mod : ans;
printf("%d\n",ans);
}
} return 0;
}

技巧四:节约空间

开数组也是要消耗很多时间的,我们进行优化。

int b_sum[1510];
int len;
int pre[161][161];
int suf[161][161];

时间优化至3.00s。

技巧五:奇技淫巧

到这一步,接下来的卡常技巧就没有学习的必要了。

比如把mod定义成成const可以快到2.52s。

将提交语言改为c++98可以快到2.4s。

然后是...然后自己看吧,没什么意义。

(主要由@Foofish大佬贡献)

//#pragma G++ target("avx")
//#pragma G++ optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
const int h=200010;
inline int read() {
register int s = 0, w = 1;
char ch = getchar();
while(ch < '0' || ch > '9') {
if(ch == '-') w= -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
s = s * 10 + ch - '0';
ch = getchar();
}
return s * w;
}
bool flag=0;
const int mod=1e9+7;
int n,m;
int a[h];
int b_sum[2010];
int len;
int pre[132][132];//pre[x][y]即modify(x,1)+modify(x,2)+...+modify(x,y)
int suf[132][132];
int sum[h];
int b_len;
int POS[201000];
#define get_pos(x) POS[x]
inline void write(int x) {
if(x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
inline void update(int &x, int y) {
x += y;
if(x >= mod) x -= mod;
}
int main(){
//ios::sync_with_stdio(0);
n=read(),m=read();
len=130;
int Bnum = n / len;
for(int i = 1; i <= Bnum + 1; ++i)
for(int j = (i - 1) * len + 1; j <= i * len; ++j)
POS[j] = i;
for(int i=1;i<=n;i++)
a[i]=read(),update(b_sum[get_pos(i)], a[i]),update(sum[i], sum[i - 1] + a[i]);
int op,x,y,z;
for(int i=1;i<=m;i++){
op=read(),x=read(),y=read();
if(op==1){
z=read();
if(x>=len)
for(int j=y;j<=n;j+=x)
flag|=1,update(a[j], z),update(b_sum[get_pos(j)], z);
else{
for(int j=y;j<=x;j++)
update(pre[x][j], z);
for(int j=1;j<=y;j++)
update(suf[x][j], z);
}
}
else{
int l=x,r=y,lb=get_pos(x),rb=get_pos(y);
int ans=0;
if(!flag)
ans+=sum[r]-sum[l-1];
else
if(lb==rb)
for(int j=l;j<=r;j++)
update(ans, a[j]);
else{
for(int j=l;j<=lb*len;j++)
update(ans, a[j]);
for(int j=lb+1;j<=rb-1;j++)
update(ans, b_sum[j]);
for(int j=(rb-1)*len+1;j<=r;j++)
update(ans, a[j]); } for(int j=1;j<len;j++){
if(!pre[j][j])
continue;
lb=(l-1)/j+1,rb=(r-1)/j+1;
if(lb==rb) {
ans-=pre[j][(l-1)%j],ans%=mod,ans+=pre[j][(r-1)%j+1],ans%=mod;
// ans = (ans - pre[j][(l - 1) % j] + pre[j][(r - 1) % j + 1] + mod) % mod;
}
else {
ans=(ans+1ll*(rb-lb-1)*pre[j][j]+pre[j][(r-1)%j+1]+suf[j][(l-1)%j+1])%mod;
}
}
ans = ans % mod + mod;
ans = (ans >= mod) ? ans - mod : ans;
printf("%d",ans); puts("");
}
} return 0;
}

耗时2.38s

#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;
template<typename T>inline void read(T& t){
int c=getchar(),f=1;t=0;
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c))t=t*10+c-48,c=getchar();t*=f;
}
template<typename T,typename ...Args>inline void read(T& t,Args&... args){
read(t),read(args...);
}

*某位大佬提供的快读,安装之后时间达到了2.23s。

目前能达到的最短时间:c++ 14 无O2优化 2.21s

#pragma G++ target("avx")
#pragma G++ optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
const int h=200010;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
char buf[1<<21],*p1=buf,*p2=buf;
template<typename T>inline void read(T& t){
int c=getchar(),f=1;t=0;
while(!isdigit(c)){if(c=='-')f=-1;c=getchar();}
while(isdigit(c))t=t*10+c-48,c=getchar();t*=f;
}
template<typename T,typename ...Args>inline void read(T& t,Args&... args){
read(t),read(args...);
}
bool flag=0;
const int mod=1e9+7;
int n,m;
int a[h];
int b_sum[2010];
int len;
int pre[132][132];//pre[x][y]即modify(x,1)+modify(x,2)+...+modify(x,y)
int suf[132][132];
int sum[h];
int b_len;
int POS[201000];
#define get_pos(x) POS[x]
inline void write(int x) {
if(x >= 10) write(x / 10);
putchar(x % 10 + '0');
}
inline void update(int &x, int y) {
x += y;
if(x >= mod) x -= mod;
}
int main(){
//ios::sync_with_stdio(0);
read(n,m);
len=130;
int Bnum = n / len;
for(int i = 1; i <= Bnum + 1; ++i)
for(int j = (i - 1)* len + 1,ran= i * len; j <=ran ; ++j)
POS[j] = i;
for(int i=1;i<=n;i++)
read(a[i]),update(b_sum[get_pos(i)], a[i]),update(sum[i], sum[i - 1] + a[i]);
int op,x,y,z;
for(int i=1;i<=m;i++){
read(op,x,y);
if(op==1){
read(z);
if(x>=len)
for(int j=y;j<=n;j+=x)
flag|=1,update(a[j], z),update(b_sum[get_pos(j)], z);
else{
for(int j=y;j<=x;j++)
update(pre[x][j], z);
for(int j=1;j<=y;j++)
update(suf[x][j], z);
}
}
else{
int l=x,r=y,lb=get_pos(x),rb=get_pos(y);
int ans=0;
if(!flag)
ans+=sum[r]-sum[l-1];
else
if(lb==rb)
for(int j=l;j<=r;j++)
update(ans, a[j]);
else{
for(int j=l,ran=lb*len;j<=ran;j++)
update(ans, a[j]);
for(int j=lb+1;j<=rb-1;j++)
update(ans, b_sum[j]);
for(int j=(rb-1)*len+1;j<=r;j++)
update(ans, a[j]); } for(int j=1;j<len;j++){
if(!pre[j][j])
continue;
lb=(l-1)/j+1,rb=(r-1)/j+1;
if(lb==rb) {
ans-=pre[j][(l-1)%j],ans%=mod,ans+=pre[j][(r-1)%j+1],ans%=mod;
// ans = (ans - pre[j][(l - 1) % j] + pre[j][(r - 1) % j + 1] + mod) % mod;
}
else {
ans=(ans+1ll*(rb-lb-1)*pre[j][j]+pre[j][(r-1)%j+1]+suf[j][(l-1)%j+1])%mod;
}
}
ans = ans % mod + mod;
ans = (ans >= mod) ? ans - mod : ans;
printf("%d",ans); puts("");
}
} return 0;
}

Code

从 洛谷P5309 Ynoi2011 初始化 看卡常的更多相关文章

  1. 洛谷P5309 Ynoi 2011 初始化 题解

    题面. 我也想过根号分治,但是题目刷得少,数组不敢开,所以还是看题解做的. 这道题目要用到根号分治的思想,可以看看这道题目和我的题解. 题目要求处理一个数组a,支持如下操作. 对一个整数x,对数组长度 ...

  2. 洛谷 P1186 【玛丽卡】

    这道题题目真的想吐槽一下...是在机房同学的解释下才看懂的.就是让你求在可以删一条边的情况下,并且删后保证可以到达终点时,求删了后的最大的最短路径. 70分暴力思路: 枚举删边,然后跑一下最短路即可, ...

  3. 洛谷 P1641 [SCOI2010]生成字符串

    洛谷 这题一看就是卡塔兰数. 因为\(cnt[1] \leq cnt[0]\),很显然的卡塔兰嘛! 平时我们推导卡塔兰是用一个边长为n的正方形推的, 相当于从(0,0)点走到(n,n)点,向上走的步数 ...

  4. 不失一般性和快捷性地判定决策单调(洛谷P1912 [NOI2009]诗人小G)(动态规划,决策单调性,单调队列)

    洛谷题目传送门 闲话 看完洛谷larryzhong巨佬的题解,蒟蒻一脸懵逼 如果哪年NOI(放心我这样的蒟蒻是去不了的)又来个决策单调性优化DP,那蒟蒻是不是会看都看不出来直接爆\(0\)?! 还是要 ...

  5. 【洛谷3674】小清新人渣的本愿(莫队,bitset)

    [洛谷3674]小清新人渣的本愿(莫队,bitset) 题面 洛谷,自己去看去,太长了 题解 很显然的莫队. 但是怎么查询那几个询问. 对于询问乘积,显然可以暴力枚举因数(反正加起来也是\(O(n\s ...

  6. BZOJ1060或洛谷1131 [ZJOI2007]时态同步

    BZOJ原题链接 洛谷原题链接 看上去就觉得是一道树形\(\mathtt{DP}\),不过到头来我发现我写了一个贪心.. 显然对越靠近根(记为\(r\))的边进行加权贡献越大,且同步的时间显然是从根到 ...

  7. 【流水调度问题】【邻项交换对比】【Johnson法则】洛谷P1080国王游戏/P1248加工生产调度/P2123皇后游戏/P1541爬山

    前提说明,因为我比较菜,关于理论性的证明大部分是搬来其他大佬的,相应地方有注明. 我自己写的部分换颜色来便于区分. 邻项交换对比是求一定条件下的最优排序的思想(个人理解).这部分最近做了一些题,就一起 ...

  8. 洛谷 P6295 - 有标号 DAG 计数(生成函数+容斥+NTT)

    洛谷题面传送门 看到图计数的题就条件反射地认为是不可做题并点开了题解--实际上这题以我现在的水平还是有可能能独立解决的( 首先连通这个条件有点棘手,我们尝试把它去掉.考虑这题的套路,我们设 \(f_n ...

  9. 快速排序--洛谷卡TLE后最终我还是选择了三向切割

    写在前边 这篇文章呢,我们接着聊一下排序算法,我们之前已经谈到了简单插入排序 和ta的优化版希尔排序,这节我们要接触一个更"高级"的算法了--快速排序. 在做洛谷的时候,遇到了一道 ...

随机推荐

  1. ubuntu下安装python

    一.安装python3.6 sudo add-apt-repository ppa:jonathonf/python-3.6 如显示不能添加"'ppa:~jonathonf/ubuntu/p ...

  2. KingbaseESV8R6不同隔离级下xmin的区别

    背景 sys_stat_activity视图中用两个字段表示: backend_xid表示事务开始需要申请的事务id backend_xmin表示一个事务快照,表示当前数据库中最小的正在运行的事务号, ...

  3. Java 9.回文数

    给你一个整数 x ,如果 x 是一个回文整数,返回 true :否则,返回 false .回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数.    例如,121 是回文,而 123 不是. ...

  4. Redis变慢?深入浅出Redis性能诊断系列文章(三)

    (本文首发于"数据库架构师"公号,订阅"数据库架构师"公号,一起学习数据库技术,助力职业发展) 本篇为Redis性能问题诊断系列的第三篇,主要从Redis服务层 ...

  5. [深度学习]-Dataset数据集加载

    加载数据集dataloader from torch.utils.data import DataLoader form 自己写的dataset import Dataset train_set = ...

  6. 输入法词库解析(二)搜狗拼音细胞词库.scel(.qcel)

    详细代码:https://github.com/cxcn/dtool 前言 .scel 是搜狗拼音输入法所使用的细胞词库格式,可以在 https://pinyin.sogou.com/dict/ 下载 ...

  7. day40-网络编程02

    Java网络编程02 4.TCP网络通信编程 基本介绍 基于客户端--服务端的网络通信 底层使用的是TCP/IP协议 应用场景举例:客户端发送数据,服务端接收并显示控制台 基于Scoket的TCP编程 ...

  8. MySql的InnoDB的三层B+树可以存储两千万左右条数据的计算逻辑

    总结/朱季谦 B+树是一种在非叶子节点存放排序好的索引而在叶子节点存放数据的数据结构,值得注意的是,在叶子节点中,存储的并非只是一行表数据,而是以页为单位存储,一个页可以包含多行表记录.非叶子节点存放 ...

  9. docker相关总结

    Docker 的相关使用记录 一.安装docker linux环境使用yum命令安装docker 第一步:确保自己的虚拟机没有安装过docker,如果安装过的需要将原先的docker进行卸载,命令如下 ...

  10. 【前端必会】tapable、hook,webpack的灵魂

    背景 什么是tapable.hook,平时做vue开发时的webpack 配置一直都没弄懂,你也有这种情况吗? 还是看源码,闲来无聊又看一下webpack的源码,看看能否找到一些宝藏 tapable和 ...