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 队测(晚上)的主要内容,如果未能解决你的问题,请参考以下文章

2018.10.23队测

2018.10.17队测T1

2018.10.17队测T3

2018-10-16队测

队测 逆序对 permut

18.10.16 队测