@codeforces - [email protected] Oleg and chess

目录

  • @description - [email protected]
  • @[email protected]
    • @part - [email protected]
    • @part - [email protected]
    • @part - [email protected]
    • @part - [email protected]
  • @accepted [email protected]
  • @[email protected]

@description - [email protected]

给定一个 n*n 的棋盘,并划定一些不能放棋子的矩形区域。

现在要在棋盘上放最多的车(读作 ju),使得这些车两两之间不会攻击。

input

第一行整数 n ——棋盘边长(1 <= n <= 10000)。

第二行整数 q ——划定的矩形个数(0 <= q <= 10000)。

接下来 q 行,每一行都是 x1, y1, x2, y2(1 <= x1 <= x2 <= n, 1 <= y1 <= y2 <= n),描述矩阵的左下角与右上角。

保证矩形两两不会相交

output

输出最多的车的个数。

sample input

5

5

1 1 2 1

1 3 1 5

4 1 5 5

2 5 2 5

3 2 3 5

sample output

3

sapmle explain

如图。

@[email protected]

一道网络流题。

一道建模极其简单,建图极其恶心的网络流题。

@part - [email protected]

考虑建模。棋盘是一个很经典的二分图,可以是黑白染色建模,也可以是行列建模。考虑到车的攻击方式是同行同列攻击,所以我们选择后者。

假如某一个格子(i, j)没有被划定不能放车,我们就第 i 行与第 j 列连边。再跑一个最大匹配就可以求出最多放置多少车了。

然而显然是会 TLE 的,而且还会 T 的很惨,惨兮兮。

@part - [email protected]

优化建图的话,因为划定的是规则的矩形,所以我们考虑用线段树来优化建图。

如图是一个内部完全没有限制的矩形,我们用行、列两棵线段树将它的两个横竖的边界拆成log n条线段树上的线段:

然后横着的和竖着的两两连边,连 log^2 n 条边【图片略鬼畜】:

这样就处理完了一个没有限制的矩形。

最后:两棵线段树的底层端点,一棵连 S,一棵连 T,容量都为 1。线段树内部的父子连容量为 inf 的边。

@part - [email protected]

然而问题又来了:我们给定的是限制的矩形区域。

所以,我们必须把原棋盘切割成若干个内部没有限制的矩形,才能运用上面所提到的优化。

怎么切?下面是一个比较显然的思路:

即对于每一个矩形,它的上下左右边界往两边割。

然而,如果下面这个图……

直接卡成 O(n^2)。

我们发现上面的那种切割方法,有很多小矩形是可以合并成大矩形。所以我们优化一下切割方法:

即上下边界往两边切,遇到其他矩形的边界或棋盘的边界,则停下来。

这样切,可以证明最多只会分出 4*n 个矩形。

怎么证明呢?【感性理解】每一个矩形的上下边界向左右各引一条线,一共 4 条线,每条线可以把一个矩形切割成两个矩形,相当于多增加了 4 个矩形。所以最多 4n 个矩形。

@part - [email protected]

OK 现在来看看怎么实现切割。

我们用扫描线算法,从左往右扫描。对于每一行,维护扫描线左边距离扫描线最近的矩形边界。如图,我们维护的就是左边的那弯弯曲曲的曲线:

假如遇到矩形左边界,我们就从这个矩形的上边界开始往下暴力遍历(对你没听错就是暴力遍历,这样的确是 O(n^2) 的,但是其实 n 不大,对吧)。假如遇到不平坦的地方(对应到代码中就是相邻两行维护的东西不相等),则说明又产生了新的矩形。我们就进行线段树建图。

假如遇到矩形右边界,更新 “扫描线左边距离扫描线最近的矩形边界”。

注意,这个算法是基于矩阵不相交的前提的。

@accepted [email protected]

口胡完毕。至于代码量,我不清楚我不知道,大家自己慢慢调,总会调出来的 qwq。

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int MAXN = 10000;
const int MAXM = 100000;
const int MAXK = 2000000;
const int INF = (1<<30);
struct FlowGraph{
    struct edge{
        int to, cap, flow;
        edge *nxt, *rev;
    }edges[2*MAXK + 5], *adj[MAXM + 5], *ecnt=&edges[0];
    int S, T, d[MAXM + 5], vd[MAXM + 5];
    void addedge(int u, int v, int c) {
        edge *p = (++ecnt);
        p->to = v, p->cap = c, p->flow = 0;
        p->nxt = adj[u], adj[u] = p;
        edge *q = (++ecnt);
        q->to = u, q->cap = 0, q->flow = 0;
        q->nxt = adj[v], adj[v] = q;
        p->rev = q, q->rev = p;
    }
    int aug(int x, int tot) {
        if( x == T ) return tot;
        int mind = T+1, sum = 0;
        for(edge *p=adj[x];p!=NULL;p=p->nxt) {
            if( p->cap > p->flow ) {
                if( d[p->to] + 1 == d[x] ) {
                    int del = aug(p->to, min(tot-sum, p->cap-p->flow));
                    p->flow += del, p->rev->flow -= del, sum += del;
                    if( d[S] == T+1 ) return sum;
                    if( sum == tot ) return sum;
                }
                mind = min(mind, d[p->to]);
            }
        }
        if( sum == 0 ) {
            vd[d[x]]--;
            if( vd[d[x]] == 0 )
                d[S] = T+1;
            d[x] = mind + 1;
            vd[d[x]]++;
        }
        return sum;
    }
    int max_flow() {
        int flow = 0;
        while( d[S] < T+1 )
            flow += aug(S, INF);
        return flow;
    }
}G;
int cnt = 0;
struct SegmentTree{
    int le, ri, num;
}t[2][4*MAXN + 5];
vector<int>v[2];
void build_segtree(int x, int l, int r, int n) {
    t[n][x].le = l, t[n][x].ri = r, t[n][x].num = (++cnt);
    if( l == r ) return ;
    int mid = (l + r) >> 1;
    build_segtree(x<<1, l, mid, n);
    build_segtree(x<<1|1, mid+1, r, n);
}
void build_edge_segtree(int x, int n) {
    if( t[n][x].le == t[n][x].ri ) {
        if( n == 0 ) G.addedge(G.S, t[n][x].num, 1);
        else G.addedge(t[n][x].num, G.T, 1);
    }
    else {
        if( n == 0 ) {
            G.addedge(t[n][x<<1].num, t[n][x].num, INF);
            G.addedge(t[n][x<<1|1].num, t[n][x].num, INF);
        }
        else {
            G.addedge(t[n][x].num, t[n][x<<1].num, INF);
            G.addedge(t[n][x].num, t[n][x<<1|1].num, INF);
        }
        build_edge_segtree(x<<1, n);
        build_edge_segtree(x<<1|1, n);
    }
}
void get_segment(int x, int l, int r, int n) {
    if( l <= t[n][x].le && t[n][x].ri <= r ) {
        v[n].push_back(t[n][x].num);
        return ;
    }
    if( l > t[n][x].ri || r < t[n][x].le )
        return ;
    get_segment(x<<1, l, r, n);
    get_segment(x<<1|1, l, r, n);
}
void build_edge_area(int x1, int y1, int x2, int y2) {
    if( x1 > x2 || y1 > y2 ) return ;
    v[0].clear(), v[1].clear();
    get_segment(1, x1, x2, 0);
    get_segment(1, y1, y2, 1);
    for(int i=0;i<v[0].size();i++)
        for(int j=0;j<v[1].size();j++)
            G.addedge(v[0][i], v[1][j], INF);
}
struct node{
    int le, ri;
    node(int _l=0, int _r=0):le(_l), ri(_r){}
};
vector<node>vec[MAXN + 5][2];
int left[MAXN + 5];
int main() {
    int n, q;
    scanf("%d%d", &n, &q);
    build_segtree(1, 1, n, 0); build_segtree(1, 1, n, 1); G.T = cnt + 1;
    build_edge_segtree(1, 0); build_edge_segtree(1, 1);
    for(int i=1;i<=q;i++) {
        int x1, y1, x2, y2;
        scanf("%d%d%d%d", &y1, &x1, &y2, &x2);
        vec[x1][0].push_back(node(y1, y2));
        vec[x2][1].push_back(node(y1, y2));
    }
    vec[n+1][0].push_back(node(1, n));
    for(int i=1;i<=n+1;i++) {
        for(int j=0;j<vec[i][0].size();j++) {
            int lst = vec[i][0][j].le;
            for(int k=vec[i][0][j].le+1;k<=vec[i][0][j].ri;k++)
                if( left[k] != left[k-1] )
                    build_edge_area(left[k-1]+1, lst, i-1, k-1), lst = k;
            build_edge_area(left[vec[i][0][j].ri]+1, lst, i-1, vec[i][0][j].ri);
        }
        for(int j=0;j<vec[i][1].size();j++)
            for(int k=vec[i][1][j].le;k<=vec[i][1][j].ri;k++)
                left[k] = i;
    }
//注意我们必须要先处理矩形的左边再处理矩形的右边,不然遇到宽度为 1 的矩形就直接 GG 了。
    int ans = G.max_flow();
    printf("%d\n", ans);
}

@[email protected]

一开始我写的从上往下的扫描线,结果发现 TLE 在 144th 组数据上。

气的我一怒之下把扫描线改成从左往右的。

然后……它就 AC 了???

听说机房里的另外一个人遇到了一样的情况,然后他把 isap 换成了 dinic 才过的。

好玄妙啊,果然是网络流。

原文地址:https://www.cnblogs.com/Tiw-Air-OAO/p/10176234.html

时间: 2024-11-08 17:10:40

@codeforces - [email protected] Oleg and chess的相关文章

@codeforces - [email&#160;protected] Mashmokh&#39;s Designed Problem

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一棵 n 个点的树,每个点的儿子是有序的. 现给定 m 次操作,每次操作是下列三种中的一种: (1)给定 u, v,询问 u, v 之间的距离. (2)给定 v, h,断开 v 到父亲的边,将 v 这棵子树加入到它的第 h 个祖先的最后一个儿子. (3)给定 k,询问在当前这棵树上

@codeforces - [email&#160;protected] T-Shirts

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 有 n 件 T-shirt,第 i 件 T-shirt 有一个 ci 和 qi,分别表示费用与质量. 同时有 k 个顾客,第 j 个顾客准备了 bj 的金钱去购买 T-shirt. 每个顾客的购买策略是相同的: 他会买他的资金范围内 q 值最大的一件,如果有多个选 c 最小的一件,每种

@codeforces - [email&#160;protected] Lucky Tickets

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 已知一个数(允许前导零)有 n 位(n 为偶数),并知道组成这个数的数字集合(并不一定要把集合内的数用完).求有多少种可能,使得这个数前半部分的数位和等于后半部分的数位和. 模 998244353. input 第一行两个整数:n k.表示这个数的位数以及组成这个数的数字集合大小.2

@codeforces - [email&#160;protected] Bandit Blues

目录 @[email protected] @[email protected] @part - [email protected] @part - [email protected] @accepted [email protected] @[email protected] @[email protected] 求有多少个长度为 n 的排列,从左往右遍历有 a 个数比之前遍历的所有数都大,从右往左遍历有 b 个数比之前遍历的所有数都大. 模 998244323. input 一行三个整数 n

@codeforces - [email&#160;protected] Vus the Cossack and a Field

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一个 n*m 的 01 矩阵,通过这个矩阵生成一个无穷矩阵,具体操作如下: (1)将这个矩阵写在左上角. (2)将这个矩阵每位取反写在右上角. (3)将这个矩阵每位取反写在左下角. (4)将这个矩阵写在右下角. (5)将得到的矩阵再作为初始矩阵,重复这些操作. 比如对于初始矩阵:

@codeforces - [email&#160;protected] Big Problems for Organizers

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] n 个点连成一棵树,经过每条边需要花费 1 个单位时间. 现给出 m 次询问,每次询问给出两个点,需要求所有点同时出发,最终所有点到达这两个点之一的最小花费时间. input 第一行包含一个整数 n (2?≤?n?≤?100000) ,表示点数. 接下来 n-1 行每行两个 1~n 的

@codeforces - [email&#160;protected] Strongly Connected Tournament

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] n 个选手参加了一场竞赛,这场竞赛的规则如下: 1.一开始,所有选手两两之间独立进行比赛(没有平局). 2.主办方将胜者向败者连边形成 n 个点的竞赛图. 3.主办方对这个竞赛图进行强连通分量缩点. 4.每一个强连通分量内部的选手重复步骤 1~3,直到每一个强连通分量内只剩一个选手.

@codeforces - [email&#160;protected] Rotate Columns (hard version)

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一个 n*m 的矩阵 A. 定义一次操作为将矩阵的某一列竖着循环移位,你可以对任意列做任意次操作. 定义 ri 为第 i 行的最大值,最大化 r1 + r2 + ... + rn. Input 第一行一个整数 t (1≤t≤40),表示数据组数. 每组数据第一行包含两个整数 n m

@codeforces - [email&#160;protected] Koala and Notebook

目录 @[email protected] @[email protected] @accepted [email protected] @[email protected] @[email protected] 给定一个 n 点 m 边的无向连通图,每条边的编号按照输入顺序依次为 1, 2, ..., m. 现从 1 号点出发,当经过编号为 i 的边时,将 i 写下来.因为写的数之间没有空隙,所以写下来的所有数最终会连成一个数. 对于每一个除 1 以外的点,当它作为终点时,最终连成的数最小是多