题目链接:L-Simone and graph coloring_第 45 届国际大学生程序设计竞赛(ICPC)亚洲区域赛(昆明) (nowcoder.com)
题意:给定一个 n 的排列,每两个点直接如果存在逆序就连一条边。请你对这 n 个点染色,要求任意一条边的两点颜色不同,且使用的颜色数量最少,输出染色方案。
分析:先来拿一组样例来模拟一下吧
对于样例 1 3 4 2来说,1可以染成1,3和4因为前边没有数字与其有连边,所以3和4也可以连1,而2前面有3和4与其均有连边,所以2的颜色不能与3和4的颜色相同,可以选择所有与2有连边的点中的颜色的最大值+1作为2即将染的颜色,这就转化为了一个逆序对问题,假如当前要对第i个点染色,因为只有逆序对之间会有连边,所以我们就根据i之前且与i构成逆序对的点的颜色情况来决定对i染的颜色,那i之前且与i构成逆序对的点的颜色情况是怎么分布的呢?假如i之前且与i构成逆序对的点的颜色中最大值是maxx,则i之前且与i构成逆序对的点的颜色一定包含了1~maxx所有值,若对于j<maxx,不存在i之前且与i构成逆序对的点的颜色为j,那么就不可能有j+1,因为没有j,则颜色为j+1的点一定可以取到j,所以i之前且与i构成逆序对的点的颜色一定包含了1~maxx所有值。我们只需要查找到i之前且与i构成逆序对的点中颜色的最大值maxx,我们就可以得到对i染的颜色为maxx+1。这样我们就可以用类似于线段树求逆序对的方法来解决这道题目了。从前往后加点,假如当前已经加完了i-1个点,对于我们当前所要加的点a[i],我们先查找a[i]~n中颜色的最大值,然后就可以确定第i个点的最大值了。
下面是代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
using namespace std;
const int N=4e6+10;
int a[N],l[N],r[N],sum[N],tans[N];
void pushup(int id)
{
sum[id]=max(sum[id<<1],sum[id<<1|1]);
}
void build(int id,int L,int R)
{
l[id]=L;r[id]=R;sum[id]=0;
if(L==R) return ;
int mid=L+R>>1;
build(id<<1,L,mid);
build(id<<1|1,mid+1,R);
}
void update_point(int id,int x,int val)
{
if(l[id]==r[id])
{
sum[id]=val;
return ;
}
int mid=l[id]+r[id]>>1;
if(x<=mid) update_point(id<<1,x,val);
else update_point(id<<1|1,x,val);
pushup(id);
}
int query_interval(int id,int L,int R)
{
if(l[id]>=L&&r[id]<=R) return sum[id];
int mid=l[id]+r[id]>>1;
int ans=0;
if(mid>=L) ans=max(ans,query_interval(id<<1,L,R));
if(mid+1<=R) ans=max(ans,query_interval(id<<1|1,L,R));
return ans;
}
int main()
{
int n,T;
cin>>T;
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
build(1,1,n);
for(int i=1;i<=n;i++)
{
tans[i]=query_interval(1,a[i],n)+1;
update_point(1,a[i],tans[i]);
}
int maxx=0;
for(int i=1;i<=n;i++) maxx=max(maxx,tans[i]);
printf("%d\n",maxx);
for(int i=1;i<=n;i++) printf("%d ",tans[i]);
puts("");
}
return 0;
}