NOIP模拟(10.31)T3 纸带

纸带

题目背景:

10.31 NOIP模拟T3

分析:线段树 or 并查集

 

第一眼:这不是线段树裸题吗?

第二眼:这不就是线段树裸题吗?

第三眼:这数据怎么是106

第四眼:这不是有2s吗?

第五眼:这把稳了!

然后直接暴力线段覆盖就可以了······注意如果要将原来的竖线放到格子上,那么左端点需要 +1才可以,这样就可能出现一些问题,因为数据的范围,你需要离散化才行,那么你离散的过程中可能出现比如说这次染了(下放到格子上)1 ~ 20, 然后下次是1 ~ 5,再下一次是8 ~ 30,如果直接离散化就会变成1 ~ 4, 1 ~ 2, 3 ~ 5,然而中间原本在6 ~ 7的部分应该是第一种颜色的,因为离散化的问题,你会知己将它覆盖,所以离散化过程中注意在相差大于一的两个数之间,插入一个空位留给这种情况就可以了,复杂度O(nlogn),真的要注意常数······

Source:

/*
	created by scarlyw
*/
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <string>
#include <cstring>
#include <cctype>
#include <vector>
#include <queue>
#include <set>
#include <ctime>

inline char read() {
	static const int IN_LEN = 1024 * 1024;
	static char buf[IN_LEN], *s, *t;
	if (s == t) {
		t = (s = buf) + fread(buf, 1, IN_LEN, stdin);
		if (s == t) return -1;
	}
	return *s++;
}

template<class T>
inline void R(T &x) {
	static char c;
	static bool iosig;
	for (c = read(), iosig = false; !isdigit(c); c = read()) {
		if (c == -1) return ;
		if (c == '-') iosig = true;
	}
	for (x = 0; isdigit(c); c = read()) 
		x = ((x << 2) + x << 1) + (c ^ '0');
	if (iosig) x = -x;
}

const int OUT_LEN = 1024 * 1024;
char obuf[OUT_LEN], *oh = obuf;
inline void write_char(char c) {
	if (oh == obuf + OUT_LEN) fwrite(obuf, 1, OUT_LEN, stdout), oh = obuf;
	*oh++ = c;
}

template<class T>
inline void W(T x) {
	static int buf[30], cnt;
	if (x == 0) write_char('0');
	else {
		if (x < 0) write_char('-'), x = -x;
		for (cnt = 0; x; x /= 10) buf[++cnt] = x % 10 + 48;
		while (cnt) write_char(buf[cnt--]);
	}
}

inline void flush() {
	fwrite(obuf, 1, oh - obuf, stdout);
}

/*
template<class T>
inline void R(T &x) {
	static char c;
	static bool iosig;
	for (c = getchar(), iosig = false; !isdigit(c); c = getchar()) {
		if (c == -1) return ;
		if (c == '-') iosig = true;
	}
	for (x = 0; isdigit(c); c = getchar()) 
		x = ((x << 2) + x << 1) + (c ^ '0');
	if (iosig) x = -x;
}
//*/

const int MAXN = 1000000 + 10;
int n, ans, cnt, top;
int tree[MAXN << 4 | 1], l[MAXN], r[MAXN];
bool vis[MAXN << 1];

struct data {
	int num, type, id;
	inline bool operator < (const data &b) const {
		return num < b.num;
	}
} a[MAXN << 1];

void push_down(int k) {
	if (tree[k] != 0) tree[k << 1] = tree[k << 1 | 1] = tree[k], tree[k] = 0;
}

inline void modify(int k, int l, int r, int ql, int qr, int w) {
	if (ql <= l && r <= qr) return (void)(tree[k] = w);
	push_down(k);
	int mid = l + r >> 1;
	if (ql <= mid) modify(k << 1, l, mid, ql, qr, w);
	if (qr > mid) modify(k << 1 | 1, mid + 1, r, ql, qr, w);
}

void get_ans(int k, int l, int r) {
	if (l == r) {
		(vis[tree[k]] || tree[k] == 0) ? 0 : (vis[tree[k]] = true, ans++);
		return ;
	}
	int mid = l + r >> 1;
	push_down(k);
	get_ans(k << 1, l, mid), get_ans(k << 1 | 1, mid + 1, r);
}

inline void read_in() {
	R(n);
	for (int i = 1; i <= n; ++i) {
		R(a[++cnt].num), a[cnt].id = i, a[cnt].type = 0, a[cnt].num++;
		R(a[++cnt].num), a[cnt].id = i, a[cnt].type = 1;
	}
}

inline void solve() {
	std::sort(a + 1, a + cnt + 1);
	register int i = 1;
	while (i <= cnt) {
		register int j = i;
		while (a[i].num == a[i + 1].num) {
			if (i == cnt) break ;
			++i;
		}
		if (j != 1 && a[j - 1].num + 1 < a[i].num) top += 2;
		else ++top;
		for (register int k = j; k <= i; ++k) 
			a[k].type ? (r[a[k].id] = top) : (l[a[k].id] = top);
		++i;
	}
	for (register int i = 1; i <= n; ++i) modify(1, 1, top, l[i], r[i], i);
	get_ans(1, 1, top), std::cout << ans;
}

int main() {
//	freopen("ribbon.in", "r", stdin);
//	freopen("ribbon.out", "w", stdout);
	read_in();
	solve();
	return 0;
}

考虑标算,(标算的确还是比线段树快)我们将操作直接倒过来,如果一个颜色出现过,当且仅当,它的所在区间,没有被之后染得颜色完全覆盖,这一次我们不通过考虑格子来考虑这个问题,坐标就是表示所在染色段的终末端,我们考虑对于每一个坐标维护一个pos[i]表示当前坐标所在的染色段的末端在哪个位置,一开始的时候所有的pos[i]即为自己本身,表示每一个染色段都是从这条线开始,也是从这条线结束(即没有染过色),然后考虑一个染色操作的影响是什么样的,相当于是将所有在l ~ r的坐标所在染色段的pos全部更改为pos[r所在的集合],即相当于对于这之中的坐标之间的格子都已经被覆盖过了,下一个位置应该是在pos[r所在集合]之后的格子,那么我们考虑直接将这一段中的所有坐标所在染色段通过并查集合并,看上去很暴力,但是每一次我们只需要将pos[i所在的集合] + 1,pos[i所在的集合](l <= i < r)合并在一起,因为原来的集合中的染色段已经在之前的操作中合并过了,只需要将不同染色段的之间的端点相连就可以了,然后如果一个颜色没有贡献,就说明pos[l所在的集合] > r可以直接跳过了,否则像上面一样合并即可,考虑这样做的复杂度,显然一开始有n个集合,那么每合并一次就会减少一个,所以最多只有n - 1次合并,复杂度就是O(αn)的,当然离散化还是有一个log。而且这样离散化就不需要插入数字了,不会有覆盖掉中间的情况。

Source:

/*
	created by scarlyw
*/
#include <cstdio>
#include <string>
#include <algorithm>
#include <cstring>
#include <iostream>
#include <cmath>
#include <cctype>
#include <vector>
#include <set>
#include <queue>

inline char read() {
	static const int IN_LEN = 1024 * 1024;
	static char buf[IN_LEN], *s, *t;
	if (s == t) {
		t = (s = buf) + fread(buf, 1, IN_LEN, stdin);
		if (s == t) return -1;
	}
	return *s++;
}

///*
template<class T>
inline void R(T &x) {
	static char c;
	static bool iosig;
	for (c = read(), iosig = false; !isdigit(c); c = read()) {
		if (c == -1) return ;
		if (c == '-') iosig = true;	
	}
	for (x = 0; isdigit(c); c = read()) 
		x = ((x << 2) + x << 1) + (c ^ '0');
	if (iosig) x = -x;
}
//*/

const int OUT_LEN = 1024 * 1024;
char obuf[OUT_LEN], *oh = obuf;
inline void write_char(char c) {
	if (oh == obuf + OUT_LEN) fwrite(obuf, 1, OUT_LEN, stdout), oh = obuf;
	*oh++ = c;
}


template<class T>
inline void W(T x) {
	static int buf[30], cnt;
	if (x == 0) write_char('0');
	else {
		if (x < 0) write_char('-'), x = -x;
		for (cnt = 0; x; x /= 10) buf[++cnt] = x % 10 + 48;
		while (cnt) write_char(buf[cnt--]);
	}
}

inline void flush() {
	fwrite(obuf, 1, oh - obuf, stdout);
}

/*
template<class T>
inline void R(T &x) {
	static char c;
	static bool iosig;
	for (c = getchar(), iosig = false; !isdigit(c); c = getchar())
		if (c == '-') iosig = true;	
	for (x = 0; isdigit(c); c = getchar()) 
		x = ((x << 2) + x << 1) + (c ^ '0');
	if (iosig) x = -x;
}
//*/

const int MAXN = 1000000 + 10;

int n, ans, cnt, top;
int father[MAXN << 2], pos[MAXN << 2], l[MAXN], r[MAXN];
bool vis[MAXN << 2];

struct data {
	int num, id, type;
	inline bool operator < (const data &b) const {
		return num < b.num;
	}
} a[MAXN << 1];

inline int get_father(int x) {
	return (father[x] == x ? x : (father[x] = get_father(father[x])));
}

inline void unite(int x, int y) {
	int fa1 = get_father(x), fa2 = get_father(y);
	if (fa1 != fa2) father[fa1] = fa2, pos[fa2] = std::max(pos[fa1], pos[fa2]);
}

inline void merge(int x, int y) {
	if (pos[get_father(x)] >= y) return ;
	ans++;
	for (int i = pos[get_father(x)] + 1; i <= y; ) 
		unite(i, i - 1), i = pos[get_father(i)] + 1;
}

inline void read_in() {
	R(n);
	for (int i = 1; i <= n; ++i) {
		R(a[++cnt].num), a[cnt].id = i, a[cnt].type = 0;
		R(a[++cnt].num), a[cnt].id = i, a[cnt].type = 1;
	}
}

inline void solve() {
	std::sort(a + 1, a + cnt + 1);
	register int i = 1;
	while (i <= cnt) {
		register int j = i;
		while (a[i].num == a[i + 1].num) {
			if (i == cnt) break ;
			++i;
		}
//		if (j != 1 && a[j - 1].num + 1 < a[i].num) top += 2;
		++top;
		for (register int k = j; k <= i; ++k) 
			a[k].type ? (r[a[k].id] = top) : (l[a[k].id] = top);
		++i;
	}
	for (int i = 1; i <= top; ++i) father[i] = pos[i] = i;
	for (int i = n; i >= 1; --i) merge(l[i], r[i]);
	std::cout << ans;
}


int main() {
//	freopen("in.in", "w", stdout);
	read_in();
	solve();
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值