目录:
OP
感谢学长的讲解与付出;
感谢ph和zsl两位大佬的指导与讨论;
夹带私货:
此节课的代表性的内容已经整理到自己的贴子中:【笔记】数据结构
树状数组总的来说可以快速对数组进行单点更改和区间查询(或通过维护前缀实现区间更改和单点查询),适用于一些边改边查的情况;
A Ultra-QuickSort
题目大意
在一个马桶搋子图片的陪同下 求逆序对;
有定义:若 i < j ,且
a
i
>
a
j
a_i\gt a_j
ai>aj ,则构成一对逆序对;
思路
有两种做法,暂且称之为归并法和树状数组法,大体相同,但树状数组法更加直观,具体做法可以参考PPT,下面简述一下归并法;
在归并排序的过程中,我们会得到两个有序递增数组(暂称为a数组和b数组),其中保证左数组(a)的下标均小于右数组(b);
在通过双指针将两个数组合并时,若对 b j b_j bj 存在 a i < b j < a i + 1 a_i\lt b_j\lt a_{i+1} ai<bj<ai+1,则说明 a数组中从 a i + 1 a_{i+1} ai+1后的每个数都可以和 b j b_j bj 构成逆序对;
代码
#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
题目大意
给出星星坐标,求出以每个星星为原点,其第三象限(含象限边界)内的星星数;
思路
由于输入是将星星按先以纵坐标升序,再以横坐标升序顺序输入的,我们可以保证对于新输入的星星,目前在场的所有星星纵坐标皆小于等于它,只需要判断横坐标小于等于它的星星有多少颗即可;
代码
#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)//a[p]+=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
题目大意
对一个二维平面进行单点数值修改和区块数值总和查询;
思路
模拟
通过二维树状数组维护即可~
板子已经更新到帖子中;
代码
#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) //a[p]+=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
题目大意
给出每头牛对应的区间,对每头牛求出有多少个区间覆盖(至多一个端点相同)它的区间;
思路
可以先按右端点降序,左端点升序的顺序排序,若有两区间完全重合,同时保证了两区间排序后必相邻;
排序后对与每头牛,可以保证所有右端点大于其的牛都在它的左侧,如果我们统计的同时在数组中添加该头牛的左端点,则对于每头牛只需要计数目前在其左端点以左有多少个左端点即可;
注意特判重合区间;
代码
#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)//a[x]+=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);
//printf("*");
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
题目大意
给出总区域大小,每棵树的坐标和可选区域的大小,求所有可选区域中树的最大值;
思路
不需要树状数组,用二维前缀和穷举即可;
做题的时候没意识到这一点,代码中用了树状数组;
代码
#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) //a[x][y]+=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
题目大意
二维区间修改,单点查询;
思路
可以通过树状数组维护二位前缀和实现;
实际上不需要做翻转的动作,只需要判断奇偶即可;
注意输出要求;
代码
#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) //a[x][y]+=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 xi和音量 v i v_i vi,定义两牛 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) abs(xi−xj)⋅max(vi,vj),求所有牛对(N(N-1)/2对)进行互相交流的总代价;
思路
我们可以按v升序排序,可以保证每头牛j 和前面交流时的 m a x ( v i , v j ) = v j max(v_i,v_j)=v_j max(vi,vj)=vj;
接下来需要处理
a
b
s
(
x
i
−
x
j
)
abs(x_i-x_j)
abs(xi−xj):
我们可以维护前面所有牛的坐标和
s
u
m
x
sumx
sumx ,再通过树状数组求出前面牛中比此牛坐标小的坐标之和
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=1i<jabs(xi−xj)=sumx−s−xj⋅(j−1−k)+xj⋅k−s;
即可求出此牛和前面所有牛交流所用代价;
代码
#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) //a[x][y]+=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类似~
另外地,此题需要将坐标和高度转换为顺序;
代码
#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];//<x,h>
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个数;
思路
大体上可以用桶排的思路进行加入和删除维护;
对于查询,我们可以使用二分的思想快速确定;
具体看代码~
代码
#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) //a[x][y]+=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 个的位置;
代码
#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];
//printf("%d\n",as);
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]);
}
//printf("%d",ct[1]);
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
\