• 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])

  • 相关阅读:
    29-赫夫曼树
    28-线索化二叉树
    27-顺序存储二叉树
    26-二叉树的遍历查找和删除
    25-二叉树的概念
    24-逻辑结构分析
    23-哈希表
    22-查找算法
    21-堆排序
    Mui-列表/table-view
  • 原文地址:https://www.cnblogs.com/NLDQY/p/11763782.html
Copyright © 2020-2023  润新知