第一次参加比赛 崩的实在是太惨了.. 只A了一道板子题(已经被自己菜哭了 没想到大家都掉线了 一起崩 这也给了我一个血的教训 以后比赛一定要多开几题

简直是在死撑着坐满五个小时 当时其实老早就想溜了(以后比赛再也不吃香蕉了.. 肚子快疼炸了

接下来我就按我自己的顺序来写题解(我怕我先写难的写不下去就咕了

//耗时一星期终于竣工了emmmm

哭泣的阿木木

众所周知,阿木木是英雄联盟里面最好玩的英雄之一。

殇之木乃伊-阿木木是一位近战型英雄,拥有强大的技能属性、爆发能力强,拥有强力的群体控制技能。阿木木有着不错的护甲、血量成长和不错的DPS输出,拥有单体和群体两个控制技能,他将悲伤化为力量在战场上为队友保驾护航,发挥自身的优势辅助自己的队友gank并攻城略地,是一名优秀的tank英雄。

由于使用了过多的Q技能--绷带牵引,阿木木的绷带库存越来越少,有一天,阿木木去市场上采购绷带,市场上的绷带一共有n种,每一种绷带的价格为 a_iai​,由于受到金融危机的影响,绷带的价格出现了一些波动,记性很差的阿木木将绷带价格的波动记录了下来,一共有Q次操作,有时阿木木会记录下新的价格波动,有时阿木木会计算一段区间[L,R]的绷带的价格之和,但是阿木木的计算器一不小心丢了,所以请聪明的你们回答阿木木每次询问的结果。

已知第i种价格波动有三种形式:

  1. 将区间[L,R]绷带的价格增加k元
  2. 将区间[L,R]绷带的价格减少k元
  3. 将第i种绷带的价格变为 b 元

每次询问请输出区间[L,R]内的绷带之和

一道裸的线段树问题 涉及到区间查询 单点更新 区间更新

  • 什么是线段树?

线段树是一种二叉搜索树,与区间树相似,它将一个区间划分成一些单元区间,每个单元区间对应线段树中的一个叶结点。对于线段树中的每一个非叶子节点[a,b],它的左儿子表示的区间为[a,(a+b)/2],右儿子表示的区间为[(a+b)/2+1,b]。因此线段树是平衡二叉树,最后的子节点数目为N,即整个线段区间的长度。

  • 创建一棵线段树

思想:递归建树,遇到叶子节点直接赋值,否则递归遍历左右建树,最后进行状态的合并(以查询区间和为例 此时状态的合并就是父节点的值=两个子节点的值之和

代码:

int sum[maxn<<2];
void pushup(int rt) //状态合并,此结点的w=两个孩子的w之和
{sum[rt] = sum[rt<<1] + sum[rt<<1|1];
}void build(int l,int r,int rt)
{if(l==r)//叶子节点 {scanf("%d",&sum[rt]);return ; }int m=(l+r)>>1;build(l,m,rt<<1);//左孩子 build(m+1,r,rt<<1|1);//右孩子 pushup(rt);
}

在这里我们可以很明显的看出,线段树的建树过程是自底向上的,它是每次递归到了没有分支的叶子节点,给叶子节点赋值,然后向父节点赋值直到根节点

  • 单点更新

与二分查询法基本一致,如果当前枚举的点左右端点相等,即叶子节点,就是目标节点。如果不是,因为这是二分法,所以设查询位置为x,当前结点区间范围为了l,r,中点为mid,则如果x<=mid,则递归它的左孩子,否则递归它的右孩子。找到x的位置,根据建树状态合并的原理,修改每个结点的状态。

代码:

void update(int p,int add,int l,int r,int rt)//单点更新,点p加上 add
{if (l==r){sum[rt]+=add;return;}int m=(l+r)>>1;if (p<=m)update(p,add,l,m,rt<<1);elseupdate(p,add,m+1,r,rt<<1|1);PushUP(rt);
}
  • 查询区间最值

区间最值的查询其实关键在于子节点向父节点的传递,从线段树底部开始往根节点进行最小值的更新,所以这个状态的合并其实也是在pushup中完成  这里能更好体现线段树的自底向上的性质

代码:

void pushup(int i)
{sum[i]=min(sum[i<<1],sum[i<<1|1]);
}int query(int L,int R,int l,int r,int rt)
{if (L<=l&&R<=r)return sum[rt];int minn=INF;int mid=(l+r)<<1;if (L<=mid) minn=min(minn,query(L,mid,l,r,rt)); //左子树if (R>mid) minn=min(minn,query(mid+1,R,l,r,rt)); //右子树return minn;
}
  • 区间更新 区间求和 (还需要加强理解)

区间更新是指更新某个区间内的叶子节点的值,因为涉及到的叶子节点不止一个,而叶子节点会影响其相应的非叶父节点,那么回溯需要更新的非叶子节点也会有很多,如果一次性更新完,操作的时间复杂度肯定很大。为此引入了线段树中的延迟(lazy)标记概念,这也是线段树的精华所在。

对lazy的理解:(该段生动的理解转自:传送门)

  • 直观理解:“懒”标记,顾名思义,用到它才动,不用它就不动。
  • 作用:存储到这个节点的修改信息,暂时不把修改信息传到子节点。就像家长扣零花钱,你用的时候才给你,不用不给你。
  • 实现思路(重点):递归到这个节点时,只更新这个节点的状态,并把当前的更改值累积到标记中。注意是累积,可以这样理解:过年,很多个亲戚都给你压岁钱,但你暂时不用,所以都被你父母扣下了。
  • lazy标记的运用:当需要递归这个节点的子节点时,标记下传给子节点。这里不必管用哪个子节点,两个都传下去。就像你如果还有妹妹,父母给你们零花钱时总不能偏心吧
  • 下传操作:

    ①:当前节点的懒标记累积到子节点的懒标记中。

    ②:修改子节点状态。就是原状态+子节点区间点的个数*父节点传下来的懒标记。这就有疑问了,既然父节点都把标记传下来了,为什么还要乘父节点的懒标记,乘自己的不行吗?因为自己的标记可能是父节点多次传下来的累积,每次都乘自己的懒标记造成重复累积

    ③:父节点懒标记清0。这个懒标记已经传下去了,不清0后面再用这个懒标记时会重复下传。就像你父母给了你5元钱,你不能说因为前几次给了你10元钱, 所以这次给了你15元,那你不就亏大了。

代码:

void PushDown(int rt,int m)      //add为lazy标记数组
{       if (add[rt]) {add[rt<<1]+=add[rt];add[rt<<1|1]+=add[rt];sum[rt<<1]+=add[rt]*(m-(m>>1));sum[rt<<1|1]+=add[rt]*(m>>1);add[rt]=0;}
}
void update(int L,int R,int c,int l,int r,int rt)
{if (L<=l&&r<=R){add[rt]+=c;sum[rt]+=(LL)c*(r-l+1);return;}PushDown(rt,r-l+1);int m=(l+r)>>1;if (L<=m) update(L,R,c,l,mid,rt<<1);if (m<R) update(L,R,c,mid+1,r,rt<<1|1);PushUp(rt);
}

区间更新举例说明:当我们要对区间[0,2]的叶子节点增加2,利用区间查询的方法从根节点开始找到了非叶子节点[0-2],把它的值设置为1+2 = 3,并且把它的延迟标记设置为2,更新完毕;当我们要查询区间[0,1]内的最小值时,查找到区间[0,2]时,发现它的标记不为0,并且还要向下搜索,因此要把标记向下传递,把节点[0-1]的值设置为2+2 = 4,标记设置为2,节点[2-2]的值设置为1+2 = 3,标记设置为2(其实叶子节点的标志是不起作用的,这里是为了操作的一致性),然后返回查询结果:[0-1]节点的值4;当我们再次更新区间[0,1](增加3)时,查询到节点[0-1],发现它的标记值为2,因此把它的标记值设置为2+3 = 5,节点的值设置为4+3 = 7。

到这里,整个线段树的基础操作已经完毕了,接下来我们看题目,发现这就是一个裸的线段树问题,但是,我们在这里很容易遗漏掉在单点更新的时候对lazy的传递,为什么要传递lazy呢?因为我们有区间更新的操作,如果没把lazy标记向下传递,就直接把原来的值覆盖掉了,那么下一次遍历到这个点的时候,lazy标记的值,还是会传到之前被覆盖的值上(因为这个wa了九次..

举个例子:比如在区间[1,4]上加5,然后再将3的值改为0,接下来再进行[3,4]的查询,如果你单点的时候没有将lazy标记传下去,

那么在查询的时候 这个lazy标记就会令3的值+5,但实际上3这个点的值已经是0了

代码:

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int maxn = 1e6+5;
LL sum[maxn<<2],add[maxn<<2];
void PushUp(int rt)
{sum[rt] = sum[rt<<1|1]+sum[rt<<1];
}
void PushDown(int rt,int m)
{if(add[rt]){add[rt<<1]+=add[rt];add[rt<<1|1]+=add[rt];sum[rt<<1]+=add[rt]*(m-(m>>1));sum[rt<<1|1]+=add[rt]*(m>>1);add[rt]=0;}
}
void build(int l,int r,int rt)
{if(l==r){scanf("%lld",&sum[rt]);return;}int m = (l+r)>>1;build(l,m,rt<<1);build(m+1,r,rt<<1|1);PushUp(rt);
}
void update(int L,int R,int c,int l,int r,int rt)
{if(L<=l&&r<=R){add[rt]+=c;sum[rt]+=(LL)c*(r-l+1);return;}PushDown(rt,r-l+1);int m=(l+r)>>1;if(L<=m) update(L,R,c,l,m,rt<<1);if(m<R) update(L,R,c,m+1,r,rt<<1|1);PushUp(rt);
}
void update1(int pos,int c,int l,int r,int rt)
{if(l==r){add[rt]=c;sum[rt]=(LL)c*(r-l+1);return;}PushDown(rt,r-l+1);int m=(l+r)>>1;if(pos<=m) update1(pos,c,l,m,rt<<1);if(m<pos) update1(pos,c,m+1,r,rt<<1|1);PushUp(rt);
}
LL query(int L,int R,int l,int r,int rt)
{if(L<=l&&r<=R)return sum[rt];PushDown(rt,r-l+1);int m=(l+r)>>1;LL ans=0;if(L<=m) ans+=query(L,R,l,m,rt<<1);if(m<R) ans+=query(L,R,m+1,r,rt<<1|1);return ans;
}
int main()
{int N,Q;scanf("%d %d",&N,&Q);build(1,N,1);for(int i=0;i<Q;i++){char s[20];scanf("%s",s);if(s[1]=='1'){int a,b,c;scanf("%d %d %d",&a,&b,&c);update(a,b,c,1,N,1);}if(s[1]=='2'){int a,b,c;scanf("%d %d %d",&a,&b,&c);update(a,b,-c,1,N,1);}if(s[1]=='3'){int a,b;scanf("%d %d",&a,&b);update1(a,b,1,N,1);}if(s[1]=='4'){int a,b;scanf("%d %d",&a,&b);printf("%lld\n",query(a,b,1,N,1));}}
}

点进来吧,这里有你想要的

QAQorz一回到长沙就直奔汉堡王,已知小食的美味度是1,汉堡的美味度是m,QAQorz一共能吃n美味度的东西,请问QAQorz吃一次汉堡王有几种不同的搭配方案?

关于方案:如果两个方案吃汉堡和小食的先后顺序不同,则称这两种方案是不同的

具体来说:假设一份小食用一个11表示,一个汉堡用m个0表示。当m=2m=2时,先吃一份小食再吃一个汉堡再吃一份小食,方案表示为1001,两个方案不同当且仅当两个01是不同的。

简直不能再裸的完全背包,背包问题参考:背包问题。当时真是上头了,只想着A那道以为是裸的LCA问题(结果真的是裸的),补题一次A过得时候越想越气

代码:

#include<bits/stdc++.h>
using namespace std;
const int MAX = 2e5+5;
const int mod = 1e8+7;
int dp[MAX+1];
int main()
{int t;scanf("%d",&t);while(t--){int n,m;scanf("%d %d",&n,&m);for(int i=0;i<2;i++){dp[0]=1;for(int j=1;j<=n;j++){dp[j]=(dp[j-1]+dp[j-m])%mod;}}printf("%d\n",dp[n]);}
}

千万别点进来,点进来你就哭了

下面开始PY的香港之行,PY有n个要去的小吃店,这n个小吃店被m条路径联通起来。

PY有1个传送石和n-1个传送石碎片。

PY可以用传送石标记一个小吃店作为根据地。

每当PY吃完一个地点的美食,必须回到根据地(传送石标记的小吃店)休息一段时间,才能去另外一个小吃店。

也就是说你从根据地走去另一个小吃店吃完之后,不需要再走回来,用传送石碎片即可回来。

现在PY想知道他该标记那个小吃店才能让她走最短的路程吃完这n个小吃店?

请聪明的你思考上述问题,并告诉他所需走的路程总和他该标记那个地点作为根据地。

PS:传送石只能标记一个小吃店

还是一道裸题.. 比赛的时候根本没开这题 后悔+++ 但是补题的时候莫名其妙RE七发(还是跟wj学长一模一样的代码 佛了..  但是也不是没有收获 代码被wj学长找出一个致命bug 这个之前ly学姐也提到过 就是应该先用tot下标记录 再++ 否则在迪杰斯特拉里面就应该是i!=0而不是i!=-1 传送门:最短路

正解就是枚举每个起点,跑迪杰特斯拉,取遍历完的最小值

代码:

#include<queue>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAX_M = 1e4+5;
const int INF = 0x3f3f3f3f;
int tot;
int head[MAX_M]; //i顶点发出的边在edge数组中的位置(头指针)
long long dis[MAX_M],vis[MAX_M];
struct Edge
{int dest; //边的终点int weight; //边的权重int next; //下一条边在edge数组中的位置} edge[MAX_M*2];
struct node
{int pos;long long val;friend bool operator<(node a,node b)//运算符函数{return a.val>b.val;}
};
void add(int s,int e,int l)
{edge[tot].next=head[s];edge[tot].dest=e;edge[tot].weight=l;head[s]=tot++;
}
void dijkstra(int s)
{memset(vis,0,sizeof(vis));memset(dis,0x3f,sizeof(dis));priority_queue<node> q;q.push(node{s,0});dis[s]=0; //与自己的距离为0while(!q.empty()){node u=q.top();q.pop();if(vis[u.pos])continue;  //若某个点已经被更新到最优,就不用再次更新其他点vis[u.pos] = 1;for(int i=head[u.pos]; i!=-1; i=edge[i].next){int dest=edge[i].dest,weight=edge[i].weight;if(dis[dest]>u.val+weight){dis[dest]=u.val+weight;q.push(node{dest,dis[dest]});}}}
}
int main()
{int N,M,a,b,c,x;long long ans=INF,sum=0;while(~scanf("%d %d",&N,&M)){tot=0,ans=1e18;memset(head,-1,sizeof(head));for(int i=0; i<M; i++){scanf("%d %d %d",&a,&b,&c);add(a,b,c);add(b,a,c);}for(int i=1; i<=N; i++){dijkstra(i);sum=0;for(int j=1; j<=N; j++){if(i!=j)sum+=dis[j];}if(sum<ans){ans=sum;x=i;}}printf("%lld %d\n",ans,x);}
}

“提莫队长正在待命!”

迅捷斥候·提莫作为班德尔城安全的侦察兵首领,也是班德尔城最富盛名的特种部队之一“主舰斥候队”一员,平时他会在各处巡逻保证班德尔城的安危。

我们现在可以把班德尔城看做由n个小城镇组成的大城市,在这n个城市之间有n−1条道路将这n个小城镇连接起来(也就是说这n个小城镇形成了一个树形的结构)。

由于城镇太多,提莫一个人无法把所有的城镇巡逻到,所以他会在某些道路上种上蘑菇,这样如果有敌人入侵在前进的路上就会被蘑菇炸伤。

现在提莫想知道如果按照他现在放蘑菇的方式,敌人经过哪些路径会被蘑菇炸伤,输出路径数。

ps:路径(u,v)是指从城镇u走到城镇v所要走过的边。

比赛的时候完全没有想到这是一道并查集的题目 一开始想爆搜以为跟去年选拔赛一棵树一样 后面听了1A大佬解释才有了一点思路 (然而有了思路却实现不了 后面经过lxr的提醒才搞懂了 通过并查集把图分成有放蘑菇和没放蘑菇的部分 形成多个连通块 没有放蘑菇就合并 用一个数组记录没有蘑菇的连通块的个数并合并 有蘑菇的路径=总路径-无蘑菇的路径

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxd = 3e5+10;
int fa[maxd],arr[maxd],n;
long long ans;
void init()
{for(int i=1; i<maxd; i++)fa[i] = i,arr[i]=1;
}
int findset(int x)
{if(x == fa[x])return x;return fa[x] = findset(fa[x]);
}
int main()
{scanf("%d",&n);init();for(int i=1; i<n; i++){int x,y,z;scanf("%d %d %d",&x,&y,&z);if(z)continue;x = findset(x),y = findset(y);ans += (long long)(arr[x]*arr[y]);arr[y] += arr[x];fa[x] = y;}printf("%lld",(long long)(n-1)*n-2*ans);return 0;
}

武器大师的宝贝

武器大师有两堆宝贝箱子,每个箱子都有着自己的一个编号。

为了输入方便,每堆箱子编号都是连续的。

现在他想分别从两堆箱子中各等概率的选择一个箱子,但是只有他选择的两个箱子编号异或(位运算)之后为0,他才能获得奖励。

现在他想请你算算每次他能获得奖励的概率。

为了防止精度误差,你需要输出一个形如A / B的最简分数。

特别的,如果概率为0,你需要输出0/1。

比赛题目读不懂. 不知道啥时异或 后面才知道是相等 这道题就是取到求两个数组相同的部分的概率 gcd+特判问题(补题又出现Bug wa了三发

代码:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
ll gcd(ll num1,ll num2)
{ll temp;if(num1<num2){temp=num1;num1=num2;num2=temp;}ll a=num1;ll b=num2;while(b!=0){temp=a%b;a=b;b=temp;}return a;
}
int main()
{int T;scanf("%d",&T);ll a,b,c,d,ans=0,sum=0;while(T--){scanf("%lld %lld %lld %lld",&a,&b,&c,&d);ll x=b-a+1,y=d-c+1;sum=x*y;if(c>a&&d<b)ans=y;else if(c<=a&&d>=b)ans=x;else if(c>a&&c<=b&&d>b)ans=b-c+1;else if(c<a&&d>=a&&d<b)ans=d-a+1;elseans=0;if(!ans){printf("0/1\n");continue;}else{if(sum%ans==0){printf("1/%lld\n",sum/ans);}else{ll k=gcd(sum,ans);printf("%lld/%lld\n",ans/k,sum/k);}}}
}

寻宝

在一维坐标轴上有许多宝藏,总共n种宝藏,每种宝藏有k个。现在共k个人寻宝,k个人初始位置可以位于任意点。但是每人需要按指定顺序捡起宝藏(1->2->3->...->n,先捡第1种,再捡第2种。。。最后捡第n种宝藏,每种宝藏捡起一个)。每个人要捡起n个宝藏。现在你自己规划好k个人的初始位置与寻宝路线(一个宝藏只能被一个人捡起),求k个人所走路程的和最短是多少。

没啥好讲的..怪就怪自己太菜.. 直接取出每一次比较的max和min 相减加到ans中

代码:

#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define ll long long
using namespace std;
int a[10],b[10];
int main()
{int n,m;ll ans=0;scanf("%d %d",&n,&m);for(int i=1; i<=n; i++){if(i>1){for(int j=1; j<=m; j++)b[j]=a[j];}for(int j=1; j<=m; j++)scanf("%d",&a[j]);sort(a+1,a+1+m);if(i>1){for(int j=1; j<=m; j++){int c=max(a[j],b[j]);int d=min(a[j],b[j]);ans+=c-d;}}}printf("%lld\n",ans);
}

凛冬将至

众(wo)所(xia)周(che)知(de),当凯特琳·徒利得知自己女儿艾莉亚逃到赫伦堡后,非常担心女儿的安全。假设维斯特洛大陆共有n个城市,共有n-1条双向道路把这nn个城市连接起来。也就是说这是一棵树。凯特琳想尽快临冬城赶到赫伦堡。除了已知的n-1条边外,凯特琳还知道一条额外的秘密路径(也是双向的):端点是是城市x和城市y,路径长度是z。现在想考考寒假过后的你有没有刷过题,问你Q个问题,每个问题给出临冬城(凯特琳所在城市)和赫伦堡(艾莉亚所在城市)的坐标,请你告诉凯特琳从临冬城到赫伦堡的最短路径长度是多少?

LCA最短路裸题,枚举非树上边,进行(U-V,U-X-Y-V,U-Y-X-V)最小值的判断

  • 什么是LCA?

LCA (Least Common Ancestors) ,即最近公共祖先,是指这样的一个问题:在一棵有根树中,找出某两个节点 u 和 v 最近的公共祖先。

  • 解决LCA问题的算法

在线算法:指程序可以以序列化的方式一个一个处理输入,也就是说在一开始并不需要知道所有的输入。

离线算法:指一开始就需要知道问题的所有输入数据,而在解决一个问题后立即输出结果。

  • LCA算法实现

一、Tarjan 算法

思路:

  1. 任选一个节点为根节点,从根节点开始
  2. 遍历该点 u 的所有子节点 v ,并标记 v 已经被访问过
  3. 若 v 还有子节点,返回 2 ,否则下一步
  4. 合并 v 到 u 所在集合
  5. 寻找与当前点 u 有询问关系的点 e
  6. 若 e 已经被访问过,则可以确定 u、e 的最近公共祖先为 e 被合并到的父亲节点

(图源:传送门 %%%大佬)

存在查询: LCA(T,3,4)、LCA(T,4,6)、LCA(T,2,1) 

注意:每个节点的颜色代表它当前属于哪一个集合,橙色线条为搜索路径,黑色线条为合并路径。

当前所在位置为 u = 1 ,未遍历孩子集合 v = {2,5} ,向下遍历。

当前所在位置为 u = 2 ,未遍历孩子集合 v = {3,4} ,向下遍历。

当前所在位置为 u = 3 ,未遍历孩子集合 v = {} ,递归到达最底层,遍历所有相关查询发现存在 LCA(T,3,4) ,但是节点 4 此时标记未访问,因此什么也不做,该层递归结束。

递归返回,当前所在位置 u = 2 ,合并节点 3 到 u 所在集合,标记 vis[3] = true ,此时未遍历孩子集合 v = {4} ,向下遍历。

当前所在位置 u = 4 ,未遍历孩子集合 v = {} ,遍历所有相关查询发现存在 LCA(T,3,4) ,且 vis[3] = true ,此时得到该查询的解为节点 3 所在集合的首领,即 LCA(T,3,4) = 2 ;又发现存在相关查询 LCA(T,4,6) ,但是节点 6 此时标记未访问,因此什么也不做。该层递归结束。

递归返回,当前所在位置 u = 2 ,合并节点 4 到 u 所在集合,标记 vis[4] = true ,未遍历孩子集合 v = {} ,遍历相关查询发现存在 LCA(T,2,1) ,但是节点 1 此时标记未访问,因此什么也不做,该层递归结束。

递归返回,当前所在位置 u = 1 ,合并节点 2 到 u 所在集合,标记 vis[2] = true ,未遍历孩子集合 v = {5} ,继续向下遍历。

当前所在位置 u = 5 ,未遍历孩子集合 v = {6} ,继续向下遍历。

当前所在位置 u = 6 ,未遍历孩子集合 v = {} ,遍历相关查询发现存在 LCA(T,4,6) ,且 vis[4] = true ,因此得到该查询的解为节点 4 所在集合的首领,即 LCA(T,4,6) = 1 ,该层递归结束。

递归返回,当前所在位置 u = 5 ,合并节点 6 到 u 所在集合,并标记 vis[6] = true ,未遍历孩子集合 v = {} ,无相关查询因此该层递归结束。

递归返回,当前所在位置 u = 1 ,合并节点 5 到 u 所在集合,并标记 vis[5] = true ,未遍历孩子集合 v = {} ,遍历相关查询发现存在 LCA(T,2,1) ,此时该查询的解便是节点 2 所在集合的首领,即 LCA(T,2,1) = 1 ,递归结束。

代码:(由于LCA的题目千变万化,只给出最基本的模板)

void Tarjan(int x)
{for(int i=head[x];i!=-1;i=edge[i].next){int y=edge[i].dest;Tarjan(y);join(x,y);}vis[x]=true;if(x==u&&vis[v]){cout<<find(v)<<endl;return;}if(x==v&&vis[u]){cout<<find(u)<<endl;return;}
} //u,v具有询问关系

假若一棵树存在动态更新,此时离线算法就显得有点力不从心了(但是在其他情况下,离线算法往往效率更高)

二、倍增

在运用倍增算法处理LCA问题之前,我们先要知道普通的LCA,记录节点u,v到根节点的距离depth[u],depth[v],如果w是u,v的最近公共祖先,先让u往上走(depth(u)-depth(w))步,让v往上走(depth(v)-depth(w))步,都将走到节点w。因此,我们首先让u和v中较深的一个往上走|depth(u)-depth(v)|步,再一起一步步往上走,直到走到同一个节点,就可以在O(depth(u)+depth(v))的时间内求出LCA。

倍增做法的关键变量:

  • depth[]: 顾名思义,就是求一个点的深度(hint:根节点最好从0开始)

  • grand[i][j]: 指i节点向上跳2的j次方次跳到了那一个点上。

预处理:

void dfs(int x,int pre)
{vis[x]=true;depth[x]=depth[pre]+1; //预处理出每个节点深度及其父亲节点grand[x][0]=pre;for(int i=1; (1<<i)<=depth[x]; i++) //预处理出每个节点往上走2^k所到的节点{grand[x][i]=grand[grand[x][i-1]][i-1];}for(int i=head[x]; i!=-1; i=edge[i].next){int u=edge[i].to;if(!vis[u]) dfs(u,x);}
}

查询:

int lca(int a,int b)
{if(depth[a]>depth[b])swap(a,b);          //b在下面int f=depth[b]-depth[a];                      //f是高度差for(int i=0; (1<<i)<=f; i++)   //(1<<i)&f找到f化为2进制后1的位置,移动到相应的位置{if((1<<i)&f)b=grand[b][i];     //比如f=5(101),先移动2^0祖先,然后再移动2^2祖先}if(a!=b){for(int i=(int)log2(N); i>=0; i--) //log2(N)为树的最大深度{if(grand[a][i]!=grand[b][i])            //从最大祖先开始,判断a,b祖先,是否相同{a=grand[a][i];b=grand[b][i];     //如不相同,a b同时向上移动2^j}}a=grand[a][0];                            //这时a的father就是LCA}return a;
}

“一个部族,一个民族,一个弗雷尔卓德。”

寒冰射手艾希新学会了一个技能,艾希通过这个技能成为了一名声名远扬的神箭手,从此再也无人敢侵犯弗雷尔卓德!

这个技能的描述如下(假设英雄联盟内的每个人都有一个编号):

假设艾希有X-1(X> = 2)个敌人,每个敌人的编号分别为1〜X- 1,那么艾希的编号就是X。艾希每次使用这个技能,那么对于某个敌人,如果这个敌人的编号的最小素因子小于等于艾希的编号的最小素因子,那么艾希能对他造成致命一击。

现在假设已知有Ť场战争,每场战争有X-1个敌人,艾希想知道她每场战争使用,由于这个数目太大,她无法计算所以希望你编写一个。

一个数X的最小素因子:能够整除X的最小素数。比如2和4的最小素因子是2,3的最小素因子是3.我们知道1不是素数,但是为了题目的完整性,在这里我们定义1的最小素因子为1。

pph会长的毒瘤题,比赛的时候瞎写,素数筛+二重循环暴力枚举(用脚趾头思考都知道会T),正确思路是素数筛+线段树(树状数组),线段树(树状数组)维护前缀和,素数筛直接筛选最小素因子

首先我们要明确,树状数组可以实现的功能线段树都可以实现,但是树状数组不如线段树厉害,它的优点在于常熟小并且短小精悍,可以直接手推

树状数组实际上一种二叉树的变形和二进制的应用

(公式推导来源于:传送门)

将C[]数组的结点序号转化为二进制

1=(001)      C[1]=A[1];

2=(010)      C[2]=A[1]+A[2];

3=(011)      C[3]=A[3];

4=(100)      C[4]=A[1]+A[2]+A[3]+A[4];

5=(101)      C[5]=A[5];

6=(110)      C[6]=A[5]+A[6];

7=(111)      C[7]=A[7];

8=(1000)    C[8]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7]+A[8];

这时我们就可以推出: C[i]=A[i-2^k+1]+A[i-2^k+2]+......A[i]; (k为i的二进制中末尾0的个数)

这也就意味着我们可以用每一个数字来管理一段区间(同线段树)

树状数组的精髓(lowbit函数):

int lowbit(int x) return x&(-x);

lowbit简而言之就是取出x的最低位1 lowbit(x)=2^k 所以公式又可以写成:C[i]=A[i-lowbit(i)+1]+A[i-lowbit(i)+2]+......A[i];

这时我们就可以很容易看出,x&(-x)就是计算该节点的父节点 (真是神奇的操作啊..

那么我们在维护、使用树状数组的时候,利用累加。原来数组上利用累加的时候是i++,这样遍历一次数组时间复杂度为O(n),我们既然可以利用lowbit,那么累加的时候将x++改为x += lowbit(i) (x += x&-x),这样虽然我们还是在原数组上跳跃,但是可以抽象成在一个树上按顺序遍历。

区间更新:

void Add(int x,int y)
{for (; x<=NUM&&x; x+=x&-x)c[x]+=y;
}

跟线段树一样,操作是非常多变的,具体的操作有具体的写法,这里就随便写一个操作了。里面NUM这个全局变量,很明显是数据的总长度,设置成全局变量了,当然作为参数由外面传进来也是可以的。

区间查询:

与其说这里是区间查询 不如说是1-n的查询,但我们可以通过求两次取差的方法获取区间值,例如:Get(right) - Get(left)。不过如果是大量的区间运算,麻烦一点写成线段树更好。

int Get(int x)
{int ans=0;for (; x; x-=x&-x)ans+=c[x];return ans;
}

举个例子 i=7;

sum[7]=A[1]+A[2]+A[3]+A[4]+A[5]+A[6]+A[7] ;   前i项和

C[4]=A[1]+A[2]+A[3]+A[4];   C[6]=A[5]+A[6];   C[7]=A[7];

可以推出:   sum[7]=C[4]+C[6]+C[7];

序号写为二进制: sum[(111)]=C[(100)]+C[(110)]+C[(111)];

代码:

#include<bits/stdc++.h>
const int MAXN=1e6+10;
int son[MAXN],cnt[MAXN],ans[MAXN],c[MAXN];void Add(int x,int y)
{for (; x<=MAXN&&x; x+=x&-x)c[x]+=y;
}int Get(int x)
{int ans=0;for (; x; x-=x&-x)ans+=c[x];return ans;
}int main()
{son[1]=1;for(int i=2; i<=MAXN; i++){for(int j=i; j<=MAXN; j+=i){if(!son[j])son[j]=i;}}for (int i=1; i<MAXN; i++){ans[i]=Get(son[i]);Add(son[i],1);}int cas,n;scanf("%d",&cas);while(cas--){scanf("%d",&n);printf("%d\n",ans[n]);}return 0;
}

小明买年糕

洛谷P1314原题,二分+维护前缀和。二分W,前缀和记录大于W的wi (右边界开小了wa了一发

代码:

#include<bits/stdc++.h>
using namespace std;
const int maxn = 200005;
typedef long long ll;
int n,m;
ll s;
ll ans;
int w[maxn],v[maxn];
int l[maxn],r[maxn];ll labs(ll a)
{if(a<0) return -a;else return a;
}bool ok(int k)
{int i,j;int g[maxn],t[maxn];ll res=0;memset(g,0,sizeof(g)),memset(t,0,sizeof(t));for(i=1; i<=n; i++){if(w[i]>=k){g[i]=g[i-1]+v[i],t[i]=t[i-1]+1;}elseg[i]=g[i-1],t[i]=t[i-1];}ll u,v;for(i=1; i<=m; i++){u=g[r[i]]-g[l[i]-1];v=t[r[i]]-t[l[i]-1];res+=u*v;}ans=min(ans,labs(res-s));return res-s>0;
}int main()
{int i,j;ll lc,rc;scanf("%d %d %lld",&n,&m,&s);for(i=1; i<=n; i++){scanf("%d %d",&w[i],&v[i]);}for(i=1; i<=m; i++)scanf("%d %d",&l[i],&r[i]);ll mid;lc=0,rc=2e18,ans=1e20;while(lc<rc){mid=lc+rc>>1;if(!ok(mid))rc=mid;elselc=mid+1;}printf("%lld\n",ans);return 0;
}

可怜的ljb

树状数组(线段树)求逆序数 跟寒训一道题很像 但是比那道题多了一个预处理(重新映射原数组)

  • 什么是逆序数?

在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序数的总数就是这个排列的逆序数。

  • 预处理

我们可以知道,求逆序数与具体的数值无关 所以我们把原数组重新映射,令a[x]=i,b[i]=a[x],这样就对应上了位置关系

预处理完之后就是一个裸的树状数组求逆序数问题,求出有多少个逆序数对,然后逆序回来就是重新映射后的数组

代码:

#include<bits/stdc++.h>
using namespace std;
#define MAXN 100005
#define LL long long
int N,a[MAXN],b[MAXN];
int c[MAXN];
LL ans;inline void Add(int x,int y)
{for (; x<=N&&x; x+=x&-x)c[x]+=y;
}inline int Get(int x)
{int ans=0;for (; x; x-=x&-x)ans+=c[x];return ans;
}int main()
{int T;scanf("%d",&T);while(T--){scanf("%d",&N);ans=0;memset(c,0,sizeof(c));for(int i=1,x; i<=N; ++i ){scanf("%d",&x);a[x]=i;}for(int i=1,x; i<=N; ++i){scanf("%d",&x);b[i]=a[x];}for (int i=N; i>=1; --i ){ans+=Get(b[i]-1); //保证严格小于b[i]Add(b[i],1);}printf("%lld\n",ans);}return 0;
}

csust2019集训队选拔赛题解相关推荐

  1. CSUST 集训队选拔赛题解

    选拔赛的题解,~~~ 题目链接:请点击 A题 素数筛 + 线段树(树状数组) 先用素数筛打表,然后线段树更新,遍历求出值,O(1)查询即可 AC代码: /*num数组 是把记录 数是否存在 存在即为1 ...

  2. CSUST2020集训队选拔赛题解

    E.恶心心的题 题意: 给一个序列 ai,q次询问,求每次LCM(al-ar,x)的值,对p取模. 思路: 先对每个数都唯一分解吧,考虑一下怎么求多个数的 lcm;举个例子 2 ^ 3 * 3 ^ 1 ...

  3. 北京信息科技大学第十三届程序设计竞赛暨ACM选拔赛题解

    北京信息科技大学第十三届程序设计竞赛暨ACM选拔赛题解 A lzh的蹦床 B 所谓过河 C 旅行家问题1 D 旅行家问题2 E 小菲和Fib数列 F 好玩的音乐游戏 G ranko的手表 H 字母收集 ...

  4. XMU区域赛选拔赛题解

    XMU区域赛选拔赛题解 B.是谁打了奶奶 Description 最近发生了一起骇人听闻的打奶奶事件,凶手就是--惊奇队长. 惊奇队长是在电车上打的奶奶,那么我们就来看一个和电车有关的问题. 某市修建 ...

  5. 软件学院集训队第一次选拔赛题解

    前言 本套题对大多数同学而言比较偏难,但涵盖了很多算法的基础知识,希望借这套题,让大家体会到算法的学习历程.有些题做不出来,对于你们现在来说是非常正常的,相信大家学习了一段时间后,再看这些题,肯定会感 ...

  6. HNUCM2020年春季ACM集训队选拔赛(2)题解

    问题 A: 爱的日期 题目描述 Inter和AMD刚刚在上个学期确定了恋爱关系,但是由于要期末考试,他们没法have a appointment. 所以他们打算在2月14日情人节那天一起出去.恰恰最近 ...

  7. 2018 焦作站亚洲区域赛校内选拔赛题解

    SUST_2018 焦作站亚洲区域赛校内选拔赛 A.高速        by yoyo tag:图论.最短路 //最短路 #include<bits/stdc++.h> using nam ...

  8. 2018中国大学生程序设计竞赛-网络选拔赛题解

    以下所有AC题解程序来自"仙客传奇"团队. A. Buy and Resell AC的C++语言程序: #include<iostream> #include<c ...

  9. SUST_2018 焦作站亚洲区域赛校内选拔赛题解

    SUST_2018 焦作站亚洲区域赛校内选拔赛 A.高速        by yoyo tag:图论.最短路 //最短路 #include<bits/stdc++.h> using nam ...

最新文章

  1. Web Api学习一
  2. 到底为什么你我都要了解社会工程学
  3. Hadoop学习笔记(8) ——实战 做个倒排索引
  4. 基于simulink的永磁无刷直流电机控制系统的仿真研究
  5. cookie工具类 java_springboot封装JsonUtil,CookieUtil工具类代码实例
  6. 大三下学期十一周总结
  7. Scala入门到精通——第二十八节 Scala与JAVA互操作
  8. libghttp 编译及封装使用实例
  9. 统信uos系统考试题_148款!富士通及旗下晟拓品牌系列打印机适配统信UOS
  10. [Poi2011]Tree Rotations线段树合并
  11. 蚂蚁金服OceanBase性价比是传统数据库的十倍
  12. 飞鸽传书2011比飞鸽传书2007的进化
  13. 12096 - The SetStack Computer
  14. stm32h7高速通信_【STM32H7教程】第75章 STM32H7的SPI总线应用之驱动DAC8501(双路输出,16bit分辨率,0-5V)...
  15. 每日小记 2017.2.26
  16. (转载)Hadoop map reduce 过程获取环境变量
  17. Ubuntu18.04安装Docker并构建JDK1.8镜像
  18. 考研专业型计算机软件与理论,计算机专业考研方向:计算机软件与理论
  19. NFL计划将数据跟踪芯片放入橄榄球中
  20. spring + springmvc +mybatis 搭建 maven 项目的核心配置文件

热门文章

  1. 微软浏览器如何安装addon(插件)
  2. 英语怎么形容“漂亮女孩”(转)
  3. Java错误类型:Exception in thread main java.lang.Error: Unresolved compilation problem: Syntax error,
  4. 分布式事务 seata 最全入门教程
  5. android 获取wifi的ip地址吗,Android开发实现在Wifi下获取本地IP地址的方法
  6. 《06》个人博客不完整版
  7. 【机器学习百科全书目录】PRML ESL MLAPP 西瓜书 花书 RLAI 统计学习方法 蒲公英书
  8. 尝试加载 Oracle 客户端库时引发 BadImageFormatException
  9. 浅谈a++ 与a--
  10. Docker基础认识与docker安装以及环境配置