【分块】

【分块】

依照某种规律把一组数分成块 块内具有相同性质->可方便计算
阈值分治->一部分分块 一部分单独算

【整除分块】

O(sqrt(n)) 支持n<=1e12
image

核心思想

打表可得:
image
可发现:
image

时间复杂度分析

image

做法

image

【模版代码】

注意取模操作!

i64 division_block(i64 n){
	i64 res=0;
	for(i64 l=1LL,r;l<=n;l=r+1LL){
		r=n/(n/l);
		i64 cnt=(r-l+1)%mod;
        i64 val=(n/l)%mod;
		res=(res+(cnt*val)%mod)%mod;
	}
	return res;
}

题目整理

【二分+分块】智乃与模数

https://ac.nowcoder.com/acm/contest/95335/G

思路

代码

注意运用了很多等差数列的结论与变形计算

#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
typedef pair<int,int> PII;
typedef long long ll;
ll abss(ll a){return a>0?a:-a;}
ll max_(ll a,ll b){return a>b?a:b;}
ll min_(ll a,ll b){return a<b?a:b;}
bool cmpll(ll a,ll b){return a>b;}
/*
【思路】
打表->发现每个商相同的可以分为一组->余数为等差数列->公差为商
找前k个数:排完序后有单调性->二分第k个数的数值x(大于等于x的数至少有k个) 去check里验证并求和
【余数的表示方式】
a/b=x......t
->余数 t=a-向下取整(a/b)*b
*/
ll n,k;
ll vtot=0,val=0;
ll check(ll x){
      ll tot=0;//计算余数>=x的情况下有多少个
      for(ll l=1,r;l<=n;l=r+1){
            r=n/(n/l);//右区间
            ll a=n-(n/l)*l;  //余数->首项(最大的那个)
            ll b=n/l;  //商->公差
            if(a<x) continue;
            /*计算个数:末项a-b*(len-1)>=x -> len<=(a-x)/b+1*/
            tot+=min_((a-x)/b+1,r-l+1);//(a-x)/b+1:区间内余数>=k的个数
      }
      return tot;
}
signed main(){
      ios::sync_with_stdio(0);
      cin.tie(0);
      cout.tie(0);
      cin>>n>>k;
      ll L=1,R=n+1;
      while(L<R){
            ll mid=(L+R)/2;
            //找小于第k个的第一个
            ll tot_=check(mid);
            if(tot_>=k){// 数量大于等于k 左端点右移 ※值变大 范围变小
                  L=mid+1;
            }
            else{
                  R=mid;
                  val=mid;
                  vtot=tot_;
            }
      }
      ll ans=(k-vtot)*(val-1);
      for(ll l=1,r;l<=n;l=r+1){
            r=n/(n/l);//右区间
            ll a=n-(n/l)*l;  //余数->首项(最大的那个)
            ll b=n/l;  //商->公差
            if(a<val) continue;
            ll len=min_((a-val)/b+1,r-l+1);
            ans+=(a+a-b*(len-1))*len/2;
      }
      cout<<ans;
      return 0;
}

https://ac.nowcoder.com/acm/problem/17450

Count A%B=C

https://atcoder.jp/contests/abc414/tasks/abc414_e

题目大意

在1~n中找数互不相等的三元组(a,b,c),使得a%b=c,求三元组个数(取模)
n<=1e12

思路

※注意n~b中b的倍数个数:[n/b]向下取整
image

代码

注意取模要预处理

//整除分块 : 求1~n中 n/i 的总和
i64 division_block(i64 n){
	i64 res=0;
	for(i64 l=1LL,r;l<=n;l=r+1LL){
		r=n/(n/l);
		i64 cnt=(r-l+1)%mod;
        i64 val=(n/l)%mod;
		res=(res+(cnt*val)%mod)%mod;
	}
	return res;
}
const int N=3e5+10;
i64 n;
void solve(){
    cin>>n;
	i64 n_mod=n%mod;
	i64 n1_mod=(n-1LL)%mod;
	i64 ans=(n_mod*n_mod)%mod;
	i64 res1=(n1_mod*n_mod)%mod*qmi(2LL,mod-2LL,mod)%mod;
	ans=(ans+mod-res1)%mod;
	i64 res2=division_block(n);
	ans=(ans+mod-res2)%mod;
	cout<<ans<<endl;
}

【倍数/因数分块】

越小的数倍数越多->需要一起算

【模版代码】预处理因数/倍数/GCD/LCM

//因数
int cnt[N];
vector<int> g[N];
//倍数
int G[B+6][B+6];
i64 lcm(i64 x,i64 y){
    if(x==0 || y==0) return (x+y);
    return x/G[x][y%x]*y;
}
void init(){
	//预处理因数
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n/i;j++){
            g[i*j].push_back(i);
        }
    }
    for(int i=1;i<=n;i++) cnt[i]=g[i].size();
    for(int i=1;i<=B;i++){
        for(int j=1;j<=n/i;j++){
            //枚举倍数
        }
    }
	//GCD表
    for(int i=0;i<=B;i++){
        for(int j=0;j<=B;j++){
            if(i==0 || j==0){
                G[i][j]=i+j;
            }
            else if(i<j){
                G[i][j]=G[j%i][j];
            }
            else{
                G[i][j]=G[i%j][j];
            }
        }
    }
}

题目整理

Multiple and Factor

https://acm.hdu.edu.cn/contest/problem?cid=1181&pid=1002

题目大意

4ed68d87-335b-4433-9ea9-de71f1d5a05c
42ef91fa-ab23-48b6-80a6-f541f2b82bd2

思路

越小的数倍数越多->分块加
因数没多少个->直接暴力加即可

注意本题gcd和lcm计算要处理成O(1)->提前打GCD表

代码
const int N=5e5+10;
const int B=300;//分块阈值
int n,m;
i64 a[N];
//因数
int cnt[N];
vector<int> g[N];
//倍数
int G[B+6][B+6];
i64 c[N];//小值修改记录
i64 sum[N];//i的倍数和
i64 lcm(i64 x,i64 y){
    if(x==0 || y==0) return (x+y);
    return x/G[x][y%x]*y;
}
void init(){
    //预处理因数
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n/i;j++){
            g[i*j].push_back(i);
        }
    }
    //因数个数
    for(int i=1;i<=n;i++) cnt[i]=g[i].size();
    //预处理倍数和
    for(int i=1;i<=B;i++){
        for(int j=1;j<=n/i;j++){
            sum[i]+=a[i*j];
        }
    }
    //预处理gcd表:注意这里复杂度是O(1)的 打表不用单算
    for(int i=0;i<=B;i++){
        for(int j=0;j<=B;j++){
            if(i==0 || j==0){
                G[i][j]=i+j;
            }
            else if(i<j){
                G[i][j]=G[j%i][j];
            }
            else{
                G[i][j]=G[i%j][j];
            }
        }
    }
}
void solve(){
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    init();
    
    while(m--){
        int op,x;
        cin>>op>>x;
        if(op==1){
            i64 k;
            cin>>k;
            if(x<=B){
                c[x]+=k;
            }
            else{
                for(int i=x;i<=n;i+=x){
                    a[i]+=k;
                }
            }
            //更新sum
            for(int i=1;i<=B;i++){
                sum[i]+=1LL*k*(n/lcm(i,x));//lcm(i,x)既是i倍数又是x倍数
            }
        }
        else if(op==2){
            i64 k;
            cin>>k;
            for(auto son:g[x]){
                a[son]+=k;
            }
            for(int i=1;i<=B;i++){
                if(x%i==0){
                    sum[i]+=1LL*k*cnt[x/i];
                }
            }
        }
        else if(op==3){
            if(x<=B){
                cout<<sum[x]<<endl;
            }
            else{
                //a中大数直接算和
                i64 ans=0;
                for(int i=x;i<=n;i+=x){
                    ans+=a[i];
                }
                //需要加上小数c加倍数的贡献
                for(int i=1;i<=B;i++){
                    ans+=c[i]*(n/lcm(i,x));//小数同样能是大数的倍数
                }
                cout<<ans<<endl;
            }
        }
        else if(op==4){
            i64 ans=0;
            for(auto son:g[x]){
                ans+=a[son];
                if(son<=B){//还需要加上小数c加倍数的贡献
                    ans+=c[son]*cnt[x/son];// *cnt[x/son]:son倍数中同时也是x因数的部分
                }
            }
            cout<<ans<<endl;
        }
    }
}

杂题整理

最努力的活着

https://acm.hdu.edu.cn/contest/problem?cid=1179&pid=1009

题目大意

4638a0ed-bc2d-4e21-9e2f-6412dfacb681

思路

放置是上帝视角:从低到高保留即可
暴力代码

i128 n,w;
void solve(){
    read(n);read(w);
    i128 nn=n;
    i128 ans=0;
    while(n>=w){
        ans+=nn*n-n*(n-1)/2;
        n-=n/w;
    }
    ans+=nn*n-n*(n-1)/2;
    write(ans);
    putchar('\n');
}

考虑分块优化
eca2159f0ed18bc6758f6fbe21073a7c_720

代码

i128 n,w;
const i128 K=100;
void solve(){
    read(n);read(w);
    i128 N=n,ans=0;
    while(n>=w){
        i128 q=n/w;
        if(q==0)break;
        
        i128 mn=q*w;//当前n中有多少个w
        i128 s_q=(n-mn)/q+1; //分值为q的块大小
        s_q=max128(s_q,1);
        //同时n不能小于w
        i128 s_w=(n-w)/q+1;  
        i128 s=min128(s_q,s_w);
        s=min128(s,(i128)100000);
        
        //算贡献:块递减大小为q 有s个块
        if(s>K){
            i128 a=n;
            i128 last=a-(s-1)*q;//算第s步的n值:注意剪前(s-1)步
            i128 b=a-s*q;//下一轮迭代的n值      
            i128 sx=s*(a+last)/2;
            i128 sx2=a*a*s-a*q*s*(s-1)+q*q*(s-1)*s*(2*s-1)/6;
            ans+=((2*N+1)*sx-sx2)/2;
            n=b;
        }
        else{
            for(i128 i=0;i<s&&n>=w;i++){
                ans+=n*(2*N-n+1)/2;
                n-=q;
            }
        }
    }
    //注意最后<w的块也需要加
    ans+=n*(2*N-n+1)/2;
    write(ans);
    putchar('\n');
}

Symmetry Intervals 2

https://ac.nowcoder.com/acm/contest/108298/H
16进制分块

struct FenwickTree {
      vector<int> tree;
      int size;
      FenwickTree(int n){
            size=n;
            tree.resize(n+1,0);
      }
      void update(int pos, int delta){
            for(;pos<=size;pos+=pos&-pos)
                  tree[pos]^=delta;
      }
      int query(int pos){
            int res=0;
            for(;pos>0;pos-=pos&-pos)
                  res^=tree[pos];
            return res;
      }
};

const int N=1e6+10;
const int M=1<<16;
int n,q;
string str=" ";
int s[N];
us mask[N];
int pre[M],suf[M],in[M];
/*
【翻转操作】
树状数组:处理块的翻转
tag_d:*暴力*处理边界的翻转
*/
int tag_d[N];
void solve(){
    read(n);
	read(q);
	FenwickTree ft(n);
	for(int i=1;i<=n;i++){
		char c=nc();
		if(c=='0' || c=='1') str+=c;
	}
	for(int i=1;i<=n;i++){
		s[i]=str[i]&1;
	}
	for(int i=1;i<=n;i++){
		for(int j=0;(i+j)<=n&&j<16;j++){
			if(s[i+j]) mask[i]|=(1<<j);
			//mask:以每个点为起始点,往下数16个
		}
	}
	//init:算1<<16种块,每一种块的前缀后缀和中间的*连续1子串*产生的贡献
	for(int i=0;i<M;i++){
		int cnt=0;
		bool fl=true;
		for(int j=0;j<16;j++){
			if((i>>j)&1){
				cnt++;
			}
			else{
				if(fl){//前缀
					pre[i]=cnt;
					fl=false;
				}
				else{
					in[i]+=((cnt+1)*cnt)>>1;
				}
				cnt=0;
			}
		}
		if(fl){//全1
			pre[i]=suf[i]=16;
		}
		else{
			suf[i]=cnt;
		}
	}
    while(q--){
		int op;
		read(op);
		if(op==1){
			int l,r;
			read(l);read(r);
			if((r-l)<15){//块内翻转:对于包含了l到r的所有块,都需要暴力标记一下"要翻转"
				for(int i=max(1,l-15);i<=r;i++){
					int u=max(0,l-i);
					int v=min(15,r-i);
					us flip=((1<<(v-u+1))-1)<<u;
					mask[i]^=flip;
				}
			}
			else{
				//左边界
				for(int i=max(1,l-15);i<l;i++){
					int u=max(0,l-i);
					int v=min(15,r-i);
					us flip=((1<<(v-u+1))-1)<<u;
					mask[i]^=flip;
					tag_d[i]^=1;
				}
				//右边界
				for(int i=max(1,r-14);i<=r;i++){
					int u=max(0,l-i);
					int v=min(15,r-i);
					us flip=((1<<(v-u+1))-1)<<u;
					mask[i]^=flip;
				}
				//中间
				ft.update(l,1);
				ft.update(r-14,1);
				tag_d[l]^=1;
				//中间块边界
				
				for(int i=max(1,r-29);i<=r-14;i++){
					tag_d[i]^=1;
				}
				
			}
		}
		else if(op==2){
			int l,a,b;
			read(l);read(a);read(b);
			int tag=ft.query(a)^ft.query(b);
			i64 ans=0;
			int cnt=0;
			for(int i=0;i<l;i+=16){
				us cur=mask[i+a]^mask[i+b];
				if(!tag) cur=~cur;
				if(i+15>=l) cur&=(1<<(l-i))-1;
				//全1
				if(pre[cur]==16){
					cnt+=16;
				}
				else{
					cnt+=pre[cur];
					ans+=(((i64)cnt+1LL)*(i64)cnt)>>1LL;
					ans+=in[cur];
					cnt=suf[cur];
				}
				tag^=tag_d[i+a+1]^tag_d[i+b+1];
			}
			ans+=(((i64)cnt+1LL)*(i64)cnt)>>1LL;
			write(ans);
			putchar('\n');
		}
	}
}
posted @ 2025-02-01 19:57  White_ink  阅读(13)  评论(0)    收藏  举报