2019.10.30 队测(晚上)
Posted nldqy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2019.10.30 队测(晚上)相关的知识,希望对你有一定的参考价值。
T1:
题目链接:Click here
Solution:
考虑把给定的地图建出图来,那么询问实际上就是询问图上两点所有路径中最大边权的最小值
询问是一个老问题了,把边按权升序排列,用kruskal重构树,答案即为树上两点lca的点权
考虑如何建图,我们用一个bfs来建图即可,每次扩展到一个被其他城市扩展过的点,就加入一条边
因为不知道有多少条边,我们用vector来存边,注意判断两点是否在一个连通块内,注意路径压缩(不能直接用fa[x]啊)
Code:
#include<bits/stdc++.h>
using namespace std;
const int N=4e5+11;
const int M=2e3+11;
const int dx[]={0,1,-1,0};
const int dy[]={1,0,0,-1};
struct E{int x,y,val;};
char s[M][M];
int n,m,P,Q,col[M][M],dis[M][M];
int v[N],rt[N],dep[N],f[N][20];
int tot,fa[N],block;
bitset<N> vis;
queue<E> q;
vector<E> edge;
vector<int> g[N];
inline bool cmp(E u,E v){return u.val<v.val;}
inline bool in(int x,int y,int id){
if(s[x][y]=='#'||col[x][y]==id) return 0;
if(x<1||x>n||y<1||y>m) return 0;
return 1;
}
int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void ins(int x,int y){g[x].push_back(y);}
void bfs(){
while(!q.empty()){
E u=q.front();q.pop();
int x=u.x,y=u.y;
for(int i=0;i<4;i++){
int nx=x+dx[i],ny=y+dy[i];
if(!in(nx,ny,col[x][y])) continue;
if(col[nx][ny]){
int id1=col[x][y],id2=col[nx][ny];
int Dis=dis[x][y]+dis[nx][ny];
edge.push_back((E){id1,id2,Dis});
}else{
col[nx][ny]=col[x][y];
dis[nx][ny]=dis[x][y]+1;
q.push(E{nx,ny,0});
}
}
}
sort(edge.begin(),edge.end(),cmp);
}
void kruskal(){
tot=P;
for(int i=1;i<=P*2;i++) fa[i]=i;
for(int i=0;i<edge.size();i++){
int x=edge[i].x,y=edge[i].y;
x=find(x),y=find(y);
if(x==y) continue;
int z=edge[i].val;
++tot;v[tot]=z;
ins(x,tot),ins(tot,x);
ins(y,tot),ins(tot,y);
fa[x]=fa[y]=tot;
}
for(int i=1;i<=tot;i++) find(i);
for(int i=1;i<=tot;i++)
if(!vis[fa[i]]) rt[++block]=fa[i],vis[fa[i]]=1;
}
void dfs(int x){
for(int i=0;i<g[x].size();i++){
int y=g[x][i];
if(y==f[x][0]) continue;
f[y][0]=x,dep[y]=dep[x]+1;
dfs(y);
}
}
void trans(){
for(int i=1;i<=19;i++)
for(int j=1;j<=tot;j++)
f[j][i]=f[f[j][i-1]][i-1];
}
int lca(int x,int y){
if(fa[x]!=fa[y]) return 0;
if(dep[y]>dep[x]) swap(x,y);
for(int i=19;i>=0;i--)
if(dep[f[x][i]]>=dep[y]) x=f[x][i];
if(x==y) return x;
for(int i=19;i>=0;i--)
if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-f;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
signed main(){
freopen("water.in","r",stdin);
freopen("water.out","w",stdout);
n=read(),m=read();
P=read(),Q=read();
for(int i=1;i<=n;i++)
scanf("%s",s[i]+1);
for(int i=1;i<=P;i++){
int x=read(),y=read();
col[x][y]=i;
q.push((E){x,y,0});
}
bfs();kruskal();
for(int i=1;i<=block;i++) dfs(rt[i]);
trans();v[0]=-1;
for(int i=1;i<=Q;i++){
int x=read(),y=read();
printf("%d
",v[lca(x,y)]);
}
return 0;
}
T2
题面:对于带权树我们定义了一个叫"连通能力"的奇怪属性。对于一棵边上有权值的树(N 个 结点 N - 1 条边的无向连通图),我们按以下方法定义其连通能力: 1、规定某结点的代价为它到其它结点的距离(简单路径所经过边的权值和)的最大值; 2、代价最小的结点的代价作为这棵树的连通能力。 设某棵给定的树以 1 号结点为根,Star 想考察以任意结点为根的子树的连通能力有多大。 请你帮助他把这 N 个值快速求出来。
数据范围:第一行一个整数 N; 接下来 N - 1 行,每行三个整数 u、v、w 表示结点 u、v 间存在权值为 w 的边。 1 ≤ N ≤ 1000000、1 ≤ w ≤ 10000。
Solution:
直径有一个众所周知的性质,即对于树上任意一点,离他距离最大的点,一定是直径某个端点
那么我们可以得出结论,对于一棵树,他的代价最小的点,即为直径的中点
考虑如何求直径,treedp即可,再记录一下直径的端点,每次从上次停下来的点暴力跳fa即可
这样显然只会跳(O(n))次,总时间复杂度(O(n))
Code:
#include<bits/stdc++.h>
#define int unsigned long long
using namespace std;
const int N=1e6+1;
int n,cnt,head[N],fa[N];
int f[N],dis[N],dia[N];
int mx[N],nmx[N],st[N],ed[N];
struct Edge{int nxt,to,val;}edge[N<<1];
void ins(int x,int y,int z){
edge[++cnt].nxt=head[x];
edge[cnt].to=y;head[x]=cnt;
edge[cnt].val=z;
}
void dfs(int x,int fat){
st[x]=ed[x]=x;
for(int i=head[x];i;i=edge[i].nxt){
int y=edge[i].to;
if(y==fat) continue;
dis[y]=dis[x]+edge[i].val;
fa[y]=x;dfs(y,x);
if(dia[y]>dia[x]){
dia[x]=dia[y];
f[x]=f[y];
}
if(mx[y]+edge[i].val>mx[x]){
ed[x]=st[x];st[x]=st[y];
nmx[x]=mx[x];mx[x]=mx[y]+edge[i].val;
}else
if(mx[y]+edge[i].val>nmx[x])
nmx[x]=mx[y]+edge[i].val,ed[x]=st[y];
}if(mx[x]+nmx[x]>dia[x]){
dia[x]=mx[x]+nmx[x];
while(st[x]!=x&&nmx[x]+dis[fa[st[x]]]-dis[x]>=(dia[x]+1)/2) st[x]=fa[st[x]];
f[x]=nmx[x]+dis[st[x]]-dis[x];
if(st[x]!=x&&dia[x]-nmx[x]-(dis[fa[st[x]]]-dis[x])<f[x])
f[x]=dia[x]-nmx[x]-(dis[fa[st[x]]]-dis[x]);
}
}
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-f;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
signed main(){
freopen("connect.in","r",stdin);
freopen("connect.out","w",stdout);
n=read();
for(int i=1;i<n;i++){
int x=read(),y=read(),z=read();
ins(x,y,z),ins(y,x,z);
}dfs(1,0);
for(int i=1;i<=n;i++)
printf("%lld
",f[i]);
return 0;
T3
题面:给定一棵无根树,边权都是1,请去掉一条边并加上一条新边,定义直径为最远的两个点的距离,请输出所有可能的新树的直径的最小值和最大值
数据范围:第一行包含一个正整数n(3<=n<=500000),表示这棵树的点数。 接下来n-1行,每行包含两个正整数u,v(1<=u,v<=n),表示u与v之间有一条边。
Solution:
考虑割掉一条边之后,再把两棵树拼接起来之后的最长直径和最短直径怎么算
最长的直径显然是把两棵树的直径在端点处连接,最短的直径则是把两条直径在中点处连接
那么我们的问题则在于怎么算原树去掉一棵子树后的直径了
我们对每个点记录这些东西:(f[x],g[x],v[x],up[x],dis[x],dis1[x],dis2[x])
(f[x])代表从(x)点向下延升的最长链的长度,(g[x])代表次长,(v[x])代表第三长
(up[x])表示以(x)为端点,另一端不在(x)的子树内的最长链的长度(即向上最长链)
(dis[x])代表以(x)为根的子树的直径长度,(dis1[x])代表以(x)的儿子为根的子树的直径中的最大值,(dis2[x])代表次大
我们设(dia[x])表示原树去掉以(x)为根的子树后的直径长度,我们用(dfs)来求这个值
(dia[x])显然为(fa[x])去掉(x)子树后,剩下的最长链加次长链,这里的链包括了(up[fa[x]]+1)
然后(dia[x])再与原本的直径取max,即(dia[x]=max(dia[x],dia[fa[x]]))
事实上,(dia[x])也可能为(fa[x])去掉(x)子树后剩下的子树的直径,即若(x)是(fa[x])直径最大的儿子,(dia[x]=max(dia[x],dis2[x])),否则(dia[x]=max(dia[x],dis1[x]))
本题细节较多,需要注意
Code:
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+11;
const int inf=192608170;
int n,cnt,head[N],fa[N];
int dis1[N],dis2[N],dis[N];
int f[N],g[N],v[N];
int minn=inf,maxx=-inf;
struct Edge{int nxt,to;}edge[N<<1];
void ins(int x,int y){
edge[++cnt].nxt=head[x];
edge[cnt].to=y;head[x]=cnt;
}
int dmt(int x,int y){
int tx=x/2,ty=y/2;
if(x&1) ++tx;if(y&1) ++ty;
return max(tx+ty+1,max(x,y));
}
void dfs(int x){
for(int i=head[x];i;i=edge[i].nxt){
int y=edge[i].to;
if(y==fa[x]) continue;
fa[y]=x;dfs(y);
v[x]=max(f[y]+1,v[x]);
if(v[x]>g[x]) swap(v[x],g[x]);
if(g[x]>f[x]) swap(g[x],f[x]);
dis[x]=max(dis[x],dis[y]);
dis2[x]=max(dis[y],dis2[x]);
if(dis2[x]>dis1[x]) swap(dis2[x],dis1[x]);
}dis[x]=max(dis[x],f[x]+g[x]);
}
void calc(int x,int ndis,int up){
if(x!=1){
int dv=dmt(dis[x],ndis);
if(dv<minn) minn=dv;
if(dis[x]+ndis+1>maxx) maxx=dis[x]+ndis+1;
}
for(int i=head[x];i;i=edge[i].nxt){
int y=edge[i].to,dv,pps;
if(y==fa[x]) continue;
dv=f[x],pps=dis1[x];
if(pps==dis[y]) pps=dis2[x];
if(dv==f[y]+1) dv=g[x],pps=max(pps,g[x]+max(v[x],up));
else if(g[x]==f[y]+1) pps=max(pps,f[x]+max(v[x],up));
else pps=max(pps,f[x]+max(g[x],up));
dv=max(up,dv);pps=max(pps,ndis);
calc(y,pps,dv+1);
}
}
int read(){
int x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-f;ch=getchar();}
while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
return x*f;
}
signed main(){
freopen("r.in","r",stdin);
freopen("r.out","w",stdout);
n=read();
for(int i=1;i<n;i++){
int x=read(),y=read();
ins(x,y),ins(y,x);
}dfs(1);calc(1,0,0);
printf("%d
%d",minn,maxx);
return 0;
}
(dia[x])
以上是关于2019.10.30 队测(晚上)的主要内容,如果未能解决你的问题,请参考以下文章