题集链接
OP
感谢学长的讲解与付出;
感谢ph和zsl两位大佬的指导与讨论;
夹带私货:
此节课的代表性的内容已经整理到自己的贴子中:【笔记】数据结构
树状数组总的来说可以快速对数组进行单点更改和区间查询(或通过维护前缀实现区间更改和单点查询),适用于一些边改边查的情况;
A Ultra-QuickSort
题目大意
在一个马桶搋子图片 的陪同下 求逆序对;
有定义:若 i < j ,且 a i > a j a_i\gt a_j a i > a j ,则构成一对逆序对;
思路
有两种做法,暂且称之为归并法和树状数组法,大体相同,但树状数组法更加直观,具体做法可以参考PPT,下面简述一下归并法;
在归并排序的过程中,我们会得到两个有序递增数组(暂称为a数组和b数组),其中保证左数组(a)的下标均小于右数组(b);
在通过双指针将两个数组合并时,若对 b j b_j b j 存在 a i < b j < a i + 1 a_i\lt b_j\lt a_{i+1} a i < b j < a i + 1 ,则说明 a数组中从 a i + 1 a_{i+1} a i + 1 后的每个数都可以和 b j b_j b j 构成逆序对;
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 #include <iostream> using namespace std;typedef long long ll; ll a[500005 ]; ll tmp[500005 ]; ll ans;void qs (int l, int r) { if (l == r) return ; int mid = l + r >> 1 ; qs (l, mid); qs (mid + 1 , r); int i = l, j = mid + 1 , c = 0 ; while (i <= mid && j <= r) if (a[i] <= a[j]) tmp[c++] = a[i++]; else tmp[c++] = a[j++],ans+=mid-i+1 ; while (i <= mid) tmp[c++] = a[i++]; while (j <= r) tmp[c++] = a[j++]; for (i = l, j = 0 ; i <= r; i ++, j ++ ) a[i] = tmp[j]; }int main () { int n; while (~scanf ("%d" , &n) && n) { for (int i = 1 ; i <= n; i++) { scanf ("%lld" , &a[i]); } ans=0 ; qs (1 ,n); printf ("%lld\n" ,ans); } }
B Stars
题目大意
给出星星坐标,求出以每个星星为原点,其第三象限(含象限边界)内的星星数;
思路
由于输入是将星星按先以纵坐标升序,再以横坐标升序 顺序输入的,我们可以保证对于新输入的星星,目前在场的所有星星纵坐标皆小于等于它,只需要判断横坐标小于等于它的星星有多少颗即可;
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <iostream> using namespace std;typedef long long ll;int lowbit (int x) {return x&-x;}int c[32003 ],n=32001 ,ans[15003 ];int sum (int x) { int ans=0 ; for (;x;x-=lowbit (x))ans+=c[x]; return ans; }void add (int x,int v) { for (;x<=n;x+=lowbit (x))c[x]+=v; }int main () { int m,x,y; cin>>m; for (int i=1 ;i<=m;i++) { cin>>x>>y; x++,y++; ans[sum (x)]++; add (x,1 ); } for (int i=0 ;i<m;i++) { printf ("%d\n" ,ans[i]); } }
C Mobile phones
题目大意
对一个二维平面进行单点数值修改和区块数值总和查询;
思路
模拟
通过二维树状数组维护即可~
板子已经更新到帖子中;
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <iostream> using namespace std;typedef long long ll;int c[1025 ][1025 ], n = 1024 ;int lowbit (int x) { return x & -x; }int sum (int x, int y) { int ans = 0 ; for (int i = x; i > 0 ; i -= lowbit (i)) for (int j = y; j > 0 ; j -= lowbit (j)) ans += c[i][j]; return ans; }void add (int x, int y, int v) { for (int i = x; i <= n; i += lowbit (i)) for (int j = y; j <= n; j += lowbit (j)) c[i][j] += v; }int main () { int o, x, y, a, l, b, r, t; while (~scanf ("%d" , &o) && o != 3 ) { if (o == 1 ) { scanf ("%d%d%d" , &x, &y, &a); x++, y++; add (x,y, a); } else if (o == 2 ) { scanf ("%d%d%d%d" , &l, &b, &r, &t); l++, b++, r++, t++; int ans = sum (r,t)-sum (r,b-1 )+ sum (l-1 ,b-1 )-sum (l-1 ,t); printf ("%d\n" , ans); } else if (o == 0 ) scanf ("%d" , &a); } }
D Cows
题目大意
给出每头牛对应的区间,对每头牛求出有多少个区间覆盖(至多一个端点相同)它的区间;
思路
可以先按右端点降序,左端点升序的顺序排序,若有两区间完全重合,同时保证了两区间排序后必相邻;
排序后对与每头牛,可以保证所有右端点大于其的牛都在它的左侧,如果我们统计的同时在数组中添加该头牛的左端点,则对于每头牛只需要计数目前在其左端点以左有多少个左端点即可;
注意特判重合区间;
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #include <iostream> #include <algorithm> #include <string.h> using namespace std;typedef long long ll;int c[100005 ],ans[100005 ];int N=100001 ; pair<pair<int ,int >,int >pir[100005 ];int lowbit (int x) { return x & -x; }int sum (int x) { int ans=0 ; for (;x;x-=lowbit (x))ans+=c[x]; return ans; }void add (int x,int v) { for (;x<=N;x+=lowbit (x))c[x]+=v; }bool cmp1 (pair<pair<int ,int >,int >a,pair<pair<int ,int >,int >b) { if (a.first.second==b.first.second) { return a.first.first<b.first.first; } return a.first.second>b.first.second; }int main () { int n; while (~scanf ("%d" ,&n)&&n) { memset (c,0 ,sizeof c); for (int i=1 ;i<=n;i++) { scanf ("%d%d" ,&pir[i].first.first,&pir[i].first.second); pir[i].first.first++,pir[i].first.second++; pir[i].second=i; } sort (pir+1 ,pir+1 +n,cmp1); for (int i=1 ;i<=n;i++) { if (pir[i].first==pir[i-1 ].first)ans[pir[i].second]=ans[pir[i-1 ].second]; else ans[pir[i].second]=sum (pir[i].first.first); add (pir[i].first.first,1 ); } for (int i=1 ;i<=n;i++) { printf ("%d" ,ans[i]); if (i!=n)printf (" " ); else printf ("\n" ); } } }
E Get Many Persimmon Trees
题目大意
给出总区域大小,每棵树的坐标和可选区域的大小,求所有可选区域中树的最大值;
思路
不需要树状数组,用二维前缀和穷举即可;
做题的时候没意识到这一点,代码中用了树状数组;
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 #include <iostream> #include <algorithm> #include <string.h> using namespace std;typedef long long ll;int c[102 ][102 ], X = 100 ,Y=100 ;int lowbit (int x) { return x & -x; }int sum (int x, int y) { int ans = 0 ; for (int i = x; i > 0 ; i -= lowbit (i)) for (int j = y; j > 0 ; j -= lowbit (j)) ans += c[i][j]; return ans; }void add (int x, int y, int v) { for (int i = x; i <= X; i += lowbit (i)) for (int j = y; j <= Y; j += lowbit (j)) c[i][j] += v; }int main () { int n; int w,h,x,y,s,t; while (~scanf ("%d" ,&n)&&n) { memset (c,0 ,sizeof c); scanf ("%d%d" ,&w,&h); for (int i=1 ;i<=n;i++) { scanf ("%d%d" ,&x,&y); add (x,y,1 ); } scanf ("%d%d" ,&s,&t); int ans=0 ; for (int i=s;i<=w;i++) { for (int j=t;j<=h;j++) { ans=max (ans,sum (i,j)-sum (i-s,j)+sum (i-s,j-t)-sum (i,j-t)); } } printf ("%d\n" ,ans); } }
F Matrix
题目大意
二维区间修改,单点查询;
思路
可以通过树状数组维护二位前缀和实现;
实际上不需要做翻转的动作,只需要判断奇偶即可;
注意输出要求;
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include <iostream> #include <algorithm> #include <string.h> using namespace std;typedef long long ll;int c[1003 ][1003 ], X = 1000 , Y = 1000 ;int lowbit (int x) { return x & -x; }int sum (int x, int y) { int ans = 0 ; for (int i = x; i > 0 ; i -= lowbit (i)) for (int j = y; j > 0 ; j -= lowbit (j)) ans += c[i][j]; return ans; }void add (int x, int y, int v) { for (int i = x; i <= X; i += lowbit (i)) for (int j = y; j <= Y; j += lowbit (j)) c[i][j] += v; }int main () { int t, n, x, x1, y1, x2, y2; char o; cin >> t; while (t--) { scanf ("%d%d" , &n, &x); memset (c, 0 , sizeof c); for (int i = 1 ; i <= x; i++) { cin>>o; if (o == 'C' ) { scanf ("%d%d%d%d" , &x1, &y1, &x2, &y2); add (x1, y1, 1 ); add (x2 + 1 , y2 + 1 , 1 ); add (x1, y2 + 1 , -1 ); add (x2 + 1 , y1, -1 ); } else if (o == 'Q' ) { scanf ("%d%d" , &x1, &y1); printf ("%d\n" , sum (x1, y1) & 1 ); } } printf ("\n" ); } }
G MooFest
题目大意
给定每头牛的位置x i x_i x i 和音量v i v_i v i ,定义两牛i , j i,j i , j 交流所需要的代价为a b s ( x i − x j ) ⋅ m a x ( v i , v j ) abs(x_i-x_j)\cdotp max(v_i,v_j) a b s ( x i − x j ) ⋅ m a x ( v i , v j ) ,求所有牛对(N(N-1)/2对)进行互相交流的总代价;
思路
我们可以按v升序排序,可以保证每头牛j 和前面交流时的m a x ( v i , v j ) = v j max(v_i,v_j)=v_j m a x ( v i , v j ) = v j ;
接下来需要处理a b s ( x i − x j ) abs(x_i-x_j) a b s ( x i − x j ) :
我们可以维护前面所有牛的坐标和 s u m x sumx s u m x ,再通过树状数组求出前面牛中比此牛坐标小的坐标之和 s s s 和头数 k k k ;
那么此时的 ∑ i = 1 i < j a b s ( x i − x j ) = s u m x − s − x j ⋅ ( j − 1 − k ) + x j ⋅ k − s \sum_{i=1}^{i<j}abs(x_i-x_j)=sumx-s-x_j\cdotp (j-1-k)+x_j\cdotp k-s ∑ i = 1 i < j a b s ( x i − x j ) = s u m x − s − x j ⋅ ( j − 1 − k ) + x j ⋅ k − s ;
即可求出此牛和前面所有牛交流所用代价;
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 #include <iostream> #include <algorithm> using namespace std;typedef long long ll; pair<int ,ll> c[20004 ];int N=20000 ; pair<int ,ll>pir[20004 ];int lowbit (int x) { return x & -x; }pair<int ,ll> sum (int x) { pair<int ,ll> ans =make_pair (0 ,0 ); for (int i = x; i > 0 ; i -= lowbit (i)) ans.second += c[i].second,ans.first+=c[i].first; return ans; }void add (int x, pair<int ,ll> v) { for (int i = x; i <=N; i += lowbit (i)) c[i].first += v.first,c[i].second+=v.second; }int main () { int n; cin>>n; for (int i=1 ;i<=n;i++) { scanf ("%d%lld" ,&pir[i].first,&pir[i].second); } sort (pir+1 ,pir+1 +n); ll sumx=0 ,ans=0 ; for (int i=1 ;i<=n;i++) { pair<int ,ll>res=sum (pir[i].second); ans+=pir[i].first* ((sumx-res.second-pir[i].second*((i-1 )-res.first))+pir[i].second*res.first-res.second); add (pir[i].second,make_pair (1 ,pir[i].second)); sumx+=pir[i].second; } printf ("%lld\n" ,ans); }
H Disharmony Trees
思路
和G类似~
另外地,此题需要将坐标和高度转换为顺序;
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 #include <iostream> #include <algorithm> #include <string.h> using namespace std;typedef long long ll; pair<ll,ll> c[100005 ]; pair<pair<ll,ll>,pair<ll,ll> > te[100005 ];int N = 100000 ;int lowbit (int x) { return x & -x; }pair<ll,ll> sum (int x) { pair<ll,ll> ans = make_pair (0 ,0 ); for (int i = x; i > 0 ; i -= lowbit (i)) ans.second += c[i].second,ans.first+=c[i].first; return ans; }void add (int x,int v) { for (int i = x; i <= N; i += lowbit (i)) c[i].second += v,c[i].first++; }bool cmp (pair<pair<ll,ll>,pair<ll,ll> >a,pair<pair<ll,ll>,pair<ll,ll> >b) { return a.first.second<b.first.second; }int main () { int n; while (~scanf ("%d" ,&n)) { memset (c,0 ,sizeof c); for (int i=1 ;i<=n;i++) { scanf ("%lld%lld" ,&te[i].first.first,&te[i].first.second); } sort (te+1 ,te+1 +n); for (int i=1 ;i<=n;i++) { if (te[i].first.first==te[i-1 ].first.first) { te[i].second.first=te[i-1 ].second.first; } else te[i].second.first=i; } sort (te+1 ,te+1 +n,cmp); for (int i=1 ;i<=n;i++) { if (te[i].first.second==te[i-1 ].first.second) { te[i].second.second=te[i-1 ].second.second; } else te[i].second.second=i; } ll ans=0 ,sumx=0 ; for (int i=n;i>=1 ;i--) { pair<ll,ll>tmp=sum (te[i].second.first); ans+=(sumx-tmp.second-te[i].second.first*(n-i-tmp.first)+tmp.first*te[i].second.first-tmp.second)*te[i].second.second; sumx+=te[i].second.first; add (te[i].second.first,te[i].second.first); } printf ("%lld\n" ,ans); } }
I KiKi’s K-Number
题目大意
对于一个容器,有三种操作:
加入数字e,删除数字e,查询大于a的第k个数;
思路
大体上可以用桶排的思路进行加入和删除维护;
对于查询,我们可以使用二分的思想快速确定;
具体看代码~
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <iostream> #include <algorithm> #include <string.h> using namespace std;typedef long long ll;int c[100005 ], N = 100001 ;int lowbit (int x) { return x & -x; }int sum (int x) { int ans = 0 ; for (int i = x; i > 0 ; i -= lowbit (i)) ans += c[i]; return ans; }void add (int x,int v) { for (int i = x; i <= N; i += lowbit (i)) c[i] += v; }int main () { int m,p,e,k; while (~scanf ("%d" ,&m)) { memset (c,0 ,sizeof c); while (m--) { scanf ("%d%d" ,&p,&e); e++; if (p==0 ) { add (e,1 ); } else if (p==1 ) { if (sum (e)-sum (e-1 )) { add (e,-1 ); } else printf ("No Elment!\n" ); } else { scanf ("%d" ,&k); int l=e,r=N+1 ; while (l<r) { int mid=l+r>>1 ; if (sum (mid)-sum (e)>=k)r=mid; else l=mid+1 ; } if (l==N+1 ) printf ("Not Find!\n" ); else printf ("%d\n" ,l-1 ); } } } }
J Reverse Prime
题目大意
定义了“反向指数”,要求进行两种操作:
删除反向质数e,查询前 i 个反向质数的质因数数量之和;
思路
具体查询过程和 I 题类似;
对于一个被删除的的反向质数,我们可以标记删除,并在维护质因数数量的树状数组中的那一位清零(删除等量的质因数);
对于查询操作,使用二分思想,找到该位之前满足剩余 i 个的位置;
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 #include <iostream> #include <algorithm> #include <string.h> #include <math.h> #include <map> using namespace std;typedef long long ll;int c[80004 ],N = 78500 , del[80004 ];int lowbit (int x) { return x & -x; }ll sum (int * c,int x) { ll ans =0 ; for (int i = x; i > 0 ; i -= lowbit (i)) ans += c[i]; return ans; }void add (int *c,int x,int v) { for (int i = x; i <= N; i += lowbit (i)) c[i]+= v; }int n[1000006 ],p[80004 ],ct[80004 ],rp[80004 ]; map<int ,int >mp;int main () { int cp=0 ; for (int i=2 ;i<1000000 ;i++) { if (!n[i])p[cp++]=i; for (int j=0 ;j<cp&&p[j]*i<1000000 ;j++) { n[p[j]*i]=1 ; if (i%p[j]==0 )break ; } } for (int i=0 ;i<cp;i++) { ll as=0 ,tmp=p[i]; for (tmp;tmp;tmp/=10 ) { as*=10 ,as+=tmp%10 ; } while (as<1000000 )as*=10 ; rp[i+1 ]=as; } sort (rp+1 ,rp+cp+1 ); for (int i=1 ;i<=cp;i++) { ll as=rp[i]; mp[as]=i; for (int j=0 ;p[j]<=sqrt (as)&&j<cp;j++) { while (as%p[j]==0 ) { as/=p[j]; ct[i]++; } } if (as>1 )ct[i]++; add (c,i,ct[i]); } char o; int x; while (cin>>o) { scanf ("%d" ,&x); if (o=='d' ) { int i=mp[x]; mp[x]=0 ; if (!i)continue ; add (del,i,1 ); add (c,i,-ct[i]); } else { x++; if (x>78498 )x=78498 ; int l=x,r=78499 ; while (l<r) { int mid=l+r>>1 ; if (mid-sum (del,mid)>=x)r=mid; else l=mid+1 ; } printf ("%lld\n" ,sum (c,l)); } } }
ED
\