文章目录
A—敌兵布阵
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>
#include <cmath>
#include <map>
using namespace std;
const int N=50005;
int p[N*4],add[4*N],a[N],n;
void PushUp(int rt)
{
//父节点等于两个子节点的和;当问题变成求区间最大值,就会把区间操作变成max操作;
p[rt]=p[2*rt]+p[2*rt+1];//等于两个叶节点的和,向上更新
}
/*rt 当前节点序号,l,r 当前节点区间,c 修改点或者区间的值*/
void PushDown(int rt,int ln, int rn)//对节点进行向下更新
{
//!!!!!ln,rn为左子树,右子树的数字数量,这一般是求和操作,
// add[2*rt]+=add[rt];变成 add[2*rt]=add[rt];也是对的。
if(add[rt])//0为已经下推至其他节点,避免进行重复操作。是否修改过,下推标记;
{
add[2*rt]+=add[rt];//add[2*rt]=add[rt];
add[2*rt+1]+=add[rt];//add[2*rt+1]=add[rt];//表示两个字节点也被修改
p[2*rt]+=add[rt]*ln;//p[2*rt]=add[rt]*ln
p[2*rt+1]+=add[rt]*rn; p[2*rt+1]=add[rt]*rn;
//消除当前节点标记,表示已经下推给下一层节点,
add[rt]=0;
}
}
/*rt 当前节点序号,l,r 当前节点区间,c 修改点或者区间的值*/
void Build(int l,int r,int rt)//通过二分法创建树
{
//如果达到叶节点
if(l == r)//左右区间相等
{
p[rt]=a[l];//这里a[l]的值是题目所输入的,当题目条件发生变化,比如让底层全是c,那么
//就会变成p[rt]=c;
return ;
}
int m=(l+r)/2;
//左右递归;
Build(l,m,2*rt);
Build(m+1,r,2*rt+1);// Build(m+1,r,2*rt+1);//
PushUp(rt);//在创建到最低时,完成下一个节点后,会自动更新他们(父节点)一直到首节点;
}
/*rt 当前节点序号,l,r 当前节点区间,c 修改点或者区间的值*/
void Point_Update(int L,int c,int l,int r,int rt)//这里是修改点的值
{
//找到要修改的叶结点
if(l == r)
{
p[rt]+=c;//相应节点增加c,可以根据题目条件进行更改。比如将相应节点的数变为几。
return ;
}
int m=(l+r)/2;
//判断节点所在区间,同二分查找
if(L<=m)
Point_Update(L,c,l,m,rt*2);//第一个(当前)节点序号为2*rt;
else
Point_Update(L,c,m+1,r,rt*2+1);
//子节点更新,本节点也要更新
PushUp(rt);
}
/*rt 当前节点序号,l,r 当前节点区间,c 修改点或者区间的值*/
//区间查询函数
int Query(int L,int R,int l,int r,int rt)
{
//当前区间在完全要查询区间内,那么直接返回该区间值。
if(L<=l&&r<=R)
return p[rt];//当前区间的起始位置为rt,利用二分,首节点的大小就是下面所有子结点的和;
int m=(l+r)/2;
PushDown(rt,m-l+1,r-m);
//累计答案
int ans=0;
//如果区间分在不同的子节点内,就分别取L—mid,和mid—R;
if(L<=m)
ans+=Query(L,R,l,m,rt*2);
if(R>m)
ans+=Query(L,R,m+1,r,2*rt+1);
return ans;
}
/*rt 当前节点序号,l,r 当前节点区间,c 修改点或者区间的值*/
int main()
{
int t,count=0;
cin>>t;
while(t--)
{
count++;
printf("Case %d:\n",count);
memset(a,0,sizeof a);
memset(p,0,sizeof p);
memset(add,0,sizeof(add));
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int Q_ans;
char open[15];
Build(1,n,1);//从1—n,首字节为1,的线段树
while(scanf("%s",open)&&strcmp(open,"End"))
{
if(!strcmp(open,"Query"))//查询操作
{
int l,r;
scanf("%d%d",&l,&r);
Q_ans=Query(l,r,1,n,1);//查询区间 l 到 r区间的和。
cout<<Q_ans<<endl;
}
if(!strcmp(open,"Add"))//就在相应节点L增加c
{
int L,c;
scanf("%d%d",&L,&c);
Point_Update(L,c,1,n,1);
}
if(!strcmp(open,"Sub"))//就在相应节点L减少c
{
int L,c;
scanf("%d%d",&L,&c);
c = -c;
Point_Update(L,c,1,n,1);
}
}
}
return 0;
}
B—Lost Cows
以此题为例:
我们首先将五个数全部初始化为1;可得到1 1 1 1 1的数列,我们输入的五个数相当于0 1 2 1 0;
此时我们从后面开始扫描修改才会更加全面。(a[5]+1)是因为没有第0位,所以会变成第一位,也就是a[5]会被插入第一个数。ans[1]=1;数列变为0 1 1 1 1。a[4]+1=2.所以ans[4]=3(第二个1所在的位置),数列会变成0 1 0 1 1。a[3]+1=3,所以ans[3]=5.数列会变成0 1 0 1 0。a[2]+1=2,即ans[2]=4,数列会变成0 1 0 0 0 ,a[1]+1=1,ans[1]=2。
所以按照顺序输出时2 4 5 3 1。
动态维护一个01序列,支持查询第k个1所在的位置。
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>
#include <cmath>
#include <map>
using namespace std;
const int maxn = 1e5 + 5;
int n, m;
int b[maxn*4], a[maxn],ans[maxn];
void push_up(int rt)
{
b[rt] = b[rt << 1] + b[rt << 1 | 1] ;
}
void Build(int l,int r,int rt)//创建树
{
//如果达到叶节点
if(l == r)
{
b[rt]=a[l];
return ;
}
int m=(l+r)/2;
//左右递归;
Build(l,m,2*rt);
Build(m+1,r,2*rt+1);
push_up(rt);
}
void Update(int L, int l, int r, int rt, int c) {
if(l == r)
{
b[rt] += c;
return ;
}
int mid = l + r >> 1;
if(L <= mid)
Update(L, l, mid, rt << 1, c);
else
Update(L, mid + 1, r, rt << 1 | 1, c);
push_up(rt);
}
int Find(int l, int r, int rt, int k) //二分查找,寻找a[i]+1在第几位上
{
if(l == r)
{
return l;
}
int mid = l + r>>1;
if(k <= b[rt << 1])
return Find(l, mid, rt << 1, k);
else
return Find(mid + 1, r, rt << 1 | 1, k - b[rt << 1]);
}
int main()
{
cin >> n;
Build(1,n,1);
for (int i = 1; i <= n; i ++)
Update(i, 1, n, 1, 1);//把之前所有的数全部格式化为1;
for(int i = 2; i <= n; i ++)
cin >> a[i];
a[1] = 0;
for(int i = n; i >= 1; i --)
{
ans[i] = Find(1, n, 1, a[i] + 1);
Update(ans[i], 1, n, 1, -1);//将所对应位的1变成0,就是0=1-1;
}
for(int i = 1; i <= n; i ++)
cout << ans[i] << endl;
return 0;
}
G—Buy Ticket
其实这道题不能说是和B题完全相同,只能说是一模一样。
详情解析
我们可以根据题目简单理解一下,一共n个人排队买票,然后输入n,w。n代表他前面有几个人,w代表票价是多少,后面的人可以插队,也不会被发现,n,w就代表了插队后,前面有多少人。他们的票价,现在问如果开始购票,出票顺序是什么(输出票价)。
发现了没有,排队买票前面有几个人,就等于b题前面有多少个比他矮的,插队进来的都是更高的。
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>
#include <cmath>
#include <map>
using namespace std;
#define ll long long
const int maxn = 2e5 + 5;
int n, m;
int b[maxn*4], a[maxn*4],ans[maxn*4];
ll pp[maxn*4],c[maxn*4];
void push_up(int rt)
{
b[rt] = b[rt << 1] + b[rt << 1 | 1] ;
}
void Build(int l,int r,int rt)//创建树
{
//如果达到叶节点
if(l == r)
{
b[rt]=a[l];
return ;
}
int m=(l+r)/2;
//左右递归;
Build(l,m,2*rt);
Build(m+1,r,2*rt+1);
push_up(rt);
}
void Update(int L, int l, int r, int rt, int c)
{
if(l == r)
{
b[rt] += c;
return ;
}
int mid = l + r >> 1;
if(L <= mid)
Update(L, l, mid, rt << 1, c);
else
Update(L, mid + 1, r, rt << 1 | 1, c);
push_up(rt);
}
int Find(int l, int r, int rt, int k) //二分
{
if(l == r)
{
return l;
}
int mid = l + r>>1;
if(k <= b[rt << 1])
return Find(l, mid, rt << 1, k);
else
return Find(mid + 1, r, rt << 1 | 1, k - b[rt << 1]);
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
memset(c,0,sizeof(c));
// Build(1,n,1);
for (int i = 1; i <= n; i ++)
Update(i, 1, n, 1, 1);
for(int i = 1; i <= n; i ++)
cin >> a[i]>>c[i];
int k = n;
for(int i = n; i >= 1; i --)
{
ans[i] = Find(1, n, 1, a[i] + 1);
Update(ans[i], 1, n, 1, -1);
//其实ans[i]存的就是该数所在的位次,b题只不过就是越矮,名次越低(小)。
pp[ans[i]]=c[i];//该题就比b题多了这一行。pp存的就是这些人的“体重”,最后的输出就是按照名次输出体重。
}
for(int i = 1; i <= n; i ++)//注意格式
{
if(i==1)
cout << pp[i];
else
cout <<" "<< pp[i];
}
cout<<endl;
}
return 0;
}
E—l Hate It
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>
#include <cmath>
#include <map>
using namespace std;
const int N=200005;
int p[N*4],add[4*N],a[N],n;
void PushUp(int rt)
{//这时父节点存的是两个子节点存的最大值。
p[rt]=max(p[2*rt],p[2*rt+1]);
}
void PushDown(int rt,int ln, int rn)//对节点进行更新
{
//ln为左儿子的数量,rn为右儿子的数量
if(add[rt])//是否修改过,下推标记
{
add[2*rt]+=add[rt];
add[2*rt+1]+=add[rt];//表示两个字节点也被修改
p[2*rt]+=add[rt]*ln;
p[2*rt+1]+=add[rt]*rn;
//消除当前节点标记,表示已经下推给下一层节点,
add[rt]=0;
}
}
void Build(int l,int r,int rt)//创建树
{
//如果达到叶节点
if(l == r)
{
p[rt]=a[l];//最底层存的值。
return ;
}
int m=(l+r)/2;
//左右递归;
Build(l,m,2*rt);
Build(m+1,r,2*rt+1);
PushUp(rt);
}
void Point_Update(int L,int c,int l,int r,int rt)
{
//找到要修改的叶结点
if(l == r)
{
p[rt] =c;//L节点的值变成c;
return ;
}
int m=(l+r)/2;
//判断节点所在区间,同二分查找
if(L<=m)
Point_Update(L,c,l,m,2*rt);
else
Point_Update(L,c,m+1,r,2*rt+1);
PushUp(rt);
}
int Query(int L,int R,int l,int r,int rt )
{
//当前区间在完全要查询区间内,那么直接返回该区间值
if(L<=l&&r<=R)
return p[rt];
int m=(l+r)/2;
int ans=0;
if(L<=m)
ans=max(ans,Query(L,R,l,m,rt*2));//左右分别求出
if(R>m)
ans=max(ans,Query(L,R,m+1,r,2*rt+1));
return ans;
}
int main()
{
int t,count=0,m,n;
while(scanf("%d%d",&n,&m)==2)
{
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
Build(1,n,1);
int a,b;
char str[2];
while(m--)
{
scanf("%s",str);
cin>>a>>b;
if(str[0]=='Q')
{
int ans=Query(a,b,1,n,1);//查询a到b区间的最大值。
printf("%d\n",ans);
}
else
Point_Update(a,b,1,n,1);//将a学生的成绩变为b;
}
}
return 0;
}
D—Just a Hook
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>
#include <cmath>
#include <map>
using namespace std;
#define ll long long
const int N=200005;
ll p[N*4],add[4*N],a[N],n;
void PushUp(int rt)
{
p[rt]=p[2*rt]+p[2*rt+1];
}
void Build(int l,int r,int rt)//创建树
{
//如果达到叶节点
if(l == r)
{
p[rt]=1;
return ;
}
int m=(l+r)/2;
//左右递归;
Build(l,m,2*rt);
Build(m+1,r,2*rt+1);
PushUp(rt);
}
void PushDown(int rt,int ln, int rn)//对节点进行更新,自我感觉一般就是父节点等于两个子节点的时候的用(如果我理解错了,私聊告诉我)
{
//ln为左儿子的数量,rn为右儿子的数量
if(add[rt])//是否修改过,下推标记
{
add[2*rt]=add[rt];
add[2*rt+1]=add[rt];//表示两个字节点也被修改
p[2*rt]=add[rt]*ln;
p[2*rt+1]=add[rt]*rn;
//消除当前节点标记,表示已经下推给下一层节点,
add[rt]=0;
}
}
void Line_Update(int L,int R,int c,int l,int r,int rt)//L R表示要操作区间
{
//如果当前区间完全在要进行修改的区间内
if(L <= l&&r <= R)
{
//更新数字和,向上保持正确
p[rt] = c * (r - l + 1);
//增加标记表示本区间已经进行修改,并根据当前add值修改下一层子节点区间
add[rt] = c;//j将区间内的数全部变成c
return ;
}
int m = (l + r) / 2;
PushDown(rt,m - l + 1,r - m);//下推add标记,若下一层标记需要根据标记修改则修改
//这里判断左右子树跟[L,R]有无交集,有交集才递归
if(L <= m)
{
Line_Update(L,R,c,l,m,2 * rt);
}
if(R > m)
{
Line_Update(L,R,c,m + 1,r,2 * rt + 1);
}
//更新本节点信息
PushUp(rt);
}
int main()
{
int t,count=1,m,n;
scanf("%d",&t);
while(t--)
{
memset(p,0,sizeof p);
memset(add,0,sizeof (add));
scanf("%d%d",&n,&m);
Build(1,n,1);
for(int i=1;i<=m;i++)
{
int t1,t2,t3;
scanf("%d%d%d",&t1,&t2,&t3);
Line_Update( t1,t2, t3, 1, n, 1);
}
printf("Case %d: The total value of the hook is %d.\n",count++,p[1]);
}
return 0;
}
F—Ultra-Quicksort
#include<iostream>
using namespace std;
#define ll long long
const int N=500000+7;
ll tmp[N],b[N],a[N];
ll k,sum;
void ms(ll q[],ll l,ll r)
{
if(l>=r)
return;
ll mid=l+r>>1;
ms(q,l,mid);
ms(q,mid+1,r);
ll k=l,i=l,j=mid+1;
while(k<=r)
{
if(j>r||i<=mid&&q[i]<=q[j])
b[k++]=q[i++];
else
b[k++]=q[j++],sum+=mid-i+1;
}
for(i=l,j=l; i<=r;j++ ,i++)
q[i]=b[j];
}
int main()
{
ll T;
while(scanf("%lld", &T) && T)
{
int i;
sum=0;
for(i=1; i<=T; ++i)
scanf("%d", &a[i]);
ms(a, 1, T);
printf("%lld\n", sum);
}
return 0;
}
H—Stars
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>
#include <cmath>
#include <map>
using namespace std;
const int N=32010;
int p[N*4],add[4*N],a[N],n;
void PushUp(int rt)//向上更新
{
p[rt]=p[rt<<1]+p[rt<<1|1];
}
void Point_Update(int L,int l,int r,int rt)
{
//找到要修改的叶结点
if(l == r)
{
p[rt]++;
return ;
}
int m=(l+r)>>1;
//判断节点所在区间,同二分查找
if(L<=m)
Point_Update(L,l,m,2*rt);
else
Point_Update(L,m+1,r,2*rt+1);
PushUp(rt);
}
int Query(int L,int R,int l,int r,int rt )
{
//当前区间在完全要查询区间内,那么直接返回该区间值
if(L<=l&&r<=R)
return p[rt];
int m=(l+r)>>1;
int ans=0;
if(L<=m)
ans+=Query(L,R,l,m,rt*2);
if(R>m)
ans+=Query(L,R,m+1,r,2*rt+1);
return ans;
}
int main()
{
int t,count=0;
cin>>t;
int x,y;
for(int i=0;i<t;i++)
{
scanf("%d%d",&x,&y);
add[Query(0,x,0,32010,1)]++;
Point_Update(x,0,32010,1);
}
for(int i=0;i<t;i++)
printf("%d\n",add[i]);
return 0;
}
C—Major‘s posters
#include <iostream>
#include <cstdio>
#include <cstring>
#include <map>
#include <algorithm>
using namespace std;
const int maxn=20100;
int lazy[maxn<<3];//记录当前区间的颜色
int vis[maxn];//vis就是求最后求去总区间内出现过多少不同的数字是避免重复计算的标志。
//要注意maxn,给一万个询问,a最多存四万,lazy一般是a的四倍,vis只要两万就够了
int c[maxn*2];//存离散化数字的数组,就是存海报左右区间中出现过的数字
int cunt;//是最终答案数量
struct node
{
int l,r;
} p[maxn];
void Pushdown(int rt)//向下更新
{
if(lazy[rt]!=-1)//当为—1时,即为杂色海报或者没有海报,
{
lazy[rt<<1]=lazy[rt];
lazy[rt<<1|1]=lazy[rt];
lazy[rt]=-1;
}
}
void Update(int L,int R,int l,int r,int rt,int C)
{
if(L<=l&&r<=R)
{
lazy[rt]=C;
return ;
}
int m=(r+l)>>1;
Pushdown(rt);
//累计结果
if(L<=m)
Update(L,R,l,m,rt<<1,C);
if(R>m)
Update(L,R,m+1,r,rt<<1|1,C);
}
void Query(int L,int R,int l,int r,int rt)
{
if(L<=l&&R>=r&&lazy[rt]!=-1)
{
if(!vis[ lazy[rt] ])
{
vis[lazy[rt]]=1;
cunt++;
}
return ;
}
if(l == r)
{
return ;
}
int mid =l+r>>1;
if(L <= mid)
Query(L,R,l,mid,rt<<1);
if(R > mid)
Query(L,R,mid+1,r,rt<<1|1);
}
int main()
{
int t;
scanf("%d",&t);
for(int i=1; i<=t; i++)
{
memset(vis,0,sizeof(vis));
memset(lazy,-1,sizeof(lazy));
cunt=0;
int tot=1;//存a个数的变量
int n;
scanf("%d",&n);
for(int j=1; j<=n; j++)
{
scanf("%d%d",&p[j].l,&p[j].r);
c[tot++]=p[j].l;
c[tot++]=p[j].r;
}
//离散化
sort(c+1,c+tot);
tot=unique(c+1,c+tot)-c;
int k=tot;
for(int j=2; j<k; j++)
{
if(c[j]-c[j-1]>1)//当两个数差值大于1,则在两个数之间插上任意一个数
c[tot++]=c[j-1]+1;
}
sort(c+1,c+tot);
//二分查找所对应的离散化后的坐标
for(int j=1; j<=n; j++)
{
int l,r;
l=lower_bound(c+1,c+tot,p[j].l)-c;
r=lower_bound(c+1,c+tot,p[j].r)-c;
Update(l,r,1,tot-1,1,j);
}
Query(1,tot-1,1,tot-1,1);
printf("%d\n",cunt);
}
return 0;
}
I—A Simple Problem With Inteqers
#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
#include <queue>
#include <cmath>
#include <map>
using namespace std;
#define ll long long
const int N=200005;
ll p[N*4],add[4*N],a[N],n;
void PushUp(int rt)
{
p[rt]=p[2*rt]+p[2*rt+1];
}
void PushDown(int rt,int ln, int rn)//对节点进行更新
{
//ln为左儿子,rn为右儿子
if(add[rt])//是否修改过,下推标记
{
add[2*rt]+=add[rt];
add[2*rt+1]+=add[rt];//表示两个字节点也被修改
p[2*rt]+=add[rt]*ln;
p[2*rt+1]+=add[rt]*rn;
//消除当前节点标记,表示已经下推给下一层节点,
add[rt]=0;
}
}
void Build(int l,int r,int rt)//创建树
{
//如果达到叶节点
if(l == r)
{
p[rt]=a[l];
return ;
}
int m=(l+r)/2;
//左右递归;
Build(l,m,2*rt);
Build(m+1,r,2*rt+1);
PushUp(rt);
}
void Point_Update(int L,int c,int l,int r,int rt)
{
//找到要修改的叶结点
if(l == r)
{
p[rt]+=c;
return ;
}
int m=(l+r)/2;
//判断节点所在区间,同二分查找
if(L<=m)
{
Point_Update(L,c,l,m,2*rt);
}
else
{
Point_Update(L,c,m+1,r,2*rt+1);
}
PushUp(rt);
}
void Line_Update(int L,int R,int c,int l,int r,int rt)//L R表示要操作区间
{
int mid=(l+r)/2;
//如果当前区间完全在要进行修改的区间内
if(L <= l&&r <= R)
{
//更新数字和,向上保持正确
p[rt] += c * (r - l + 1);
//增加标记表示本区间已经进行修改,并根据当前add值修改下一层子节点区间
add[rt] += c;
return ;
}
int m = (l + r) / 2;
PushDown(rt,m - l + 1,r - m);//下推add标记,若下一层标记需要根据标记修改则修改
//这里判断左右子树跟[L,R]有无交集,有交集才递归
if(L <= m)
{
Line_Update(L,R,c,l,m,2 * rt);
}
if(R > m)
{
Line_Update(L,R,c,m + 1,r,2 * rt + 1);
}
//更新本节点信息
PushUp(rt);
}
ll Query(int L,int R,int l,int r,int rt )
{
//当前区间在完全要查询区间内,那么直接返回该区间值
if(L<=l&&r<=R)
return p[rt];
int m=(l+r)/2;
PushDown(rt,m-l+1,r-m);
//累计答案
ll ans=0;
if(L<=m)
ans+=Query(L,R,l,m,rt*2);
if(R>m)
ans+=Query(L,R,m+1,r,2*rt+1);
return ans;
}
int main()
{
int t,count=0,m,n;
scanf("%d%d",&n,&m);
for(int i=1; i<=n; i++)
scanf("%lld",&a[i]);
Build(1,n,1);
int a,b,c;
char str[5];
while(m--)
{
scanf("%s",str);
if(str[0]=='Q')
{
cin>>a>>b;
ll ans=Query(a,b,1,n,1);
printf("%lld\n",ans);
}
else
{
cin>>a>>b>>c;
Line_Update(a,b,c,1,n,1);
}
}
return 0;
}