「演算法筆記」從【伊卡洛斯】談資料分治
阿新 • • 發佈:2019-01-29
引子
有時候,一些類似毒瘤資料結構的題目乍一看似乎十分令人頭疼。它們往往是要求你統計某種元素的個數。實際上,這些題目有些不是資料結構題,而是使用了資料分治的思想對基本的程式進行優化。
例題——伊卡洛斯
題目大意:給定一個數列,每次詢問一個區間內數的乘積的因數個數的結果。,
題解
性質:()
樸素演算法
首先一看到這種題目就想到莫隊。(這是直覺)
我們記下區間中每一個素因子的出現次數即可。
詳見程式碼。
時間複雜度 。
其中函式的意義是且。
()
期望得分分。
程式碼
#include <bits/stdc++.h>
#define K 1000000007
#define M 1000000
#define N 100000
#define L 20
#define pb push_back
using namespace std;
typedef long long ll;
vector <int> pri,fen[N|1],cnt[N|1];
int n,m,lft,rht,ans,out[N|1],reg[M|1],num[N*L|1];
bool isp[M|1];
struct query {
int ord,lbnd,rbnd,lump;
bool operator < (const query &o) const {
if(lump==o.lump)
return rbnd<o.rbnd;
return lump<o.lump;
}
} q[N|1 ];
int fastpow(int x,int y) {
int res=1,cur=x;
for(int i=0;(1<<i)<=y;i++) {
if((1<<i)&y)
res=1ll*res*cur%K;
cur=1ll*cur*cur%K;
}
return res;
}
void initpri() {
memset(isp,1,sizeof(isp)),isp[0]=isp[1]=0;
for(int i=2;i<=M;i++) {
if(isp[i]) pri.pb(i);
for(int j=0;j<pri.size()&&i*pri[j]<=M;j++) {
isp[i*pri[j]]=0;
if(i%pri[j]==0) break;
}
}
for(int i=1;i<=N*L;i++)
num[i]=fastpow(i,K-2);
}
void readnum() {
scanf("%d%d",&n,&m);
for(int t,i=1;i<=n;i++) {
scanf("%d",&t);
for(int j=0,k=0;j<pri.size()&&pri[j]*pri[j]<=t;j++,k=0) if(t%pri[j]==0) {
for(;t%pri[j]==0;t/=pri[j],k++);
fen[i].pb(pri[j]),cnt[i].pb(k);
} if(t>1) fen[i].pb(t),cnt[i].pb(1);
}
}
void mosolve() {
int l=sqrt(n);
for(int i=1;i<=m;i++) {
scanf("%d%d",&q[i].lbnd,&q[i].rbnd);
q[i].ord=i,q[i].lump=q[i].lbnd/l;
}
sort(q+1,q+1+m);
for(int i=1;i<=M;i++)
reg[i]=1;
lft=rht=ans=1;
for(int i=0;i<cnt[1].size();i++) {
reg[fen[1][i]]+=cnt[1][i];
ans=1ll*ans*(reg[fen[1][i]])%K;
}
for(int i=1;i<=m;i++) {
for(;lft>q[i].lbnd;lft--) {
for(int j=0;j<cnt[lft-1].size();j++) {
ans=1ll*ans*num[reg[fen[lft-1][j]]]%K;
reg[fen[lft-1][j]]+=cnt[lft-1][j];
ans=1ll*ans*reg[fen[lft-1][j]]%K;
}
}
for(;rht<q[i].rbnd;rht++) {
for(int j=0;j<cnt[rht+1].size();j++) {
ans=1ll*ans*num[reg[fen[rht+1][j]]]%K;
reg[fen[rht+1][j]]+=cnt[rht+1][j];
ans=1ll*ans*reg[fen[rht+1][j]]%K;
}
}
for(;lft<q[i].lbnd;lft++) {
for(int j=0;j<cnt[lft].size();j++) {
ans=1ll*ans*num[reg[fen[lft][j]]]%K;
reg[fen[lft][j]]-=cnt[lft][j];
ans=1ll*ans*reg[fen[lft][j]]%K;
}
}
for(;rht>q[i].rbnd;rht--) {
for(int j=0;j<cnt[rht].size();j++) {
ans=1ll*ans*num[reg[fen[rht][j]]]%K;
reg[fen[rht][j]]-=cnt[rht][j];
ans=1ll*ans*reg[fen[rht][j]]%K;
}
}
out[q[i].ord]=ans;
}
for(int i=1;i<=m;i++)
printf("%d\n",out[i]);
}
int main() {
initpri();
readnum();
mosolve();
return 0;
}
優化
我們對這些素因數分情況討論。
當它們小於等於時,我們使用字首和記錄它們的出現個數。
當它們大於時,我們使用剛才的方法計算他們的出現個數。
同時我們發現,小於等於的數僅有一個的素因子。
所以,我們每次移動區間時只需將陣列中的一個數改變。
時間複雜度。
其中表示小於等於的素數有幾個。
期望得分分。
程式碼
#include <cmath>
#include <cstdio>
#include <algorithm>
#define mxn 100000
#define mxm 1000000
#define mxk 168
#define hfm 1000
const int mod=1e9+7;
using namespace std;
bool inp[mxn|1];
int n,m,k,l,r,ans,lump,a[mxn|1],pri[mxm|1],inv[mxn|1];
int sum[mxn|1][mxk|1],cnt[mxm|1],res[mxn|1];
struct query {
int id,l,r;
bool operator<(const query&o) const {
return l/lump==o.l/lump?r<o.r:l/lump<o.l/lump;
}
} q[mxn|1];
void prework() {
lump=sqrt(n+0.5);
for(int i=2;i<=hfm;i++)
if(!inp[i]) {
pri[++k]=i;
for(int j=2*i;j<=hfm;j+=i)
inp[j]=1;
}
inv[0]=inv[1]=1;
for(int i=2;i<=n;i++)
inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
}
void add(int i,int x) {
if(a[i]!=1) {
ans=1ll*ans*inv[cnt[a[i]]+1]%mod;
cnt[a[i]]+=x;
ans=1ll*ans*(cnt[a[i]]+1)%mod;
}
}
int main() {
scanf("%d%d",&n,&m);
prework();
for(int i=1;i<=n;i++) {
scanf("%d",&a[i]);
for(int j=1;j<=mxk;j++) {
sum[i][j]=sum[i-1][j];
while(a[i]%pri[j]==0) {
a[i]/=pri[j];
sum[i][j]++;
}
}
}
for(int i=1;i<=m;i++)
q[i].id=i,scanf("%d%d",&q[i].l,&q[i].r);
sort(q+1,q+m+1);
l=2,r=1,ans=1;
for(int i=1;i<=n;i++) {
while(l>q[i].l) add(--l,1);
while(r<q[i].r) add(++r,1);
while(l<q[i].l) add(l++,-1);
while(r>q[i].r) add(r--,-1);
int tmp=1;
for(int i=1;i<=mxk;i++)
tmp=1ll*tmp*(sum[r][i]-sum[l-1][i]+1)%mod;
res[q[i].id]=1ll*tmp*ans%mod;
}
for(int i=1;i<=n;i++)
printf("%d\n",res[i]);
return 0;
}
小試牛刀
題目連結:Graph
題目大意:給定一個帶權無向圖,每個節點有黑白兩種顏色。有兩種操作:
1.詢問兩端顏色分別為u和v的邊的權值和
2.修改某點的顏色
題解
程式碼
#include <cmath>
#include <cstdio>
#include <cstring>
#include <map>
#define mxn 100000
#define pii pair<int,int>
#define ppi pair<pii,int>
#define mkp make_pair
typedef long long ll;
using namespace std;
char op[9]; int q,x,y;
map<pii,ll> mp;
ll ans[3],sum[mxn|1][2],u[mxn|1],v[mxn|1],w[mxn|1],wei[mxn<<1|1];
int t,n,m,col[mxn|1],typ[mxn|1],d[mxn|1];
int siz,tot,lnk[mxn|1][2],ter[mxn<<1|1],nxt[mxn<<1|1];
void add(int u,int v,ll w,int b) {
ter[++tot]=v; wei[tot]=w;
nxt[tot]=lnk[u][b]; lnk[u][b]=tot;
}
int main() {
for(t=1;~scanf("%d%d",&n,&m);t++) {
for(int i=1;i<=n;i++) scanf("%d",col+i); mp.clear();
for(int u,v,w,i=1;i<=m;i++) {
scanf("%d%d%d",&u,&v,&w);
if(u>v) swap(u,v);
mp[mkp(u,v)]+=w;
} m=