【NOIP模拟】 (10.31) T3 纸带

20 篇文章 1 订阅
18 篇文章 0 订阅

纸带

题目描述:
       一串数,对其进行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;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值