【Codeforces】【网络流】【线段树】【扫描线】Oleg and chess (CodeForces - 793G)

题意:

给定一个n*n的矩阵,一个格子上可以放一个车。其中有q个子矩阵,且q个子矩阵互不相交或者是重叠(但边界可以衔接)。这q个子矩阵所覆盖的地方都是不能够放车的。车可以越过子矩阵覆盖的地方进行攻击(车可以横着或者是竖着直走,就像象棋中的那样)。现在问的是一共最多能够放多少个不能够互相攻击的车。

数据范围

1<=n<=10000,0<=q<=10000

输入格式

首先输入两个数字n,q,分别表示矩阵的大小和子矩阵的个数。
接下来有q行,每行四个正整数x1,y1,x2,y2,分别表示每一个子矩阵的左上角的坐标和右下角的坐标。

输出格式

直接输出最多能够放多少个车即可。

思路

如果不是放在网络流的题里面,我绝对会去想贪心

我觉得已经比较详细了

听完题解之后觉得非常的恶心。
首先,如果按照最普通的方式建图的话,就是一个套路性的东西:对于每一行、每一列分别建一个点,然后再让代表第i行的点指向代表第j行的点,表示(i,j)这个地方可以放一个车。当然,如果是被子矩阵覆盖了的话,肯定就不能够相边。最后跑一边最大流就得到了答案。

但是经过一番推敲后我们可以发现,一共有O(n)个点,然后最坏有O(n*n)条边,对于网络流而言是要T的。下面我们考虑优化。

首先让我们把题目中给出的子矩形称作黑色矩形
其实我们需要优化的就是建边的过程,也就是行向列进行连边的问题。我们可以考虑将剩下的空白的部分转化为一个个的小矩形(把这些矩形称作白色矩形),然后利用线段树进行统一的连边。
这样说是很抽象的,下面我们具体探讨一下如何利用线段树优化建图。

假设当前需要加入的白色矩形为(x1,y1,x2,y2),那么在行坐标上就对应于(x1,x2)这个线段,列坐标上就对对应于(y1,y2)这个线段。然后我们在最开始建立两棵线段树,一棵表示行坐标,一棵表示列坐标,然后将(x1,x2)拆解成log(n)个子线段,将(y1,y2)拆解为log(n)个子线段,然后将这两坨子线段互相连边,那么就有log^2(n)条边。这样子就解决了建边的问题了(舒服)。

接下来,我们就只剩下了一个子问题了,那就是如何去找白色矩形(对空白部分进行划分)。首先需要满足两个前提:

1.能够成功划分完所有的空白的区域
2.划分的白色矩阵尽量少

这道题目前的优秀解法是这样的(橙色的是黑色矩阵):
在这里插入图片描述
可以看出,对于每一个黑色矩形,它的上下两条边都向两边进行了发散,直到碰到了其它的边或者是边界才停下来,最终就会形成类似于上面所示的一个图。
然后红色的线就自然的将空白的区域划分为了若干个白色矩形。最后经实践证明,一共有O(n)级别的白色矩形。

如何来构造这样一个方案呢?
考虑这样一个算法:

扫描每一个黑色矩形,将每一个子矩阵分成两条线段,一条是位于第y1列的(x1,x2,0),第y2列的(x1,x2,1)(后面的值表示类型),然后根据列坐标进行排序。然后按照列坐标从小到大的顺序枚举这些线段,对于列左边相同的,先枚举类型0的,再枚举类型1的。
首先让我们考虑类型0的:
假设当前的线段是(x1,x2),在第j列。我们需要在全局维护一个a[],其中a[i]表示从当前枚举到的第i行往前走,最远的能够到达的未被黑色矩形覆盖的空白格子的列坐标。
然后从x1到x2进行枚举,并记录一个当前的白色矩形的左边界last,以及上一次左边界发生变化的位置(行),lastpos。假设枚举到了i,那么假如a[i]==last,那么就继续枚举;否则就将(lastpos,last,i-1,j-1)插入。
下面是给出的一个例子:
在这里插入图片描述
当扫描到i+2时,我们发现a[i]发生了变化,而此时的last=a[i],lastpos=i,然后我们在这里发现了一个新的矩形,也就是(lastpos,last,i+1,j-1),插入即可。
这样做是十分暴力的,复杂度高达O(n^2),但是可以过

接下来考虑类型1的:
假设当前的线段为(x1,x2),其实这条线段的作用就是用来更新a[]数组的。也就是将a[x1~x2]修改为当前的列坐标即可。记住列坐标相等的类型1线段与类型0线段,类型1一定是在类型0之后做

需要注意的是,在最开始输入完黑色矩阵后,我们还需要加入一个(1,n+1,n+1,n)的一条线段,来保证最后右边的空白区域有白色矩形来填充。

最后网络流的图大概是这样的:

在这里插入图片描述
左边为一棵线段树,右边为一棵线段树,叶子分别连向源、汇点,然后中间黄色的边是插入白色矩形的时候连的边。
具体细节就参考代码吧。

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define MAXN 10000
#define MAXQ 10000
#define INF 0x3FFFFFFF
using namespace std;
struct Line
{
    int p,l,r,typ;
    Line(){};
    Line(int _p,int _l,int _r,int _typ):p(_p),l(_l),r(_r),typ(_typ){};
};
bool operator < (const Line &a,const Line &b)
{
    if(a.p!=b.p)    return a.p<b.p;
    return a.typ>b.typ;
}
struct edge
{
    int to,cap;
    edge *nxt,*bck;
}edges[MAXN*500];
edge *ncnt=&edges[0],*Adj[MAXN*10+5];
struct node
{
    int ch[2];
}tree[MAXN*10];
int tcnt=0,rt[2];
vector<Line> Lineseq;
vector<int> seq[2];
int n,q,S,T;
int a[MAXN+5];
int d[MAXN*10],vd[MAXN*10];
void AddEdge(int u,int v,int cap)
{
    edge *p=++ncnt;
    p->to=v,p->cap=cap;
    p->nxt=Adj[u],Adj[u]=p;
    
    edge *q=++ncnt;
    q->to=u,q->cap=0;
    q->nxt=Adj[v],Adj[v]=q;
    
    p->bck=q,q->bck=p;
}
void Build(int &p,int l,int r)
{
    p=++tcnt;
    if(l==r)
        return;
    int mid=(l+r)/2;
    Build(tree[p].ch[0],l,mid);
    Build(tree[p].ch[1],mid+1,r);
}
void Bianli(int p,int l,int r,int typ)
{
    if(l==r)
    {
        if(typ==0)  AddEdge(S,p,1);//参考之前网络流的图
        else        AddEdge(p,T,1);
        return;
    }
    int mid=(l+r)/2;
    //最后网络流的图的形状十分特殊
    if(typ==0)
        AddEdge(tree[p].ch[0],p,INF),AddEdge(tree[p].ch[1],p,INF);
    if(typ==1)
        AddEdge(p,tree[p].ch[0],INF),AddEdge(p,tree[p].ch[1],INF);
    Bianli(tree[p].ch[0],l,mid,typ);
    Bianli(tree[p].ch[1],mid+1,r,typ);
}
void Query(int p,int ql,int qr,int l,int r,int typ)
{
    if(qr<l||ql>r)
        return;
    if(ql<=l&&r<=qr)
    {
        seq[typ].push_back(p);
        return;
    }
    int mid=(l+r)/2;
    Query(tree[p].ch[0],ql,qr,l,mid,typ);
    Query(tree[p].ch[1],ql,qr,mid+1,r,typ);
}
void Insert_Rectangle(int x1,int y1,int x2,int y2)
{
    seq[0].clear(),seq[1].clear();
    Query(rt[0],x1,x2,1,n,0);Query(rt[1],y1,y2,1,n,1);
    for(int i=0;i<(int)seq[0].size();i++)//先把对应的子线段找出来,然后两两连边
        for(int j=0;j<(int)seq[1].size();j++)
            AddEdge(seq[0][i],seq[1][j],INF);
}
void Scan()
{
    Line cur;
    fill(a,a+1+n,1);
    for(int i=0;i<(int)Lineseq.size();i++)
    {
        cur=Lineseq[i];
        int las=a[cur.l],lasp=cur.l;//last,lastpos的初值(可以画个图理解一下)
        for(int p=cur.l;p<=cur.r;p++)
            if(cur.typ==0)
            {
                if(las!=a[p])
                {
                    if(cur.p!=las)//插入前记得判断这个矩阵是否存在(而不是一根线)
                        Insert_Rectangle(lasp,las,p-1,cur.p-1);//插入白色矩阵
                    las=a[p],lasp=p;
                }
                a[p]=cur.p+1;
            }
            else
                a[p]=cur.p;
        if(cur.typ==0&&cur.p!=las)//同样需要进行判断
            Insert_Rectangle(lasp,las,cur.r,cur.p-1);
    }
}
int aug(int u,int tot)
{
    if(u==T)
        return tot;
    int v,delta,sum=0,mind=T+1;
    if(d[u]<mind)
        mind=d[u];
    for(edge *p=Adj[u];p!=NULL;p=p->nxt)
    {
        v=p->to;
        if(p->cap>0)
        {
            if(d[u]==d[v]+1)
            {
                delta=min(tot-sum,p->cap);
                delta=aug(v,delta);
                sum+=delta;
                p->cap-=delta,p->bck->cap+=delta;
                if(d[S]>=T+1)
                    return sum;
                if(sum==tot)
                    break;
            }
        }
    }
    if(sum==0)
    {
        vd[d[u]]--;
        if(vd[d[u]]==0)
            d[S]=T+1;
        d[u]=mind+1;
        vd[d[u]]++;
    }
    return sum;
}
int Isap()
{
    int flow=0;
    memset(d,0,sizeof(d));
    memset(vd,0,sizeof(vd));
    vd[0]=T+1;
    while(d[S]<T+1)
        flow+=aug(S,INF);
    return flow;
}
int main()
{
    scanf("%d %d",&n,&q);
    int x1,y1,x2,y2;
    for(int i=1;i<=q;i++)
    {
        scanf("%d %d %d %d",&x1,&y1,&x2,&y2);
        Lineseq.push_back(Line(y1,x1,x2,0));
        Lineseq.push_back(Line(y2+1,x1,x2,1));
    }
    Lineseq.push_back(Line(n+1,1,n,0));//插入特殊矩阵
    Lineseq.push_back(Line(n+2,1,n,1));
    sort(Lineseq.begin(),Lineseq.end());
    Build(rt[0],1,n);Build(rt[1],1,n);//线段树初始化
    S=0,T=tcnt+1;
    Bianli(rt[0],1,n,0);Bianli(rt[1],1,n,1);//"遍历"来进行线段树之间的连边
    Scan();//扫描线
    //预处理完毕
    int ans=Isap();
    printf("%d\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/T-Y-P-E/p/10176648.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值