[luogu2825 HEOI2016/TJOI2016] 游戏 (二分图最大匹配)

传送门

Description

在2016年,佳缘姐姐喜欢上了一款游戏,叫做泡泡堂。简单的说,这个游戏就是在一张地图上放上若干个炸弹,看是否能炸到对手,或者躲开对手的炸弹。在玩游戏的过程中,小H想到了这样一个问题:当给定一张地图,在这张地图上最多能放上多少个炸弹能使得任意两个炸弹之间不会互相炸到。炸弹能炸到的范围是该炸弹所在的一行和一列,炸弹的威力可以穿透软石头,但是不能穿透硬石头。给定一张nm的网格地图:其中代表空地,炸弹的威力可以穿透,可以在空地上放置一枚炸弹。x代表软石头,炸弹的威力可以穿透,不能在此放置炸弹。#代表硬石头,炸弹的威力是不能穿透的,不能在此放置炸弹。例如:给出14的网格地图xx,这个地图上最多只能放置一个炸弹。给出另一个14的网格地图x#,这个地图最多能放置两个炸弹。现在小H任意给出一张n*m的网格地图,问你最多能放置多少炸弹。

Input

第一行输入两个正整数n,m,n表示地图的行数,m表示地图的列数。1≤n,m≤50。接下来输入n行m列个字符,代表网格地图。的个数不超过nm个。

Output

输出一个整数a,表示最多能放置炸弹的个数

Sample Input

4 4
#∗∗∗
∗#∗∗
∗∗#∗
xxx#

Sample Output

5

Solution

实际上就是互相影响的不能同时选择
把图按照块编号后跑最大匹配

Code

//By Menteur_Hxy
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#define F(i,a,b) for(register int i=(a);i<=(b);i++)
#define E(i,u) for(register int i=head[u];i;i=nxt[i])
#define ins(a,b,c) add(a,b,c),add(b,a,0)
#define add(a,b,c) nxt[++cnt]=head[a],to[cnt]=b,cst[cnt]=c,head[a]=cnt
using namespace std;

int read() {
    int x=0,f=1; char c=getchar();
    while(!isdigit(c)) {if(c=='-')f=-f; c=getchar();}
    while(isdigit(c)) x=(x<<1)+(x<<3)+c-48,c=getchar();
    return x*f;
}

const int N=1010,INF=0x3f3f3f3f;
int n,m,xid,yid,S,T,cnt;
int nxt[N*N],to[N*N],cst[N*N],head[N*N],cur[N*N];
int map[N][N],dx[N][N],dy[N][N],que[N*N<<2],dis[N*N];

bool bfs() {
    memset(dis,0,sizeof(dis));
    int h=0,t=1; que[++h]=S; dis[S]=1;
    while(h<=t) {
        int u=que[h++];
        E(i,u) if(!dis[to[i]]&&cst[i]>0)
            dis[to[i]]=dis[u]+1,que[++t]=to[i];
    }
    return dis[T];
}

int dfs(int u,int flow) {
    if(u==T) return flow;
    int used=0;
    for(register int &i=cur[u],v;i;i=nxt[i]) 
        if(cst[i]>0&&dis[(v=to[i])]==dis[u]+1) {
            int tmp=dfs(v,min(flow-used,cst[i]));
            cst[i]-=tmp,cst[i^1]+=tmp;
            used+=tmp; if(used==flow) return flow;
        }
    if(!used) dis[u]=-1;
    return used;    
}

int dinic() {
    int res=0;
    while(bfs()) {
        F(i,0,T) cur[i]=head[i];
        res+=dfs(S,INF);
    }
    return res;
}

int main() {
    n=read(),m=read();
    F(i,1,n) {
        scanf("\n");
        F(j,1,m) {
            char ch=getchar();
            if(ch=='x') map[i][j]=1;
            else if(ch=='#') map[i][j]=2;
        }
    }
    F(i,1,n) {
        xid++;
        F(j,1,m) 
            dx[i][j]=xid,xid+=(map[i][j]==2);
    }
    F(i,1,m) {
        yid++;
        F(j,1,n) 
            dy[j][i]=yid,yid+=(map[j][i]==2);
    }
    S=0,T=xid+yid+1,cnt=1;
    F(i,1,xid) ins(S,i,1); F(i,1,yid) ins(i+xid,T,1);
    F(i,1,n) F(j,1,m) if(!map[i][j]) 
        ins(dx[i][j],dy[i][j]+xid,1);
    printf("%d",dinic());
    return 0;
}

转载于:https://www.cnblogs.com/Menteur-Hxy/p/9485463.html

内容概要:该研究通过在黑龙江省某示范村进行24小时实地测试,比较了燃煤炉具与自动/手动进料生物质炉具的污染物排放特征。结果显示,生物质炉具相比燃煤炉具显著降低了PM2.5、CO和SO2的排放(自动进料分别降低41.2%、54.3%、40.0%;手动进料降低35.3%、22.1%、20.0%),但NOx排放未降低甚至有所增加。研究还发现,经济性和便利性是影响生物质炉具推广的重要因素。该研究不仅提供了实际排放数据支持,还通过Python代码详细复现了排放特征比较、减排效果计算和结果可视化,进一步探讨了燃料性质、动态排放特征、碳平衡计算以及政策建议。 适合人群:从事环境科学研究的学者、政府环保部门工作人员、能源政策制定者、关注农村能源转型的社会人士。 使用场景及目标:①评估生物质炉具在农村地区的推广潜力;②为政策制定者提供科学依据,优化补贴政策;③帮助研究人员深入了解生物质炉具的排放特征和技术改进方向;④为企业研发更高效的生物质炉具提供参考。 其他说明:该研究通过大量数据分析和模拟,揭示了生物质炉具在实际应用中的优点和挑战,特别是NOx排放增加的问题。研究还提出了多项具体的技术改进方向和政策建议,如优化进料方式、提高热效率、建设本地颗粒厂等,为生物质炉具的广泛推广提供了可行路径。此外,研究还开发了一个智能政策建议生成系统,可以根据不同地区的特征定制化生成政策建议,为农村能源转型提供了有力支持。
<think>题目:P2824 [HEOI2016/TJOI2016] 排序 题目大意:给出一个1到n的全排列,有m个操作,每个操作将区间[l, r]升序或降序排序。最后询问第q个位置上的数字。 常规思路:直接模拟排序操作会超时,因为m和n最大为10^5。 高效解法:二分答案 + 线段树(或类似数据结构)维护01序列。 思路: 1. 注意到最后只询问一个位置的值,考虑二分答案ans。 2. 假设当前二分的值为mid,将原数组中大于等于mid的值设为1,小于mid的值设为0。 3. 这样,排序操作就变成了将区间内的0和1进行排序: - 升序排序:相当于将0全部放到前面,1全部放到后面。 - 降序排序:相当于将1全部放到前面,0全部放到后面。 4. 用线段树维护01序列,支持区间赋值和区间和(即区间内1的个数)。 5. 对于每个操作[l, r]: - 先查询该区间内1的个数(记为cnt)。 - 如果是升序排序,则将区间[l, r-cnt]赋值为0,将区间[r-cnt+1, r]赋值为1。 - 如果是降序排序,则将区间[l, l+cnt-1]赋值为1,将区间[l+cnt, r]赋值为0。 6. 操作完成后,检查位置q的值是否为1(注意:由于我们二分的是答案,如果位置q为1,说明答案至少为mid,否则小于mid)。 7. 不断二分,直到找到最大的mid,使得最后位置q的值为1,这个mid就是答案。 代码实现步骤: - 二分框架:二分答案,范围是1到n。 - 构建线段树:支持区间赋值和区间和。 - 对每个操作进行模拟(区间赋值操作)。 - 最后根据位置q的值调整二分边界。 注意:线段树需要支持区间覆盖(set)操作,并且要注意懒标记的处理。 代码示例: ```cpp #include <iostream> #include <cstdio> #include <algorithm> #include <cstring> using namespace std; const int MAXN = 100005; struct Node { int sum; // 区间内1的个数 int set; // 懒标记:-1表示无操作,0表示置0,1表示置1 } tree[MAXN << 2]; struct Operation { int op, l, r; } op[MAXN]; int n, m, q; int a[MAXN]; // 原数组 int b[MAXN]; // 用于01序列的数组 void pushUp(int rt) { tree[rt].sum = tree[rt<<1].sum + tree[rt<<1|1].sum; } void pushDown(int rt, int l, int r) { if (tree[rt].set != -1) { int mid = (l + r) >> 1; // 下传左子树 tree[rt<<1].set = tree[rt].set; tree[rt<<1].sum = (mid - l + 1) * tree[rt].set; // 下传右子树 tree[rt<<1|1].set = tree[rt].set; tree[rt<<1|1].sum = (r - mid) * tree[rt].set; // 清除标记 tree[rt].set = -1; } } void build(int rt, int l, int r) { tree[rt].set = -1; if (l == r) { tree[rt].sum = b[l]; return; } int mid = (l + r) >> 1; build(rt<<1, l, mid); build(rt<<1|1, mid+1, r); pushUp(rt); } void update(int rt, int l, int r, int L, int R, int val) { if (L <= l && r <= R) { tree[rt].set = val; tree[rt].sum = (r - l + 1) * val; return; } pushDown(rt, l, r); int mid = (l + r) >> 1; if (L <= mid) update(rt<<1, l, mid, L, R, val); if (R > mid) update(rt<<1|1, mid+1, r, L, R, val); pushUp(rt); } int query(int rt, int l, int r, int L, int R) { if (L <= l && r <= R) { return tree[rt].sum; } pushDown(rt, l, r); int mid = (l + r) >> 1; int res = 0; if (L <= mid) res += query(rt<<1, l, mid, L, R); if (R > mid) res += query(rt<<1|1, mid+1, r, L, R); return res; } // 检查二分值mid:构建01序列,然后模拟所有操作 bool check(int mid) { // 构建01序列:>=mid为1,否则为0 for (int i = 1; i <= n; i++) { b[i] = (a[i] >= mid); } // 构建线段树 build(1, 1, n); // 模拟m个操作 for (int i = 0; i < m; i++) { int l = op[i].l, r = op[i].r; int cnt = query(1, 1, n, l, r); // 查询区间内1的个数 if (op[i].op == 0) { // 升序:0在前,1在后 if (cnt > 0) update(1, 1, n, r - cnt + 1, r, 1); // 后cnt个置1 if (r - l + 1 - cnt > 0) update(1, 1, n, l, r - cnt, 0); // 前面的置0 } else { // 降序:1在前,0在后 if (cnt > 0) update(1, 1, n, l, l + cnt - 1, 1); // 前cnt个置1 if (r - l + 1 - cnt > 0) update(1, 1, n, l + cnt, r, 0); // 后面的置0 } } // 查询位置q的值 return query(1, 1, n, q, q) == 1; } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) { scanf("%d", &a[i]); } for (int i = 0; i < m; i++) { scanf("%d%d%d", &op[i].op, &op[i].l, &op[i].r); } scanf("%d", &q); // 二分答案 int L = 1, R = n, ans = 0; while (L <= R) { int mid = (L + R) >> 1; if (check(mid)) { ans = mid; L = mid + 1; } else { R = mid - 1; } } printf("%d\n", ans); return 0; } ``` §§相关问题§§ 1. 为什么二分答案的正确性成立?如何保证最终答案的正确性? 2. 线段树在本题中维护的信息是什么?懒标记的作用是什么? 3. 如果题目要最后多个位置的值,如何修改算法? 4. 如何处理排序操作中的区间覆盖与线段树懒标记的冲突? 5. 是否有其他数据结构(如平衡树)可以替代线段树解决此题?</think>### 洛谷 P2824 "排序" C++ 题解 **题目大意**:给定一个 1~n 的排列,进行 m 次区间排序操作(升序或降序),最后询问第 q 个位置的值。 **核心算法**:二分答案 + 线段树维护 01 序列 **解题思路**: 1. **二分答案**:最终答案范围 [1, n],二分检查 mid 是否可能为答案 2. **01 序列转换**:将原数组转为 01 序列(≥mid 为 1,<mid 为 0) 3. **线段树操作**: - 排序操作转化为区间赋值(升序:0 在前 1 在后;降序:1 在前 0 在后) - 通过区间和快速计算 1 的数量 4. **验证答案**:检查位置 q 在最终序列中是否为 1 **代码实现**: ```cpp #include <iostream> #include <cstdio> #include <algorithm> using namespace std; const int MAXN = 1e5 + 5; struct Node { int sum; // 区间内 1 的数量 int set; // 懒标记:-1 无操作,0 置 0,1 置 1 } tree[MAXN << 2]; struct Operation { int op, l, r; // op=0 升序,op=1 降序 } ops[MAXN]; int n, m, q, pos; int a[MAXN], b[MAXN]; // 线段树维护 void pushUp(int rt) { tree[rt].sum = tree[rt << 1].sum + tree[rt << 1 | 1].sum; } void pushDown(int rt, int len) { if (tree[rt].set != -1) { int setVal = tree[rt].set; tree[rt << 1].set = setVal; tree[rt << 1 | 1].set = setVal; tree[rt << 1].sum = setVal * (len - (len >> 1)); tree[rt << 1 | 1].sum = setVal * (len >> 1); tree[rt].set = -1; } } void build(int rt, int l, int r) { tree[rt].set = -1; if (l == r) { tree[rt].sum = b[l]; return; } int mid = (l + r) >> 1; build(rt << 1, l, mid); build(rt << 1 | 1, mid + 1, r); pushUp(rt); } void update(int rt, int L, int R, int setVal, int l, int r) { if (L <= l && r <= R) { tree[rt].set = setVal; tree[rt].sum = setVal * (r - l + 1); return; } pushDown(rt, r - l + 1); int mid = (l + r) >> 1; if (L <= mid) update(rt << 1, L, R, setVal, l, mid); if (R > mid) update(rt << 1 | 1, L, R, setVal, mid + 1, r); pushUp(rt); } int query(int rt, int L, int R, int l, int r) { if (L <= l && r <= R) return tree[rt].sum; pushDown(rt, r - l + 1); int mid = (l + r) >> 1, res = 0; if (L <= mid) res += query(rt << 1, L, R, l, mid); if (R > mid) res += query(rt << 1 | 1, L, R, mid + 1, r); return res; } bool check(int mid) { // 构建 01 序列 for (int i = 1; i <= n; i++) b[i] = (a[i] >= mid); build(1, 1, n); // 执行所有排序操作 for (int i = 0; i < m; i++) { int l = ops[i].l, r = ops[i].r; int cnt = query(1, l, r, 1, n); // 查询 1 的数量 if (ops[i].op == 0) { // 升序排序 if (r - cnt + 1 <= r) update(1, r - cnt + 1, r, 1, 1, n); if (l <= r - cnt) update(1, l, r - cnt, 0, 1, n); } else { // 降序排序 if (l <= l + cnt - 1) update(1, l, l + cnt - 1, 1, 1, n); if (l + cnt <= r) update(1, l + cnt, r, 0, 1, n); } } return query(1, q, q, 1, n) == 1; // 检查目标位置 } int main() { scanf("%d%d", &n, &m); for (int i = 1; i <= n; i++) scanf("%d", &a[i]); for (int i = 0; i < m; i++) scanf("%d%d%d", &ops[i].op, &ops[i].l, &ops[i].r); scanf("%d", &q); // 二分答案 int L = 1, R = n, ans; while (L <= R) { int mid = (L + R) >> 1; if (check(mid)) { ans = mid; L = mid + 1; } else { R = mid - 1; } } printf("%d\n", ans); return 0; } ``` **算法优势**: - **时间复杂度**:O(m log² n) - **空间复杂度**:O(n) - **核心技巧**:将排序操作转化为 01 序列的区间赋值,避免直接排序的高复杂度
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值