题目链接
问题:
有一组n个正整数。对于每两个元素a
在区间 a[L]~a[R] 中,所有的元素的不相等。
找到一个符合事实的 字典序最小的列。
输入:
第一行输入包含一个整数T,表示测试用例的数量。
第二行两个整数n和m(1≤n,m≤1e5) n 是数组 的长度,m是区间数。
接下来的m行中
的每一行包含两个整数 L 和 R。
输出:
Sample Input
3
2 1
1 2
4 2
1 2
3 4
5 2
1 3
2 4
Sample Output
1 2
1 2 1 2
1 2 3 1 1
分析:
1.将所有的区间 优先按 L 升序,其次按 R 升序。(划重点)
原因:这样有序的排列后,,不会因为有重合的区间而使得已经确定的值又改变。
根据字典序最小原则,先确定的数字之后就不会再改变
2. 如果当前处理的区间包含于上一个处理的区间,那么当前这个不用处理,continue。。
上个 ---------------------
当前 ---------------
3.存在位置没有被区间覆盖。那么字典序最小原则,这个位置是 1.。。。。比如
上个 ------------------
当前 (1) ----
4. 如果当前 区间 L 在上个区间里, R 在上个区间外。那么从上个区间的后一位开始处理。也就是*****的位置
上个 --------------------
当前 -----------------!*******
一、对于第三种情况,可以 for(1~n),每次处理以 数字 i 为 L 的所有区间,,如果没有,可以直接赋值为 1。。
因为之后的区间的 L 肯定会更大,所以这个位置就是 1 ,这也是这种排序方式的好处
二、最困难的是第四种情况。!。如何确定 ******* 位置的数字,使得区间里的数不重复,且数列字典序最小。
错误思路:
最先想到的是,直接把 ****** 像 1,2....这样赋值,显然是错误的。
如果 -------------
-------------******** 像这样两个区间 L 重合,那么 * 就不能是第一个区间里的任何数!!!
究竟哪些数字可以用,哪些数字不能用,并且怎样用才能使数列的字典序最小????
AC代码的巧妙之处就是用了优先队列来记录可以使用的数字,并且是升序,所以能保证字典序最小。
首先把优先队列初始化为 (1~n),每用一个删除一个,
还要去维护,添加前面虽然在某一位使用过
但是之后不会处理到那一位上的数字
下面来讨论 究竟什么数字可以使用并且怎么去实现。。
一切的处理方法都是建立在对所有区间的排序方法上。。
for(i in 1~n) 大循环意思是 按 1~n 的顺序 , 每次处理 L 为 i 的所有区间,
{
while (k < m && a[k].L == i) // 对 L 为 i 的所有区间做处理、、、
{ k++;}
// 注意 !!!程序走到这里时,L = i 的所有区间已经全部处理完了,!!!
// 下一个 处理的是 L = i+1 的 所有区间,
// 也就是说当前的 第 i 位 之后永远不会处理到~~!!
// 所以 就可以把 填在第 i 位的数字放入优先队列中 供之后使用
}
#include <cstdio>
#include <cstring>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
const int MAXN = 1000005;
struct node
{
int l,r;
}a[MAXN];
// 记录最终答案的数组
int ans[MAXN];
// 按区间的左端点从小到大,左端点相等按右端点从小到大
bool cmp(node a,node b)
{
if (a.l == b.l)
return a.r < b.r;
return a.l < b.l;
}
// flag 标记最终ans中出现的元素
int flag[MAXN];
// 从小到大的优先队列
priority_queue<int,vector<int>,greater<int> > que;
int main()
{
int t;
while (scanf("%d",&t)!=EOF)
{
while (t--)
{
// n个元素,m个区间
int n,m;
scanf("%d%d",&n,&m);
// 把m个区间存入结构体中
for (int i = 0;i < m; ++i)
{
scanf("%d%d",&a[i].l,&a[i].r);
}
// 对m个区间排序,按左端点从小到大,左端点相同按右端点从小到大
sort(a,a+m,cmp);
// 初始化 数列为1 2 3 .... n
// ans 初始化为 0
for (int i = 1;i <= n; ++i)
{
que.push(i);
ans[i] = 0;
flag[i] = 0;
}
// pos记录上一次处理结束的下一个位置的坐标,
// 也就是上一个区间的右端点的后一位
// 同时pos 是每次需要处理的位置的坐标
int pos = 1,k = 0;
for (int i = 1;i <= n; ++i)
{
// 处理以 i 为左端点的每个区间
// k表示第k个区间
while (k < m && a[k].l == i)
{
// 比较pos与当前需要处理的区间的左端点的大小
// 取大的数作为这一次处理的起点
// 大的数一定在上一个区间的右边
pos = max(i,pos);
// 正式开始处理这个区间
while (pos <= a[k].r)
{
ans[pos] = que.top();
flag[ans[pos]] = 1;
que.pop();
pos++;
}
// 下一个区间
k++;
}
//保证区间内的数不同 ans数组再往前走 用过的位置的数已经可以使用
// 到这里是以当前 i 为 左端点的区间都处理完了
// ans[i] = 0表示并没有处理到第i个数,
// 也就是前面的所有区间都没有覆盖到 ans[i]
// 字典序最小原则,前面的区间没有被处理到,就赋值为1
if (ans[i] == 0)
ans[i] = 1;
//
else if (flag[ans[i]] == 1)
{
que.push(ans[i]);
flag[ans[i]] = 0;
}
}
for (int i = 1;i <= n; ++i)
{
printf("%d%c",ans[i],i == n ? '\n' : ' ');
}
// 把数列清空
while (!que.empty())
que.pop();
}
}
return 0;
}