【日常摸鱼】牛客挑战赛2

【日常摸鱼】牛客挑战赛2

前言

略了。。不多bb

A Cut

等同于合并果子,水题略了

B Travel

裸最短路,水题略了

C Butterfly

链接

https://ac.nowcoder.com/acm/contest/17/C

题意

给定一个n*m的矩阵,矩阵元素由X和O构成,请求出其中最大的由X构成的蝴蝶形状。
由X构成的蝴蝶形状的定义如下:
存在一个中心点,并且其往左上、左下、右上、右下四个方向扩展相同的长度(扩展的长度上都是X),且左上顶点与左下顶点、右上顶点与右下顶点之间的格子全由X填充。我们不在意在蝴蝶形状内部是X还是O。
例如:
XOOOX
XXOXX
XOXOX
XXOXX
XOOOX
是一个X构成的蝴蝶形状。
X也是。

XOOX
OXXO
OXXO
XOXX
不是(不存在中心点)。
\(n,m\leq 2000\)

题解

因为蝴蝶形中间可以有空的,不好二分。。我们先想办法转化成能二分的东西。。
一个蝴蝶形可以看做两个(一竖+一斜杠),我们先算出从一个点(i,j)最大的能向左或向右的(竖+斜)。
这个是连续的,因为能有长度为x的(竖+斜),就能有长度为x-1的。
然后一个蝴蝶形就是两个同一行的(竖+斜)的组合。
设R(i,j)和L(i,j)分别表示(i,j)可以向左或向右延伸的最大的(竖+斜)。
然后假设我们能用(i,j)和(i,k)组合出一个蝴蝶形。
于是有:
\(R(i,j)\geq k-j+1\)
\(L(i,k)\geq k-j+1\)
移项得:
\(R(i,j)+j-1 \geq k \geq j\)
\(j \geq k+1-L(i,k)\)
对于同一行,按照这两个不等式,就能套用各种数据结构了。。我用的是set。。

\(Code\)

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const LL INF=1e18;
const int N=2e5+100;
int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void print(LL x){
    if(x>9) print(x/10);
    putchar(x%10+'0');
}
void get_min(LL &x,LL y){
    if(x>y) x=y;
}
int n,m; 
char s[2010];
int mp[2010][2010];
int d[2010][2010],dr[2010][2010],dl[2010][2010];
int L[2010][2010],R[2010][2010];
set<int> S[2];
set<int>::iterator it;
int id[2010],val[2010];
bool cmp(int x,int y){
    return val[x]<val[y];
}
int main(){
    int x,ans=0;
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i){
        scanf("%s",s+1);
        for(int j=1;j<=m;++j){
            if(s[j]=='X') mp[i][j]=1;
            else mp[i][j]=0;
        }
    }
    for(int i=n;i>=1;--i){
        for(int j=1;j<=m;++j){
            if(!mp[i][j]) d[i][j]=dr[i][j]=dl[i][j]=0;
            else{
                d[i][j]=dr[i][j]=dl[i][j]=1;
                if(i<n) d[i][j]+=d[i+1][j];
                if(i<n&&j<m) dr[i][j]+=dr[i+1][j+1];
                if(i<n&&j>1) dl[i][j]+=dl[i+1][j-1];
            }
        }
    }
    for(int i=n;i>=1;--i){
        for(int j=1;j<=m;++j){
            L[i][j]=min(d[i][j],dr[i][j]);
            R[i][j]=min(d[i][j],dl[i][j]);
        }
    }
    for(int i=1;i<=m;++i) id[i]=i;
    for(int i=1;i<=n;++i){
        S[0].clear();S[1].clear();
        int k;
        for(k=1;k<=m;++k){
            val[k]=k+1-R[i][k];
        }
        sort(id+1,id+1+m,cmp);
        S[0].insert(0);S[1].insert(0);k=1;
        for(int j=1;j<=m;++j){
            while(k<=m&&val[id[k]]<=j){
                S[(id[k]&1)].insert(id[k]);
                ++k;
            }
            it=S[(j&1)].upper_bound(L[i][j]+j-1);
            --it;
            x=*it;
            if(x>=j&&x<=L[i][j]+j-1){
                ans=max(ans,x-j+1);
            }
        }
    }
    printf("%d\n",ans);
    return 0;
}

D Delete

链接

https://ac.nowcoder.com/acm/contest/17/D

题意

给定一张n个点,m条边的带权有向无环图,同时给定起点S和终点T,一共有q个询问,每次询问删掉某个点和所有与它相连的边之后S到T的最短路,询问之间互相独立(即删除操作在询问结束之后会立即撤销),如果删了那个点后不存在S到T的最短路,则输出-1。
\(n,q\leq 100000,m\leq 200000\)

题解

看到是DAG上求距离,先拓扑排序一波。
排好之后,ST间的每条路径都相当于在拓扑序上按顺序选几个点连起来。
我们先预处理S到所有点的距离和所有点到T的距离。
如果删的是拓扑序在S前或在T后的,直接输出ST的最短距离。
对于其他点,考虑一条路径对答案的影响,相当于对这条路径没经过的所有点取个min。
这样的话我们枚举每一条边(x,y),对拓扑序在xy之间的所有点用dis(S,x)+len(x,y)+dis(y,T)来取min。
这样的话问题就是区间min和单点查询了,可以用线段树。

\(Code\)

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const LL INF=1e18;
const int N=2e5+100;
int read(){
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
void print(LL x){
    if(x>9) print(x/10);
    putchar(x%10+'0');
}
int n,m,S,T;
struct edge{int x,y;LL z;}e[N];
vector<int> L[N],R[N];
int du[N];
int q[N];
int num[N];
LL ds[N],dt[N];
LL mn[N<<1];
void upd(int l,int r,int id,LL w,int lq,int rq){
    if(lq==l&&rq==r){
        mn[id]=min(mn[id],w);
        return;
    }
    int mid=(l+r)>>1;
    if(lq>mid) upd(mid+1,r,id<<1|1,w,lq,rq);
    else if(rq<=mid) upd(l,mid,id<<1,w,lq,rq);
    else{
        upd(l,mid,id<<1,w,lq,mid);
        upd(mid+1,r,id<<1|1,w,mid+1,rq);
    }
    return;
}
LL ask(int l,int r,int id,int x){
    if(l==r) return mn[id];
    int mid=(l+r)>>1;
    if(x<=mid) return min(ask(l,mid,id<<1,x),mn[id]);
    else return min(ask(mid+1,r,id<<1|1,x),mn[id]);
}
int main(){
    n=read();m=read();S=read();T=read();
    for(int i=1;i<=m;++i){
        e[i].x=read();e[i].y=read();e[i].z=read();
        ++du[e[i].y];
        R[e[i].x].push_back(i);
        L[e[i].y].push_back(i);
    }
    int l=1,r=0,x,y;edge ed;LL ans;
    for(int i=1;i<=n;++i) if(!du[i]) q[++r]=i;
    while(l<=r){
        x=q[l++];
        for(int i=0;i<R[x].size();++i){
            y=e[R[x][i]].y;
            --du[y];
            if(!du[y]) q[++r]=y;
        }
    }
    for(int i=1;i<=n;++i){
        num[q[i]]=i;
    }
    for(int i=1;i<=n;++i){
        ds[i]=dt[i]=INF;
    }
    ds[S]=0;
    for(int i=1;i<=r;++i){
        for(int j=0;j<R[q[i]].size();++j){
            ed=e[R[q[i]][j]];
            ds[ed.y]=min(ds[ed.y],ds[ed.x]+ed.z);
        }
    }
    dt[T]=0;
    for(int i=r;i>=1;--i){
        for(int j=0;j<L[q[i]].size();++j){
            ed=e[L[q[i]][j]];
            dt[ed.x]=min(dt[ed.x],dt[ed.y]+ed.z);
        }
    }
    for(int i=1;i<(N<<1);++i) mn[i]=INF;
    
    for(int i=1;i<=m;++i){
        ans=ds[e[i].x]+e[i].z+dt[e[i].y];
        if(ans<INF&&num[e[i].x]+1<=num[e[i].y]-1) 
            upd(1,n,1,ans,num[e[i].x]+1,num[e[i].y]-1);
    }
    int Q=read();
    while(Q--){
        x=read();
        if(num[x]<num[S]||num[x]>num[T]){
            if(ds[T]==INF)puts("-1");
            else printf("%lld\n",ds[T]);
            continue;
        }
        ans=ask(1,n,1,num[x]);
        if(ans==INF) puts("-1");
        else printf("%lld\n",ans);
    }
    return 0;
}

E Mod

这个窝不会。。。
题解大概是类似辗转相减的方法。。
细节很多很不好写。。我就放弃了

posted @ 2021-02-05 12:03  Iscream-2001  阅读(38)  评论(0编辑  收藏  举报
/* */