华山论剑
时间限制:
1000 ms | 内存限制:
65535 KB
难度:
3
-
描述
-
有n个剑客(编号1~n)相约华山比剑,分 m 次决斗,为了节省时间,每次决斗 编号在[l,r]的剑客一起决斗,然后xi获胜。当进行下一次决斗,失败后的剑客可能再参与到决斗,m 次决斗后可能不止一位获胜者(没有失败过就视为获胜者)。
-
输入
-
多组测试数据。
对于每组测试数据,第一行输入n和m。接下来输入m行,每行输入l,r,xi。
2 ≤ n ≤ 3*10^5; 1 ≤ m ≤ 3*10^5,l ≤ xi ≤ r
输出
- 每组测试数据输出n个数字,数字间用空格隔开。第i个数子表示第一次击败i号剑客的剑客编号,若i号剑客是最后的获胜者,输出0; 样例输入
-
3 2 1 2 2 1 3 2
样例输出
-
2 0 2
-
多组测试数据。
思路:
一般常规思路是线段树区间set,然后赢得那个点不动,这里学到了区间点更新,就是一个点一个点更新,更新过一次就不再更新了
两种写法:(关键是理解)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 3e5 + 5;
int ans[maxn], lazy[maxn*4], n , m;
void update(int rt, int l, int r, int i, int j, int val)
{
if(lazy[rt]) return ;
// if(l == r && l != val && i == l && lazy[rt] == 0) //注释是我一开始一直re的地方。。l==r就一定要ruturn掉,否则进去下面的程序会re。。。
// {
// lazy[rt] = 1;
// ans[l] = val;
// return;
// }
if(l == r)
{
if(l != val && l <= j && l >= i)
{
lazy[rt] = 1;
ans[l] = val;
}
return ;
}
int mid = (l+r)/2;
// if(j <= mid) update(rt*2, l, mid, i, j, val);
// else if(i > mid) update(rt*2+1, mid+1, r, i, j, val);
// else
// {
// update(rt*2, l, mid, i, mid, val);
// update(rt*2+1, mid+1, r, mid+1, j, val);
// }
if(i <= mid)update(rt*2, l, mid, i, j, val); //这里就是不断把区间划分成一个一个的小区间
if(j > mid)update(rt*2+1, mid+1 , r, i, j, val);
lazy[rt] = lazy[rt*2]&&lazy[rt*2+1];
}
int main()
{
while(~scanf("%d%d", &n, &m))
{
memset(ans, 0, sizeof(ans));
memset(lazy, 0, sizeof(lazy));
while(m--)
{
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
update(1, 1, n, x, y, z);
}
for(int i = 1; i <= n; i++)
printf("%d%c", ans[i], i == n ? '\n' : ' ');
}
return 0;
}
第二种写法,更直观一些
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn = 3e5 + 5;
int ans[maxn], lazy[maxn*4], n , m;
void update(int rt, int l, int r, int i, int j, int val)
{
if(lazy[rt]) return ; //更新过直接返回
if(l == r)
{
if(l != val && l == i)
{
lazy[rt] = 1;
ans[l] = val;
}
return ;
}
int mid = (l+r)/2;
//核心代码:
if(j <= mid) update(rt*2, l, mid, i, j, val);
else if(i > mid) update(rt*2+1, mid+1, r, i, j, val);
else //在两边的,要把要求的区间也分开,这样,要求区间最后也就会变成一个
{
update(rt*2, l, mid, i, mid, val);
update(rt*2+1, mid+1, r, mid+1, j, val);
}
lazy[rt] = lazy[rt*2]&&lazy[rt*2+1];
}
int main()
{
while(~scanf("%d%d", &n, &m))
{
memset(ans, 0, sizeof(ans));
memset(lazy, 0, sizeof(lazy));
while(m--)
{
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
update(1, 1, n, x, y, z);
}
for(int i = 1; i <= n; i++)
printf("%d%c", ans[i], i == n ? '\n' : ' ');
}
return 0;
}
并查集思想维护区间:
每个点的pre 记录的这个点后面离他最近的没更新的。。这样每次在所求区间里,都会直接跳过更新过了的, 如果这个点在x前面 都变成x, 在后面都变成 r+1那个点的pre。。
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 3e5 + 5;
int pre[maxn], ans[maxn];
int Find(int x)
{
return x == pre[x] ? x : pre[x] = Find(pre[x]);
}
int main()
{
int n, m, l, r, x;
while(~scanf("%d%d", &n, &m))
{
memset(ans, 0, sizeof(ans));
for(int i = 0; i <= n+1; i++)
pre[i] = i;
while(m--)
{
scanf("%d%d%d", &l, &r, &x);
for(int i = Find(l); i <= r; i = Find(i+1))
{
if(i == x) continue;
ans[i] = x;
if(i < x) pre[i] = x;
else pre[i] = pre[r+1];
}
// cout << 1 << endl;
}
for(int i = 1; i <= n; i++)
printf("%d%c", ans[i], i == n ? '\n' : ' ');
}
return 0;
}