HDU 5869 Different GCD Subarray Query 树状数组 + 一些数学背景
http://acm.hdu.edu.cn/showproblem.php?pid=5869
題意:給定一個數組,然后給出若干個詢問,詢問[L, R]中,有多少個子數組的gcd是不同的。
就是[L, R]中不同區間的gcd值,有多少個是不同的。
?
?給個樣例
3 3
7 7 7
1 2
1 3
3 3
?
數學背景:
一個數字和若N個數字不斷GCD,其結果只有loga[i]種,為什么呢?因為可以把a[i]質因數分解,其數目最多是loga[i]個數字相乘。(最小的數字是2,那么loga[i]個2相乘也爆了a[i]了)
所以,考慮以a[i]為結尾的序列,有多少個不同的gcd,可以記錄下。
比如樣例。括號里的依次是gcd值和a[i]這個數字最早與那個位置gcd,其值是val、
1、 3、 4、 6、 9
(1, 1) ? (3, 2) (4, 3) (6, 4) (9, 5)
(1, 1) (1, 2) (2, 3) (3, 4)
(1, 2) (1, 3) ? ?//9這個數字在[3, 5]中一路gcd,就能得到1
這里其實已經維護了左端點了,要求左端點盡量大。
1、為什么要維護左端點,因為答案給出的是[L, R]這段區間的不同gcd,如果你預處理的時候,R和L - 1得到的gcd是不能算進去的。
2、要求左端點盡量大,那是因為我后來要對R排序,盡量往R靠,結果是最優的,(可以參考下求區間不同數字個數的方法。后面再寫。)
?
然后就是套路了。把得到的gcd往左端點里塞。?,這是重點,一開始的時候我把不同的gcd往右端點塞了,這是不對的,因為往又端點塞的話,R與L - 1的gcd就會被你算進去了,所以會wa。
book[x]表示x這個值出現的最右的那個位置。
#include <cstdio> #include <cstdlib> #include <cstring> #include <cmath> #include <algorithm> #define IOS ios::sync_with_stdio(false) using namespace std; #define inf (0x3f3f3f3f) typedef long long int LL;#include <iostream> #include <sstream> #include <vector> #include <set> #include <map> #include <queue> #include <string> const int maxn = 1000000 + 20; const int N = 1000000 + 20; int c[maxn]; int lowbit(int x) {return x & (-x); } void upDate(int pos, int val) {while (pos <= N - 20) {c[pos] += val;pos += lowbit(pos);} } int query(int pos) {int ans = 0;while (pos) {ans += c[pos];pos -= lowbit(pos);}return ans; } int n, q; int a[maxn]; struct node {int L, R;int id;bool operator < (const struct node & rhs) const {return R < rhs.R;} }b[maxn]; int book[maxn]; int ans[maxn]; void work() {memset(book, 0, sizeof book);memset(c, 0, sizeof c);for (int i = 1; i <= n; ++i) {scanf("%d", &a[i]);}for (int i = 1; i <= q; ++i) {scanf("%d%d", &b[i].L, &b[i].R);if (b[i].L > b[i].R) swap(b[i].L, b[i].R);b[i].id = i;}sort(b + 1, b + 1 + q);int cur = 1; // for (int i = 1; i <= q; ++i) { // cout << b[i].L << " " << b[i].R << endl; // }for (int i = 1; i <= q; ++i) {for (int j = cur; j <= b[i].R; ++j) {int x = a[cur];for (int k = cur; k >= 1; k--) {if (book[x] < k) { //左端點只能盡量右靠,左邊的不管,樣例:7 7 7if (book[x]) { //樹狀數組不能從0開始upDate(book[x], -1);}book[x] = k; //不同的gcd,壓到左端點upDate(book[x], 1);} // if (book[x] > k) while(1); // book[x] = k; // upDate(book[x], 1);if (k == 1) break;if (x == 1) break;x = __gcd(x, a[k - 1]);}cur++;}ans[b[i].id] = query(b[i].R) - query(b[i].L - 1);}for (int i = 1; i <= q; ++i) {printf("%d\n", ans[i]);}} int main() { #ifdef localfreopen("data.txt","r",stdin); #endifwhile (scanf("%d%d", &n, &q) != EOF) work();return 0; } View Code?
關于樹狀數組
3.1、求逆序對:首先先把數據離散化,因為樹狀數組覆蓋的區間是1—max這樣的,不離散的話開不到那么大的數組。例如離散后是:5、2、1、4、3、思路是:插入5,add(5,1),把pos為5的地方設置為1,然后ans += i - get_sum(5);? i的意思是當前插入了i個數,然后get_sum()是當前有多少個數比5少,其實就是問1—5之間存在多少個數,那當然是比5小的啦。一減,就是關于5逆序對個數。eg:關于2的逆序對個數是1對。
LL get_inversion (int a[],int lena)? //求逆序對個數
{
??? LL ans = 0;//逆序對一般都很多,需要用LL
??? for (int i=1;i<=lena;++i)??? // a[]={5,2,1,4,3} ans=6;
??? {?????????????????? // a[]={5,5,5,5,5} ans=0; 逆序對嚴格大于
??????? add(a[i],1);? ans += i-get_sum(a[i]);
??? }
??? return ans;
}
關于數據離散化,可以開一個結構體,保存val和pos,然后根據val排序一下,根據pos從小到大賦值即可。for (int i=1;i<=n;++i) a[book[i].pos]=i; //從小到大離散。a[3]=1,a[1]=2等等
{9,1,0,5,4}? 離散化后 ?{5,2,1,4,3}
?
3.2、求解區間不同元素個數,離線算法。復雜度O(q + nlog(n))
設樹狀數組的意義是:1--pos這個段區間的不同元素的種類數。怎么做?就是add(pos,1);在這個位置中+1,就是說這個位置上元素種類+1。然后先把詢問按R遞增的順序排序。因為這里是最優的,我每次盡量往R靠,使得查詢不重不漏。什么意思呢?就是假如有:2、1、3、5、1、7的話。一開始的[1,4]這段數字全部壓進樹狀數組,用個數組book[val],表示val這個元素出現的最右的位置,因為我們需要刪除重復的,也是要盡量往右靠。到達pos=5這個位置的時候,注意了,因為1是出現過的book[1] = 2,所以我們要做的是把2這個位置出現元素的種類數-1,就是add(book[1],? -1)。然后把第五個位置出現的元素種類數+1,就是add(5,1)。為什么呢?因為你盡量把種類往右靠,因為我們的R是遞增的,這樣,你使得查詢[4,6]成為可能,因為我那個1加入來了,而不是一直用pos=2那個位置的1,再者,查詢[4,7]的話,一樣的意思,因為中間的1進來了。所以我們因為盡量往右靠,畢竟我們都把query按R排序了。還有這個只能離線,一直預處理ans[i]表示第i個詢問的ans。更新到[4,7]后,查詢[1,2]已經不可能了,因為很明顯,pos=2這個位置已經被刪除了。
void work ()
{
??? scanf("%d",&n);
??? for (int i=1;i<=n;++i) scanf("%d",&a[i]);
??? int q; scanf("%d",&q);
??? for (int i=1;i<=q;++i)
??? {
??????? scanf("%d%d",&query[i].L,&query[i].R);
??????? query[i].id = i; //記錄ans
??? }
??? sort(query+1,query+1+q);
??? int cur = 1;
??? for (int i=1;i<=q;++i)
??? {
??????? for (int j=cur;j<=query[i].R;++j)
??????? {
??????????? if (book[a[j]])
??????????????? add(book[a[j]],-1); //del 這個位置
??????????? book[a[j]]=j; //更新這個位置的最右值
??????????? add(j,1); //這個位置出現了新元素
??????? }
??????? cur = query[i].R+1; //表示現在預處理到這個位置了。不能往回查,而且也不會往回
??????? ans[query[i].id] = get_sum(query[i].R) - get_sum(query[i].L-1); //區間減法
??? }
??? for (int i=1;i<=q;++i)
??????? printf ("%d\n",ans[i]);
}
轉載于:https://www.cnblogs.com/liuweimingcprogram/p/6050569.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的HDU 5869 Different GCD Subarray Query 树状数组 + 一些数学背景的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: EasyUI 表格点击右键添加或刷新
- 下一篇: js 获取多少天前