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

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

比如这道题 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. windows10/11高性能模式开启

    大部分用户windows10和11高性能模式都被隐藏了 并且没有隐藏选项我们如何开启呢如下 win+R如下 打开运行-输入cmd进入后输入代码如下 powercfg -SETACTIVE 8c5e7f ...

  2. 报错:selenium.common.exceptions.InvalidArgumentException: Message: invalid argument: missing 'name'

    代码运行到这:driver.add_cookie(cookies),报错了 相信坑了不少人,接下来是解决办法 driver.add_cookie(cookies) cookies = { " ...

  3. KingbaseES V8R3 shared_buffer占用过多导致实例崩溃

    背景 有这样一个案例.客户备库意外宕机,从集群日志只看出发生了主备切换,备库一直持续恢复备库没有成功,从数据库日志看到如下报错: terminating connection because of c ...

  4. 04-MyBatisPlus条件构造器

    一.wapper介绍 Wrapper : 条件构造抽象类,最顶端父类 AbstractWrapper : 用于查询条件封装,生成 sql 的 where 条件 QueryWrapper : Entit ...

  5. 部署Netlify站点博客

    Netlify站点部署静态博客 今天尝试把站点部署在Netlify上,因为部署在GitHub Pages上,国内访问速度太慢了,所以就尝试一下别的站点,部署成功之后发现速度还是不太行,后边继续找找原因 ...

  6. Java安全之freemaker模版注入

    Java安全之freemaker模版注入 freemaker简介 FreeMarker 是一款模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等 ...

  7. Minio纠删码快速入门

    官方文档地址:http://docs.minio.org.cn/docs/master/minio-erasure-code-quickstart-guide Minio使用纠删码erasure co ...

  8. Elastic:使用Postman来访问需要账号密码的Elastic Stack

  9. DevOps图示

  10. Codeforces Round #709 Div2

    就ac了一题.QAQ掉分了我好难过. A题要画图推出来公式. B不会,现在没看到好懂得题解. C题,思路错了,给一些数,每天只能选其中一些,每个数不能被选超过k次,应该先将每天只能选一个得这个数先选上 ...