洛谷P2846 [USACO08NOV]光开关Light Switching 线段树+区间反转

博客详细介绍了如何使用线段树解决USACO08NOV的光开关问题,包括区间反转操作。通过维护线段树节点的开灯数量和未反转次数属性,有效地处理了查询和更新操作。文章提供了注意事项和完整的AC代码,帮助读者理解并实现该算法。

洛谷P2846 [USACO08NOV]光开关Light Switching


标签

  • 线段树
  • 区间反转

简明题意

  • 给出一排初始时都关闭着的灯,现需要你支持两张操作。

    1. 将区间[L,R]的灯都反转
    2. 询问区间[L,R]亮着的灯的数量

思路

  • 区间操作显然线段树可以解决
  • 我们给每个节点两个属性:
    1. sum表示当前区间开着的灯的数量
    2. tag表示这个区间的开关还有多少次未被按下
  • 我们只需要维护上面的两个属性即可。

注意事项


总结

  • 要注意spread的时机,以保证真实值

AC代码

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;

const int maxn = 1e5 + 10;

int n, m;

struct Node
{
   int l, r, num;
   bool on;
};

Node tree[maxn * 4];

void build(int o, int l, int r)
{
   tree[o].l = l, tree[o].r = r;
   if (l == r)
   	return;

   int mid = (l + r) / 2;
   build(o * 2, l, mid);
   build(o * 2 + 1, mid + 1, r);
}

void spread(int o)
{
   if (tree[o].on == 1)
   {
   	if (tree[o].l != tree[o].r)
   		tree[o * 2].on ^= tree[o].on, tree[o * 2 + 1].on ^= tree[o].on;
   		
   	tree[o].on = 0;
   	tree[o].num = tree[o].r - tree[o].l + 1 - tree[o].num;
   }
}

void update(int o)
{
   spread(o * 2), spread(o * 2 + 1);
   if (tree[o].l != tree[o].r)
   	tree[o].num = tree[o * 2].num + tree[o * 2 + 1].num;
}

void change(int o, int l, int r)
{
   spread(o);
   if (tree[o].l == l && tree[o].r == r)
   {
   	tree[o].on = 1;
   	return;
   }
   
   int mid = (tree[o].l + tree[o].r) / 2;
   if (r <= mid)
   	change(o * 2, l, r);
   else if (l > mid)
   	change(o * 2 + 1, l, r);
   else
   	change(o * 2, l, mid), change(o * 2 + 1, mid + 1, r);

   update(o);
}

int ask(int o, int l, int r)
{
   spread(o);
   if (tree[o].l == l && tree[o].r == r)
   	return tree[o].num;

   int mid = (tree[o].l + tree[o].r) / 2;
   if (r <= mid)
   	return ask(o * 2, l, r);
   else if (l > mid)
   	return ask(o * 2 + 1, l, r);
   return ask(o * 2, l, mid) + ask(o * 2 + 1, mid + 1, r);
}

void solve()
{
   scanf("%d%d", &n, &m);
   build(1, 1, n);

   while (m--)
   {
   	int opt, l, r;
   	scanf("%d%d%d", &opt, &l, &r);

   	if (opt == 0)
   		change(1, l, r);
   	else
   		printf("%d\n", ask(1, l, r));
   }
}

int main()
{
   //freopen("Testin.txt", "r", stdin);
   solve();
   return 0;
}

双倍经验

### 解决方案 USACO 的题目 **P2895 Meteor Shower S** 是一道经典的 BFS(广度优先搜索)问题,涉及路径规划以及动态障碍物的处理。以下是关于此题目的 C++ 实现方法及相关讨论。 #### 1. 题目概述 贝茜需要在一个二维网格上移动到尽可能远的位置,同时避开由流星造成的破坏区域。每颗流星会在特定时间落在某个位置,并摧毁其周围的五个单元格(中心及其上下左右)。目标是最小化贝茜受到的风险并计算最短到达安全地点的时间[^5]。 --- #### 2. 关键算法思路 为了高效解决这个问题,可以采用以下策略: - 使用 **BFS(广度优先搜索)** 来模拟贝茜可能的行走路线。 - 动态更新地图上的危险区域,确保在每个时刻只考虑有效的威胁。 - 提前预处理所有流星的影响范围,减少冗余计算。 由于直接在每次 BFS 中调用 `boom` 函数可能导致性能瓶颈[^4],因此可以通过优化来降低复杂度。 --- #### 3. 优化建议 为了避免重复标记已知的危险区域,可以在程序初始化阶段完成如下操作: - 创建一个数组记录每个单位时间内哪些坐标会被流星影响。 - 将 BFS 和流星爆炸事件同步进行,仅在必要时扩展新的状态。 这种方法能够显著提升运行速度,尤其对于大规模输入数据(如 $ M \leq 50,000 $),效果尤为明显。 --- #### 4. C++ 示例代码实现 下面提供了一个高效的解决方案框架: ```cpp #include <bits/stdc++.h> using namespace std; const int MAXN = 1e6; int grid[1001][1001]; // 地图大小假设为合理范围内 bool visited[1001][1001]; queue<pair<int, pair<int, int>>> q; // 存储 {time, {x, y}} // 方向向量定义 vector<pair<int, int>> directions = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} }; void initializeGrid(int N, vector<tuple<int, int, int>>& meteors) { memset(grid, 0, sizeof(grid)); for(auto &[t, x, y] : meteors){ if(t >= N || t < 0) continue; // 超过最大时间或负数忽略 grid[x][y] = max(grid[x][y], t); for(auto &[dx, dy] : directions){ int nx = x + dx, ny = y + dy; if(nx >=0 && nx <1001 && ny>=0 && ny<1001){ grid[nx][ny] = max(grid[nx][ny], t); } } } } bool isValid(int time, int x, int y){ return !(grid[x][y] <= time); // 如果当前时间<=流星爆炸时间则不可通过 } int main(){ ios::sync_with_stdio(false); cin.tie(0); int T, X, Y; cin >> T >> X >> Y; vector<tuple<int, int, int>> meteors(T); for(int i=0;i<T;i++) cin >> get<0>(meteors[i]) >> get<1>(meteors[i]) >> get<2>(meteors[i]); initializeGrid(X*Y, meteors); memset(visited, false, sizeof(visited)); q.push({0,{X,Y}}); visited[X][Y]=true; while(!q.empty()){ auto current = q.front(); q.pop(); int currentTime = current.first; int cx = current.second.first, cy = current.second.second; if(isValid(currentTime,cx,cy)){ cout << currentTime; return 0; } for(auto &[dx,dy]:directions){ int nx=cx+dx,ny=cy+dy; if(nx>=0&&nx<1001&&ny>=0&&ny<1001&&!visited[nx][ny]){ if(isValid(currentTime,nx,ny)){ q.push({currentTime+1,{nx,ny}}); visited[nx][ny]=true; } } } } cout << "-1"; // 若无解返回-1 return 0; } ``` 上述代码实现了基于 BFS 的最优路径查找逻辑,并预先构建了流星影响的地图以加速查询过程。 --- #### 5. 进一步讨论 尽管本题的核心在于 BFS 及动态更新机制的应用,但在实际编码过程中仍需注意以下几个方面: - 输入规模较大时应选用快速 IO 方法(如关闭同步流 `ios::sync_with_stdio(false)` 并取消绑定 `cin.tie(NULL)`)。 - 对于超出边界或者无关紧要的数据点可以直接跳过处理,从而节省不必要的运算开销。 - 利用位掩码或其他压缩技术存储访问标志可进一步节约内存资源。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值