REQ 【CodeForces - 594D】【樹狀陣列+離線查詢+區間思維】
題目連結
很好的一道題,昨晚上推的,今天由於程式碼能力太弱敲了半天,再不斷的找到自己思維的BUG,於是RE了一發、T了一發、WA了一發,就Ac了,還不錯,那我們來講解一下題目的思路。
我們知道對於一個值的尤拉函式值,就是它的值去乘上它所有的質數-1除以質數:如果設某數的質數為p,那麼其尤拉值就等於原值*(p-1)/p,其中,我們的“/p”操作得用逆元來解,所以剩下的就是怎麼處理儲存區間中不同種類的質數們了,所以,遇上這種問題,並且還是沒有更新的,我就想到了把查詢離線的做法。
知道每個數的質數,我們先預處理一下,對於所有1e6以內的數,我們先處理所有的質數,用質數篩
void init() //把1~1000000每個數的質因子寫出來 { memset(Prime, 0, sizeof(Prime)); //0是質數、1非質數(合數) Prime[0] = Prime[1] = 1; cnt = 0; for(int i=2; i<maxV; i++) { if(Prime[i] == 0) { for(int j=2*i; j<maxV; j+=i) { Prime[j] = 1; } sushu[++cnt] = i; } } for(int i=1; i<maxV; i++) have_p[i].clear(); for(int i=2; i<maxV; i++) get_have(i, i); }
其中,get_have()是用來得到每個數對應的質因子的函式:
void get_have(int index, int v) { int i=1; while(v>1 && i<=cnt) { if(sushu[i] * sushu[i] > v) { have_p[index].push_back(v); return; } if(v%sushu[i] == 0) { have_p[index].push_back(sushu[i]); while(v%sushu[i] == 0) v/=sushu[i]; } i++; } }
然後,我們既然已經處理完了質因子了,我們接下來只需要處理每個質因子前後出現的位置就行了,為什麼要這麼處理?我們查詢的是一個區間,其中可能會有一串相等的質因子,我們得處理它們,所以,我們的查詢就有了目的,我們得預處理每個質因子上次出現的位置,以及下次出現的位置,對於查詢區間的右移,我們只需要把目前節點的出現的質因子用後面位置的質因子代替就行,譬如,現在的區間是[ql, qr],我們的下一個查詢區間是[ql+1, qr],那麼ql的點就是區間外的點了(我對查詢區間按照左端點升序排序),那麼這個點旗下有些它的質因子,我們把它的質因子向後推就是了,如果之後的區間並沒有出現這樣的質因子,我們將它的r[][](也就是下一次出現的位置,第i個位置的點,它的第j個質因子)放到(N+1)的位置上去,同理,對於第一個點也是這樣的處理方式。
這麼就有了l、r的思想了,l記錄字首,如果字首是0,表明它是第一次出現,如果字尾是N+1,表明它之後就沒有這個質數的參與了,所以,我們列寫下預處理的函式:
void init2()
{
for(int i=1; i<=N; i++) trie[i] = 1;
memset(l, 0, sizeof(l));
int flag[maxV]; memset(flag, 0, sizeof(flag));
for(int i=1; i<=N; i++)
{
int len = (int)have_p[a[i]].size();
for(int j=0; j<len; j++)
{
l[i][j] = flag[have_p[a[i]][j]];
flag[have_p[a[i]][j]] = i;
}
}
for(int i=1; i<maxV; i++) flag[i] = N + 1;
for(int i=N; i>=1; i--)
{
int len = (int)have_p[a[i]].size();
for(int j=0; j<len; j++)
{
r[i][j] = flag[have_p[a[i]][j]];
flag[have_p[a[i]][j]] = i;
}
}
}
剩下的區間查詢就顯得很簡單了,我們的樹狀陣列記錄的是字首積,寫法也有所不同,記錄每個質因子的時候,我們存進去的是(p-1)/p的值,“/p”用的是逆元。然後,原數的字首積我們就直接用字首和的思想了,當然除的時候還是用逆元的思想。
對了,還有人會問我,為什麼可以把質因子存起來,這麼就很簡單了,2*3*5*7*11*13*17*19=9699690(9e6),也就是說,1e6內的數最多的質因子種類數就是8個,所以,複雜度足夠。
#pragma comment(linker, "/STACK:102400000,102400000")
#include <iostream>
#include <cstdio>
#include <cmath>
#include <string>
#include <cstring>
#include <algorithm>
#include <limits>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#define lowbit(x) ( x&(-x) )
#define pi 3.141592653589793
#define e 2.718281828459045
using namespace std;
typedef unsigned long long ull;
typedef long long ll;
const int mod = 1e9+7;
const int maxN = 2e5+5;
const int maxV = 1e6+2;
int N, Q, Prime[maxV], sushu[maxV], cnt; //sushu[]記錄質素們,cnt記錄到200000一共有多少個素數
ll a[maxN], trie[maxN], pre[maxN], ans[maxN];
vector<ll> have_p[maxV];
int l[maxN][10], r[maxN][10]; //對應點i的第j個質因子左、右分別出現位置,沒有就是0、(N+1)
struct Ques //離線查詢
{
int l, r, id;
Ques(int a=0, int b=0, int c=0):l(a), r(b), id(c) {}
}qes[maxN];
bool cmp(Ques e1, Ques e2) { return e1.l < e2.l; }
ll fast_mi(ll x, int y) //快速冪處理逆元
{
ll ans = 1;
while(y)
{
if(y & 1) ans = ans*x%mod;
x = x*x%mod;
y>>=1;
}
return ans;
}
void update(int x, ll val)
{
if(x == 0) return;
while(x<=N)
{
trie[x] = trie[x]*val%mod;
x += lowbit(x);
}
}
ll query(int x)
{
ll ans = 1;
while(x>0)
{
ans = ans*trie[x]%mod;
x -= lowbit(x);
}
return ans;
}
void get_have(int index, int v)
{
int i=1;
while(v>1 && i<=cnt)
{
if(sushu[i] * sushu[i] > v)
{
have_p[index].push_back(v);
return;
}
if(v%sushu[i] == 0)
{
have_p[index].push_back(sushu[i]);
while(v%sushu[i] == 0) v/=sushu[i];
}
i++;
}
}
void init() //把1~1000000每個數的質因子寫出來
{
memset(Prime, 0, sizeof(Prime)); //0是質數、1非質數(合數)
Prime[0] = Prime[1] = 1;
cnt = 0;
for(int i=2; i<maxV; i++)
{
if(Prime[i] == 0)
{
for(int j=2*i; j<maxV; j+=i)
{
Prime[j] = 1;
}
sushu[++cnt] = i;
}
}
for(int i=1; i<maxV; i++) have_p[i].clear();
for(int i=2; i<maxV; i++) get_have(i, i);
}
void init2()
{
for(int i=1; i<=N; i++) trie[i] = 1;
memset(l, 0, sizeof(l));
int flag[maxV]; memset(flag, 0, sizeof(flag));
for(int i=1; i<=N; i++)
{
int len = (int)have_p[a[i]].size();
for(int j=0; j<len; j++)
{
l[i][j] = flag[have_p[a[i]][j]];
flag[have_p[a[i]][j]] = i;
}
}
for(int i=1; i<maxV; i++) flag[i] = N + 1;
for(int i=N; i>=1; i--)
{
int len = (int)have_p[a[i]].size();
for(int j=0; j<len; j++)
{
r[i][j] = flag[have_p[a[i]][j]];
flag[have_p[a[i]][j]] = i;
}
}
}
int main()
{
init();
while(scanf("%d", &N)!=EOF)
{
pre[0] = 1;
for(int i=1; i<=N; i++) { scanf("%lld", &a[i]); pre[i] = pre[i-1] * a[i]%mod; } //字首積處理
scanf("%d", &Q);
for(int i=1; i<=Q; i++) { scanf("%d%d", &qes[i].l, &qes[i].r); qes[i].id = i; }
sort(qes+1, qes+1+Q, cmp);
init2();
int i = 1, j = 1;
for(int q=1; q<=Q; q++)
{
while(j<=qes[q].r)
{
int len = (int)have_p[a[j]].size();
for(int k=0; k<len; k++) if(l[j][k] == 0) update(j, (have_p[a[j]][k] - 1)*fast_mi(have_p[a[j]][k], mod-2)%mod);
j++;
}
while(i<qes[q].l)
{
int len = (int)have_p[a[i]].size();
for(int k=0; k<len; k++) update(r[i][k], (have_p[a[i]][k] - 1)*fast_mi(have_p[a[i]][k], mod-2)%mod);
i++;
}
ans[qes[q].id] = pre[qes[q].r]*fast_mi(pre[qes[q].l-1], mod-2)%mod*query(qes[q].r)%mod*fast_mi(query(qes[q].l-1), mod-2)%mod;
}
for(int i=1; i<=Q; i++) printf("%lld\n", ans[i]);
}
return 0;
}