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

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

比如这道题 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. 1.2_Selenium的三生三世

  2. Linux的NIS配置

    快速命令 # Server和Client设置NIS域名 nisdomainname nis_server echo 'NISDOMAIN=nis_server' >> /etc/sysco ...

  3. 第三十五篇:vue3,(组合式api的初步理解)

    好家伙, 来一波核心概念:数据劫持是响应式的核心 1.由set up开始 (1)vue3中的一个新的配置项,值为一个函数. (2)组件中所用的到的:数据,方法,计算属性均要配置在set up中. (3 ...

  4. spring项目中starter包的原理,以及自定义starter包的使用

    MAVEN项目中starter的原理 一.原始方式 我们最早配置spring应用的时候,必须要经历的步骤:1.pom文件中引入相关的jar包,包括spring,redis,jdbc等等 2.通过pro ...

  5. day32-线程基础02

    线程基础02 3.继承Thread和实现Runnable的区别 从java的设计来看,通过继承Thread或者实现Runnable接口本身来创建线程本质上没有区别,从jdk帮助文档我们可以看到Thre ...

  6. 如何用 refcursor 返回结果集

    可以通过返回 Refcursor 类型的函数,或者out 类型的函数或 procedure 返回结果集. 一.返回refcursor 类型的函数 create or replace function ...

  7. Java中的SPI原理浅谈

    在面向对象的程序设计中,模块之间交互采用接口编程,通常情况下调用方不需要知道被调用方的内部实现细节,因为一旦涉及到了具体实现,如果需要换一种实现就需要修改代码,这违反了程序设计的"开闭原则& ...

  8. 海康摄像机使用GB28181接入SRS服务器的搭建步骤---源码安装的方式

    下载代码 地址:https://github.com/ossrs/srs-gb28181 https://github.com/ossrs/srs-gb28181.git 注意:使用的是含有gb281 ...

  9. 2.云原生之Docker容器环境安装实践

    转载自:https://www.bilibili.com/read/cv15181036/?from=readlist 官方一键安装脚本 补充时间:[2020年4月22日 11:00:59] 一键安装 ...

  10. Elasticsearch:significant terms aggregation

    在本文中,我们将重点关注significant terms和significant text聚合.这些聚合旨在搜索数据集中有趣和/或不寻常的术语,这些术语可以告诉您有关数据的隐藏属性的更多信息.此功能 ...