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\n",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\n",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\n%d",minn,maxx);
    return 0;
}

\(dia[x]\)

原文地址:https://www.cnblogs.com/NLDQY/p/11763782.html

时间: 2024-08-30 00:36:49

2019.10.30 队测(晚上)的相关文章

18.10.16 队测

T1 : 求给定集合有多少个非空子集可以分割成两个集合,使得它们的和相等. 其中:\(n\leq20~,~a[i]\leq1e8\) 题目可以转化成:给每个数前添加一个系数\(p\in[-1,1]\),使得和为0的方案数. 由于\(n\)比较小,所以可以考虑爆搜.朴素的爆搜可以枚举每个数不选,第一个集合,第二个集合,复杂度\(O(3^n)\) . 然后发现根据套路,可以想到折半搜索\((meet~ in~ the ~middle)\) 先爆搜前十个的状态和\(sum\),然后爆搜后十个进行匹配,

2018.10.15队测T2

题意 给出n个与坐标轴平行的线段,保证没有两条共线的线段具有公共点,没有重合的线段 找出最大的十字形并输出大小R,大小为R的十字形指的是以一个中心点向四周延伸出R单位长度形成的图形 1≤n≤100000,所有坐标的范围在-10^9~10^9中 暴力 把线段按长度排序,O(n^2)枚举+O2 原文地址:https://www.cnblogs.com/tangjingrong/p/9798668.html

2019.10.30 csp-s模拟测试94 反思总结

头一次做图巨的模拟题OWO 自从上一次听图巨讲课然后骗了小礼物以后一直对图巨印象挺好的233 T1: 对于XY取对数=Y*log(x) 对于Y!取对数=log(1*2*3*...*Y)=log1+log2+log3+...+logY 因为数字大小不超过1e5,直接累加最后比较就可以了 #include<iostream> #include<cstdio> #include<cmath> using namespace std; int t,x,y; double a,b

2018.10.15队测T3

题意 在一个网格图上,每次删掉一条边(u,v),再询问能否从u到v,如果能,就输出"HAHA",并删掉给出的该情况对应的边,否则就输出"DAJIA",并删掉另一条边 网格图大小<=500 1.删掉一条边,就相当于把边两侧的块联通了 于是就想到了并查集 2.两个顶点删边后不连通的情况: 即块A和块B在删边前已经在同一联通块中 另一种情况: 即在边界处的边被删了,这时可以看成块A块B与编号为0的块联通了 正解: 用并查集维护每个块的联通性,如果两个块在删边前就已处

2018.10.15队测

T1:算是sb题吧,我几乎完全不记得折半搜索了,虽然考试中想到过类似的做法,但是时间过不去就没想了.测试后惊讶发现我居然写过这道题,一模一样,但是一点印象都没有.也是个教训,以后学过的东西还是得复习.折半搜索这思路还是蛮简单的. 链接:subsets T2:这题是真的sao,我tm被这题坑死了,看他的描述就想到一个逆序对的结论,然后疯狂的去判无解的情况,然后写了个没有道理的贪心水分,事实证明一点道理都没有,还让我没时间写T3的暴力. 链接:swap T3:这题好像暂时是出锅了,留个坑待填. 原文

2018.10.17队测T3

题意 一棵n个节点的树,q次询问,每次询问编号为到l~r的节点构成的联通块个数 发现一条边(u,v)如果在联通块内,则l≤u≤r且l≤v≤r, 这就是二维偏序问题 所以每次求出满足条件的边的个数num,ans即为r-l+1-num 原文地址:https://www.cnblogs.com/tangjingrong/p/9813590.html

2018.10.23队测

T1:我不知道这是什么鬼啊,学长们推出一个部分分的结论,我就直接写了,18分,结果题目还锅了,不费改. T2:这题也锅了,改完数据后据说别校全场切了,听到学长说的差分序列后就想出标算的做法了,可惜学长他们写的不一样. 链接:sequence T3:状压大模拟,太恶心了,留坑... 原文地址:https://www.cnblogs.com/lcxer/p/9845748.html

2019.10.30题解

写在前面: 有6天没更博客了,一直在学Sam,这几天的考试似乎不太理想,濒临被卡线,应该Sam是我联赛前学的最后一个省选知识点吧,以后的重心还是要放到联赛上,做一做杂题3还有线段树进阶以及期望Dp,尽量联赛和高手之间的分差小一点吧 A. 序列 标签: BIT 题解: 先咕咕咕 原文地址:https://www.cnblogs.com/AthosD/p/11764123.html

2019.10.30 运飞龙 计算机专业英语

启动流程:(1)计算机加电(2)Blos开始运行检测硬件CPU内存硬盘等(3)Blos读取cmps存储器中的参数:选择启动设备(4)从启动设备上读取等一个扇区的内容cmbs主引导记录512字节前446为引导信息,后64为分区信息最后两个为为标志位(5)根据分区信息读入bootcoader启动装载模块启动操作系统(6)然后操作系统询问BIOS以获得配置信息对于每种设备系统会检查其设备驱动程序是否存在,如果没有系统则会要求用户按照设备驱动程序一旦有了全部的设备驱动程序操作系统就将他们调入内核然后初始