【题解】网格 & Single Cut of Failure(trick:答案上界)

1 - 网格

Statement

给定一个 (n imes m) 的网格,有 (c) 个格子被删掉了。

  • 称两个格子是相邻的当且仅当这两个格子有公共边。

  • 称两个格子是连通的当且仅当这两个格子相邻或者存在另一个格子与这两个格子都连通。

现在希望将若干个格子删掉使得存在至少两个格子不连通。

判断能否达成,如果能,那么最小化被删除的个数。

Solution

性质

发现一个性质:(ansleq 2) .

Proof

如果存在一个格子在四个角上,那么显然成立。

否则,如果存在格子在四条边上(不在端点),度为 (3) ,那么一定存在一个这样的格子两边有至少一个格子被删除。

如果都不满足,那么就缩小一圈的范围,同上即可。

思路来了:重要的套路/trick: 看答案上界

无解

对于 (-1) ,有两种情况:

  • 格子在最开始就少于两个( (n imes m-c=1)
  • 最开始有两个格子并且相邻(这个可以在最后算完之后判断,如果 (ans eq 0)(n imes m-c=2)

ans=0

Public :将所有有公共边的格子相连,构成一张图。

图不连通即 (ans=0) .(这部分会在下面讲怎么判)

ans=1(总体做法)

图有割点,把割点换成 (1) 即可。跑一遍 Tarjan。

但是图非常大,(Nleq 1e5,Mleq 2e5) ,这样一个网格图建图显然不现实。

考虑如果有大量空行(没被删掉)显然是无用的,其中很大一部分不可能产生割边;

发现对于一个删掉的格子,只有以它为中心,(5 imes 5) 的格子是需要考虑的。记直接相邻的格子为一级连通,其余是二级连通。(其实割点只能产生在一级连通上,但是为了保证所有不是割点的点能够判断出来,建图的时候二级连通也要加)

然后这样把所有有效的部分 BFS 出来,建图跑 Tarjan 求割点。

至于建图的话,直接拿 <pair<int,int>,int> 的 map+数组离散化(重标号)就能方便地得到所有有效点,然后再遍历一遍连边即可。

注意连边的时候不要连重边,可以对于一个点,向右向下连边,这样就可以避免重复。

其实具体实现写了一堆 STL,看代码

如果 Tarjan 跑完之后,时间戳 DFN 的计数 (cnt<tot) (点数),说明图本来就不连通,(ans=0) .

如果存在割点 且是一级连通(ans=1) .

ans=2

Otherwise,直接当初始值。

Code

看上去 UOJ 的第 16 个Hack数据卡掉了 map 并且卡了部分 Hash…… 那就不做了罢(

//Author: RingweEH
#define PII pair<int,int>
const int N=5000010;
int n,m,c,cnt,tot,ans,be[N];
int dfn[N],low[N],cut[N];
set<PII> s;
map<PII,int> vis,id,A,B;
queue<PII> q;
vector<int> g[N];
PII a[N],b[N];

void add( int u,int v )     //doubled?
{
    g[u].push_back( v ); g[v].push_back( u );
}

void Tarjan( int u )            //Tarjan求无向图割点
{
    dfn[u]=low[u]=++cnt;
    for ( int v : g[u] ) 
        if ( !dfn[v] )
        {
            Tarjan( v ); low[u]=min( low[u],low[v] );
            if ( dfn[u]==low[v] ) cut[u]++;
        }
        else low[u]=min( low[u],dfn[v] );
    if ( ((u!=1 && cut[u]) || cut[u]>1) && be[u] ) ans=min( ans,1 );    //判根和是否是一级联通
}

void BFS( PII now )
{
    memset( be+1,0,4*tot ); memset( dfn+1,0,4*tot ); memset( cut+1,0,4*tot ); id.clear();
    for ( int i=1; i<=tot; i++ )
        g[i].clear();
    cnt=tot=0; q.push( now ); vis[now]=1;
    while ( !q.empty() )            //只有5*5矩阵内的格子有效
    {
        PII u=q.front(); q.pop();
        for ( int i=-2; i<=2; i++ )
            for ( int j=-2; j<=2; j++ )
            {
                PII v; v.first=u.first+i,v.second=u.second+j;
                if ( v.first<1 || v.first>n || v.second<1 || v.second>m ) continue;
                if ( !s.count(v) )      //还没被删掉的点
                {
                    if ( !id[v] ) id[v]=++tot,b[tot]=v;     //离散化所有有效点
                    if ( max( abs(i),abs(j) )<2 ) be[id[v]]=1;      //一级连通才有机会成为割点
                }
                else if ( !vis[v] ) q.push( v ),vis[v]=1;       //连通块内已经被删掉的点入队
            }
    }
    for ( int i=1; i<=tot; i++ )        //对于选出来的点建图,只需要两个方向因为另外两个会由另一个来连
    {
        PII u; u.first=b[i].first+1,u.second=b[i].second;
        if ( id[u] ) add( i,id[u] );
        PII v; v.first=b[i].first,v.second=b[i].second+1;
        if ( id[v] ) add( i,id[v] );
    }
    Tarjan( 1 );
    if ( cnt !=tot ) ans=min( ans,0 );
}

int main()
{
    int T=read();
    while ( T-- )
    {
        n=read(); m=read(); c=read();
        ans=2; s.clear(); vis.clear();
        for ( int i=1; i<=c; i++ )
            a[i].first=read(),a[i].second=read(),s.insert( a[i] );
        
        if ( 1ll*n*m-c<2 ) { printf( "-1
" ); continue; }      //只有一个
        for ( int i=1; i<=c; i++ )      //遍历所有删除点组成的连通块
            if ( !vis[a[i]] ) BFS( a[i] );
        if ( min( n,m)==1 ) ans=min( ans,1 );
        if ( 1ll*n*m-c==2 && ans ) ans=-1;      //有两个并且连通

        printf( "%d
",ans );
    }

    return 0;
}

2 - Single Cut of Failure

Solution

这道比较简单。

十月份的时候呵姥爷讲的题目,和网格的思路也是类似的,就是看答案上界。

连接矩形对角线,显然 (2) 次是最大的答案,那么只需要判断答案能否为 (1) 即可。

(1) 的条件:存在一条线段经过所有线。

将外轮廓拉成一条直线,每一条线都对应于这条直线上的一条线段,那么只需要判断是否存在一个区间,使得覆盖了每条线段的两个端点之一恰好一次。

双指针+桶即可

Code

//Author:RingweEH
const int N=5e6+10;
int n,W,H,col[N],num[N],p1[N],p2[N],b[N],cnt,siz;
bool vis[N];

int trans( int x,int y )
{
        if ( !x ) return y;
        if ( y==H ) return H+x;
        if ( x==W ) return 2*H+W-y;
        if ( !y ) return 2*H+2*W-x;
        return 0;
}

void write( int x )
{
        if ( x<=H ) printf( "%.1lf %.1lf",0.0,x*0.5 );
        else if ( x<=H+W ) printf( "%.1lf %.1lf",(x-H)*0.5,H*0.5 );
        else if ( x<=2*H+W ) printf( "%.1lf %.1lf",W*0.5,(2*H+W-x)*0.5 );
        else printf( "%.1lf %.1lf",(2*W+2*H-x)*0.5,0.0 );
}

int main()
{
	n=read(); W=read()*2; H=read()*2;
    for ( int i=1; i<=n; i++ )
    {
        int x=read()*2,y=read()*2;
        b[++cnt]=p1[i]=trans(x,y); b[++cnt]=p1[i]+1;
        x=read()*2; y=read()*2;
        b[++cnt]=p2[i]=trans(x,y); b[++cnt]=p2[i]+1;
    }

    sort( b+1,b+cnt+1 ); cnt=unique( b+1,b+1+cnt )-b-1;
    for ( int i=1; i<=n; i++ )
    {
        int tmp=lower_bound( b+1,b+1+cnt,p1[i] )-b;
        col[tmp]=i; num[tmp]=p1[i];
        tmp=lower_bound( b+1,b+1+cnt,p2[i] )-b;
        col[tmp]=i; num[tmp]=p2[i];
    }
    
    int p=2;
    for ( int i=2; i<=cnt; i+=2 )
    {
        while ( p<cnt && !vis[col[p+1]] ) vis[col[p+1]]=1,p+=2,siz++;
        if ( siz==n )
        {
            printf( "1
" ); write( num[i-1]+1 );
        	printf( " " ); write( num[p-1]+1 ); printf( "
" ); return 0; 
        }
        vis[col[i+1]]=0; siz--;
    }
    W/=2; H/=2;
	printf( "2
%.1lf %d %.1lf %d
%.1lf %d %.1lf %d
",0.5,0,W-0.5,H,0.5,H,W-0.5,0 );
}
原文地址:https://www.cnblogs.com/UntitledCpp/p/trick_1.html