求法

目录
因为2^16>4e4,所以开15够了
信息学奥赛一本通(C++版)在线评测系统 (ssoier.cn)
- #include
- using namespace std;
- const int N=4e4+10,M=2*N;
- int h[N],e[M],ne[M],idx;
- int n,m,root;
- int depth[N],f[N][16],q[N];//depth存的是每个节点的深度,f存的是以节点i开始跳2的j次方步到达的点
- void add(int a,int b)
- {
- e[idx]=b,ne[idx]=h[a],h[a]=idx++;
- }
- void bfs()
- {
- memset(depth,0x3f,sizeof depth);//初始化每个深度
- int hh=0,tt=0;
- q[0]=root;//把根节点放进队列中
- depth[0]=0,depth[root]=1;//初始化0为虚拟根,因为待会跳的话会跳出根节点,根节点就定义为第一层
- while(hh<=tt)
- {
- int t=q[hh++];
- for(int i=h[t];~i;i=ne[i])
- {
- int j=e[i];
- if(depth[j]>depth[t]+1)//假如这个节点没被更新过
- {
- depth[j]=depth[t]+1;//更新这个节点的深度
- q[++tt]=j;//把这个点放进队列中
- f[j][0]=t;//从j开始跳2^0=1步是t
- for(int k=1;k<=15;k++)//更新这个点跳的步数走到的点
- f[j][k]=f[f[j][k-1]][k-1];//用递归的方式更新
- }
- }
- }
- }
- int lca(int a,int b)
- {
- if(depth[a]
swap(a,b);//让a是深度最大的点 - //让a跳到跟b同个深度
- for(int i=15;i>=0;i--)//让a跳到跟b同个深度,从大的步数开始跳
- if(depth[f[a][i]]>=depth[b])
- a=f[a][i];//更新跳到的点
- if(a==b) return b;//假如跳到了b,说明b是最近公共祖先
- //在同一深度下一起跳2^i步来找公共祖先
- for(int i=15;i>=0;i--)
- if(f[a][i]!=f[b][i])
- {
- a=f[a][i];//更新跳到的点
- b=f[b][i];//更新跳到的点
- }
- return f[a][0];//因为是跳到公共祖先的下一层,则输出他们点的上一步就是公共祖先了
- }
- int main()
- {
- cin>>n;
- memset(h,-1,sizeof h);
- while(n--)
- {
- int a,b;
- cin>>a>>b;
- if(b==-1) root=a;
- else add(a,b),add(b,a);
- }
- bfs();//先搜索一下深度和跳多少步到那个位置
- cin>>m;
- while(m--)
- {
- int a,b;
- cin>>a>>b;
- int p=lca(a,b);//求一下公共祖先
- if(p==a) puts("1");
- else if(p==b) puts("2");
- else puts("0");
- }
- return 0;
- }
tarjan是离线算法就是先把所有询问存下来,然后在求
- #include
- using namespace std;
- typedef pair<int,int> pii;
- #define x first
- #define y second
- const int N=2e5+10,M=2*N;
- int n,m;
- int h[N],e[M],ne[M],w[M],idx;
- int ans[N],dist[N];//ans是记录每个点的答案,diat存的是点跟1号点的距离
- int st[N];//st存的是三种状态,0表示还没遍历过,1表示争做遍历,2表示已经遍历过
- int p[N];//用来并查集
- vector
query[N];//第一个存的是询问的点,第二个存的是下标 - void add(int a,int b,int c)
- {
- e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
- }
- void dfs(int u,int fa)//处理每个点跟1的距离
- {
- for(int i=h[u];~i;i=ne[i])
- {
- int j=e[i];
- if(j==fa) continue;
- dist[j]=dist[u]+w[i];
- dfs(j,u);
- }
- }
- int find(int x)//并查集
- {
- if(p[x]!=x) p[x]=find(p[x]);
- return p[x];
- }
- void tarjan(int u)
- {
- st[u]=1;//标记这个点正在遍历
- for(int i=h[u];~i;i=ne[i])
- {
- int j=e[i];
- if(!st[j])//假如没有遍历过
- {
- tarjan(j);//则遍历一遍这个点
- p[j]=u;//把这个点记录是跟u一个集合的
- }
- }
- for(auto item:query[u])//处理所有询问
- {
- int y=item.x,id=item.y;//y是询问的点,id是询问的位置
- if(st[y]==2)//假如是遍历过的点
- {
- int anc=find(y);//找一下是由那个点的集合,也就是最近公共祖先
- ans[id]=dist[y]+dist[u]-2*dist[anc];//答案就是y与1距离加上u的距离减去最近公共祖先距离的两倍就是答案
- }
- }
- st[u]=2;//标记这个点已经遍历过
- }
- int main()
- {
- scanf("%d%d",&n,&m);
- memset(h,-1,sizeof h);
- for(int i=1;i
- {
- int a,b,c;
- scanf("%d%d%d",&a,&b,&c);
- add(a,b,c),add(b,a,c);
- }
- for(int i=0;i
- {
- int a,b;
- scanf("%d%d",&a,&b);
- if(a!=b)//假如相等则答案就是0,不用求
- {
- query[a].push_back({b,i});//把a询问的b和下标i放进a询问中
- query[b].push_back({a,i});//把b询问的a和下标i放进b询问中
- }
- }
- dfs(1,-1);//处理每个点跟1号点的距离
- for(int i=1;i<=n;i++) p[i]=i;//并查集初始化
- tarjan(1);//做一遍Trajan算法
- for(int i=0;i
printf("%d\n",ans[i]); - return 0;
- }
3.次小生成树(kruskal+倍增)
因为2^17>1e5,所以开17就行了
kruskal求最小生成树,倍增求两点之间的最大和次大边
- #include
- using namespace std;
- typedef long long ll;
- const int N=1e5+10,M=3e5+10,INF=0x3f3f3f3f;
- int p[N];//并查集
- int h[N],e[M],ne[M],w[M],idx;
- int depth[N],d1[N][17],d2[N][17],f[N][17];//depth是深度,d1是某个点走2^j次方步的最大值,d2是次大值,f是某个点走2^k次方步所到达的点
- int q[N];
- int n,m;
- struct Edge
- {
- int a,b,w;
- bool used;//用来标记是否是树边
- bool operator <(const Edge &t) const
- {
- return w
- }
- }edge[M];
- void add(int a,int b,int c)
- {
- e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
- }
- int find(int x)
- {
- if(p[x]!=x) p[x]=find(p[x]);
- return p[x];
- }
- ll kruskal()//求最小生成树
- {
- memset(h,-1,sizeof h);//初始化
- for(int i=1;i<=n;i++) p[i]=i;//初始化
- sort(edge,edge+m);//排序
- ll res=0;
- for(int i=0;i
- {
- int a=find(edge[i].a),b=find(edge[i].b),w=edge[i].w;
- if(a!=b)//假如不在一个集合
- {
- p[a]=b;//合并成一个集合
- res+=w;//加上这条最小生成树的边
- edge[i].used=true;//标记一下是树边
- add(edge[i].a,edge[i].b,w),add(edge[i].b,edge[i].a,w);//建树图
- }
- }
- return res;//返回最小生成树的权值和
- }
- void bfs()//求 深度 最大值 次大值 每个点走几步由那个点更新过来
- {
- memset(depth,0x3f,sizeof depth);
- depth[0]=0,depth[1]=1;//0号点是避免跳出去,1号点初始化深度为1
- int hh=0,tt=0;
- q[0]=1;//1号点入队
- while(hh<=tt)
- {
- int t=q[hh++];
- for(int i=h[t];~i;i=ne[i])
- {
- int j=e[i];
- if(depth[j]>depth[t]+1)//假如没有更新过
- {
- depth[j]=depth[t]+1;//则更新深度
- q[++tt]=j;//把这个点入队
- f[j][0]=t;//j这个点走2^0=1步是t
- d1[j][0]=w[i],d2[j][0]=-INF;//j这个点向上走2^0=1步的最大值是w[i],次大没有标记为负无穷
- for(int k=1;k<=16;k++)//更新跳到的点
- {
- int anc=f[j][k-1];//表示由那个点跳过来的
- int d[4]={d1[anc][k-1],d2[anc][k-1],d1[j][k-1],d2[j][k-1]};//把j~anc~k的最大次大加到d中
- f[j][k]=f[anc][k-1];//更新跳的点
- d1[j][k]=d2[j][k]=-INF;//先初始化为负无穷
- for(int u=0;u<4;u++)//求一遍最大和次大
- {
- int dist=d[u];
- if(dist>d1[j][k]) d2[j][k]=d1[j][k],d1[j][k]=dist;
- else if(dist!=d1[j][k]&&dist>d2[j][k]) d2[j][k]=dist;
- }
- }
- }
- }
- }
- }
- int lca(int a,int b,int w)//用来求最大值与次大值能否更新
- {
- static int d[2*N];
- int cnt=0;
- if(depth[a]
swap(a,b);//假如a深度低,则交换 - for(int k=16;k>=0;k--)//让a与b同个深度
- if(depth[f[a][k]]>=depth[b])//假如还是大,则继续跳
- {
- //把他们跳过的位置的最大值与次大值都记录下来
- d[cnt++]=d1[a][k];
- d[cnt++]=d2[a][k];
- a=f[a][k];//更新跳的点
- }
- if(a!=b)//假如还没跳到一起,也就是没找到最近公共祖宗
- {
- for(int k=16;k>=0;k--)//让a和b一起跳
- if(f[a][k]!=f[b][k])
- {
- //把他们跳过的位置的最大值与次大值都记录下来
- d[cnt++]=d1[a][k];
- d[cnt++]=d2[a][k];
- d[cnt++]=d1[b][k];
- d[cnt++]=d2[b][k];
- a=f[a][k],b=f[b][k];//更新跳过的位置
- }
- //最后也要把跳到最近公共祖先的位置也记录下来
- d[cnt++]=d1[a][0];
- d[cnt++]=d2[a][0];
- d[cnt++]=d1[b][0];
- d[cnt++]=d2[b][0];
- }
- int dist1=-INF,dist2=-INF;
- for(int i=0;i
//求一遍最大值和次大值 - {
- int dist=d[i];
- if(dist>dist1) dist2=dist1,dist1=dist;
- else if(dist!=dist1&&dist>dist2) dist2=dist;
- }
- if(w>dist1) return w-dist1;//假如最大值可以用来更新
- if(w>dist2) return w-dist2;//反之次大值可以用来更新
- return INF;//反之为正无穷
- }
- int main()
- {
- scanf("%d%d",&n,&m);
- for(int i=0;i
- {
- int a,b,w;
- scanf("%d%d%d",&a,&b,&w);
- edge[i]={a,b,w};
- }
- ll sum=kruskal();//求一下最小生成树的权值和
- bfs();//求深度 最大值 次大值 更新的点
- ll res=1e18;
- for(int i=0;i
- if(!edge[i].used)//假如是非树边
- {
- int a=edge[i].a,b=edge[i].b,w=edge[i].w;
- res=min(res,sum+lca(a,b,w));//求一遍最大值与次大值能否用来更新
- }
- printf("%lld\n",res);
- return 0;
- }
4. 闇の連鎖(倍增+树形差分)

c表示所有非树边构成的换的树边+1,假如该树边的c大于1 则说明得砍大于1的非树边,则不满足题意方案则+0,假如等于1,说明砍1条非树边就行则方案+1,假如等于0,说明不管怎么砍非树边都满足则+m
然后这题要用到树形差分就是d[x]+=c,d[y]+=c,d[p]-=c,p是x与y的最近公共祖先
- #include
- using namespace std;
- typedef long long ll;
- const int N=1e5+10,M=2e5+10;
- int h[N],e[M],ne[M],idx;
- int depth[N],f[N][17];//depth是深度,f是记录条几步条到的点
- int q[N],d[N];//d存的就是c,就是树形差分
- int ans,n,m;;
- void add(int a,int b)
- {
- e[idx]=b,ne[idx]=h[a],h[a]=idx++;
- }
- void bfs()
- {
- memset(depth,0x3f,sizeof depth);
- depth[0]=0,depth[1]=1;//0号点是虚拟原点,1号点让他为跟
- int hh=0,tt=0;
- q[0]=1;//1号点放进队列中
- while(hh<=tt)
- {
- int t=q[hh++];
- for(int i=h[t];~i;i=ne[i])
- {
- int j=e[i];
- if(depth[j]>depth[t]+1)//假如还没更新过
- {
- depth[j]=depth[t]+1;//则更新
- q[++tt]=j;//把这个点放进队列中
- f[j][0]=t;//标记这个点的上一步是t
- for(int k=1;k<=16;k++)//更新条的其他步到达的点
- f[j][k]=f[f[j][k-1]][k-1];
- }
- }
- }
- }
- int lca(int a,int b)
- {
- if(depth[a]
swap(a,b);//假如a的深度小于b的则交换 - for(int k=16;k>=0;k--)//让a跳到跟b同个深度
- if(depth[f[a][k]]>=depth[b])
- a=f[a][k];//更新跳到的点
- if(a==b) return b;//假如b是最近公共祖先了,则返回
- for(int k=16;k>=0;k--)//让a跟b一起跳
- if(f[a][k]!=f[b][k])
- a=f[a][k],b=f[b][k];//更新两个跳到的位置
- return f[a][0];//返回他们的上一个就是最近公共祖先节点
- }
- //该点的值就是子树的差分前缀和,跟其他差分一样都是差分前缀和就是该点的值
- int dfs(int u,int fa)//返回值是子树差分值前缀和
- {
- int res=d[u];//res记录的是树形前缀和,把这个点的差分也加上
- for(int i=h[u];~i;i=ne[i])
- {
- int j=e[i];
- if(j==fa) continue;//避免重复搜索
- int s=dfs(j,u);//s是j这个子树的差分前缀和
- if(s==0) ans+=m;//假如是0则加上m
- else if(s==1) ans++;//假如是1则加1
- res+=s;//把所有子树的差分都加上
- }
- return res;
- }
- int main()
- {
- memset(h,-1,sizeof h);
- scanf("%d%d",&n,&m);
- for(int i=1;i
- {
- int a,b;
- scanf("%d%d",&a,&b);
- add(a,b),add(b,a);
- }
- bfs();//求一遍深度和跳到的点
- for(int i=1;i<=m;i++)
- {
- int a,b;
- scanf("%d%d",&a,&b);
- int p=lca(a,b);//求a跟b的公共祖先
- d[a]++,d[b]++,d[p]-=2;//因为是差分,则a+1,b+1,p-2
- }
- dfs(1,-1);//最后求一遍所有的方案
- printf("%d\n",ans);
- return 0;
- }
-
相关阅读:
Linux 学习总结(93)—— Linux 管道符使用总结
Python && JAVA 去除字符串中空格的五种方法
CISSP学习笔记:事件预防和响应
three.js中关于摄影机
【Unity3D】运动模糊特效
基于resnet网络架构训练图像分类模型
OKHttp
跨平台代码编写规范——参考《Loup&卡普》的文档
OpenCV(二十):图像卷积
常见移动端导航类型
-
原文地址:https://blog.csdn.net/m0_63729880/article/details/126404265