纸带
题目描述:
一串数,对其进行N次操作,第i次操作为在[Li,Ri]上擦除曾经写上的数字(如果有的话),并且写上数字i。询问最终可以看到多少种数字。
输入格式:
第一行,一个整数N,表示操作次数。
接下来N行,每行两个整数,Li和Ri,表示第i次操作的左端点和右端点。
输出格式:
输出一行,一个整数,表示最终看到的数字种数。
数据规模:
对于30%的数据满足:N<=100;Ri<=10000。
对于40%的数据满足:N<=1000。
对于70%的数据满足:N<=10^5
对于100%的数据满足:1<=n<=10^6;0<=Li<=10^9
解析:
解法一:
建立一个数组,表示每一个格子上面涂写的数字,每次操作直接修改。
时间复杂度O(NR),空间复杂度O(R)。
期望得分:20分。
解法二:
用一个队列维护当前保留的线段的集合。每当一个新线段加入目前的集合时,检查集合内所有线段,如果分割某线段则分割线段并答案加一,覆盖某线段则覆盖该路径......
时间复杂度O(N^2),空间复杂度O(N)。
期望得分:40分。
解法三(非正解方法):
解法一的线段树优化方法,需要进行离散化。
时间复杂度O(N log N),空间复杂度 O(N)。
期望得分:100分。
解法四:
来说说正解。我们发现,如果从正面加区间会很复杂,那么我们就考虑能不能倒着加区间。
如果倒着加区间,有一个优势是如果一个区间被染色后,那么之后就不用再考虑这些被涂过的区间了(原因自己想),那么我们需要一种数据结构来跳过这个区间。并查集就能完成。
用father[i]表示位置i所属的区间,用pos[i]来表示区间i最右侧的位置,那么pos[father[i]]就表示位置i所属的区间最右侧的位置。每次加区间时,若与原有区间有重合,则将这两个区间合并。注意!如果现在要加的区间被原有区间包含则不进行处理(因为说明这个区间最终会被覆盖),具体在加区间时会出现以下这几种情况:
情况一:
情况二:
情况三:
情况四:
对于第一种情况,我们直接加一个判断即可:
if(pos[getfather(l)]>=r) return;
对于第二到第四种情况,我们就可以这样:
ans++;
for(register int i=pos[getfather(l)]+1;i<=r;i=pos[getfather(i)]+1) merge(i,i-1);
这样就巧妙地将区间i里面位置放进一个集合,同时又与重叠区间合并。
整个算法的大致做法就是这样,但即使按这种方法做了,最终又不能拿100分,因为你如果仔细阅读了数据范围就会发现,区间最大会取到10^9,而内存限制为128MB,直接存储肯定MLE,那么我们就要想一种方法把内存尽量压下来,于是就自然联想到了离散化。对区间进行离散化处理,能有效地提高算法的时空效率。
inline void pre()
{
sort(a+1,a+cnt+1,comp);
register int i=1;
while(i<=cnt)
{
register int j=i;
while(i<=cnt&&a[i+1].num==a[i].num)
{
i++;
if(i>=cnt) break;
}
top++;
for(int k=j;k<=i;k++)
{
if(a[k].kind==0) L[a[k].id]=top;
else R[a[k].id]=top;
}
i++;
}
}
代码(并查集+离散化):
#include <bits/stdc++.h>
using namespace std;
const int Max=2001000;
int n,m,ans,cnt,top;
int L[Max*2],R[Max*2];
int father[Max*2],pos[Max*2];
struct shu{int num,id,kind;}; //id为插入次序,kind存储起始和终点
shu a[Max*2];
inline int get_int()
{
int x=0,f=1;
char c;
for(c=getchar();(!isdigit(c))&&(c!='-');c=getchar());
if(c=='-') {f=-1;c=getchar();}
for(;isdigit(c);c=getchar()) x=(x<<3)+(x<<1)+c-'0';
return x*f;
}
inline bool comp(const shu &a,const shu &b) //离散化前线排序
{
return a.num<b.num;
}
inline void pre() //离散化
{
sort(a+1,a+cnt+1,comp);
register int i=1;
while(i<=cnt)
{
register int j=i;
while(i<=cnt&&a[i+1].num==a[i].num)
{
i++;
if(i>=cnt) break;
}
top++;
for(int k=j;k<=i;k++)
{
if(a[k].kind==0) L[a[k].id]=top;
else R[a[k].id]=top;
}
i++;
}
}
inline int getfather(int v)
{
if(father[v]==v) return father[v];
father[v]=getfather(father[v]);
return father[v];
}
inline void merge(int x,int y)
{
int i=getfather(x);
int j=getfather(y);
if(i!=j) father[j]=i;
}
inline void solve(int l,int r) //判断,合并
{
if(pos[getfather(l)]>=r) return;
ans++;
for(register int i=pos[getfather(l)]+1;i<=r;i=pos[getfather(i)]+1) merge(i,i-1);
}
int main()
{
//freopen("ribbon.in","r",stdin);
//freopen("ribbon.out","w",stdout);
n=get_int();
for(register int i=1;i<=n;i++)
{
a[++cnt].num=get_int();
a[cnt].id=i;
a[cnt].kind=0;
a[++cnt].num=get_int();
a[cnt].id=i;
a[cnt].kind=1;
}
pre();
for(register int i=1;i<=top;i++) father[i]=pos[i]=i; //初始化
for(register int i=n;i>=1;i--) solve(L[i],R[i]);
cout<<ans<<"\n";
return 0;
}
代码(线段树+离散化)
/*
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;
}