[BZOJ 1804] Flood

Link:

BZOJ 1804 传送门

Solution:

不容易啊,第一道完全自己A掉的IOI题目.....

 

算法思想其实很简单:

模拟缩减的过程即可

将每条边转为2条有向边,每次找到最左边的边,沿着最外圈走一周,并将走过的边打上$vis$标记

最后那些正向和反向都被走过的边就是能留下的边(最后形成链的边)

 

难点在于如何实现在最外层走一周:

由于这是一个平面图,我们对每个点只要记录上下左右四个方向的边即可

保证是顺时针转,因此尽量往左边走就能满足最外圈这个条件

(注意对$vis$数组更新的位置,否则可能在遇到链时死循环)

 

其实还有一种不带$log$的做法:

平面图转化为对偶图,$bfs$每条边到边界的距离

如果两边的距离相等,这条边就计入答案(难点在建图)

待填坑

 

Code:

#include <bits/stdc++.h>
 
using namespace std;
typedef pair<int,int> P;
#define X first
#define Y second
const int MAXN=4e5+10;
 
P nd[MAXN],tp[MAXN];
int n,m,dir[MAXN][4],cnt[MAXN][4],vis[MAXN][4],res=0;
int tot=0,tdir[MAXN];
struct edge{int x,y,dir;} e[2*MAXN];
bool cmp(edge a,edge b) //对边的排序
{
    if(nd[a.x].X==nd[b.x].X)
        if((a.dir==0 || a.dir==2) && (b.dir==1 || b.dir==3)) return true;
        else if((a.dir==1 || a.dir==3) && (b.dir==0 || b.dir==2)) return false;
    return nd[a.x].X<nd[b.x].X;
}
 
void add_edge(int a,int b,int id)
{
    if(nd[a].X==nd[b].X)
        if(nd[a].Y<nd[b].Y) dir[a][0]=b,dir[b][2]=a,e[id].dir=0;
        else dir[a][2]=b,dir[b][0]=a,e[id].dir=2;
    else
        if(nd[a].X<nd[b].X) dir[a][1]=b,dir[b][3]=a,e[id].dir=1;
        else dir[a][3]=b,dir[b][1]=a,e[id].dir=3;;
}
 
int Back(int x){return (x+2)%4;}
int Left(int x){return (x+3)%4;}
int Right(int x){return (x+1)%4;}
void Travel(int id)
{
    int cur=e[id].y,d=e[id].dir,pre=e[id].x;cnt[pre][d]++;
    tot=1;tp[tot]=P(pre,cur);tdir[tot]=d;
    while(cur!=e[id].x)
    {        
        d=Left(d); //尽量往左走
        while(!dir[cur][d] || vis[cur][d]) d=Right(d);
        pre=cur;cur=dir[cur][d];cnt[pre][d]++;
        tp[++tot]=P(pre,cur);tdir[tot]=d;
    }
    for(int i=1;i<=tot;i++) //一定要最后一起打vis标记,否则可能死循环
        vis[tp[i].X][tdir[i]]=vis[tp[i].Y][Back(tdir[i])]=true;
}
 
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d%d",&nd[i].X,&nd[i].Y);
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&e[i].x,&e[i].y);
        if(nd[e[i].x].X>nd[e[i].y].X) swap(e[i].x,e[i].y);
        if(nd[e[i].x].Y>nd[e[i].y].Y) swap(e[i].x,e[i].y);
        add_edge(e[i].x,e[i].y,i);
    }
    
    sort(e+1,e+m+1,cmp);
    for(int i=1;i<=m;i++)
    {
        if(vis[e[i].x][e[i].dir]) continue; //已走过的边
        Travel(i);
    }
    for(int i=1;i<=m;i++)
        if(cnt[e[i].x][e[i].dir] && cnt[e[i].y][Back(e[i].dir)]) res++;
    printf("%d",res);
    return 0;
}

 

Review:

遇到平面图时(保证边两两不相交),一定要考虑其对偶图

 

posted @ 2018-06-05 20:51  NewErA  阅读(282)  评论(0编辑  收藏  举报