2021 ICPC 昆明 L Simone and graph coloring(树状数组OR线段树)
本来不是一道难题,但是硬是一个多小时没想明白,都过300多个了才想到咋写,结果三个人还不都会写树状数组(活该打铁)
考场上打了线段树,然后写挫了,就调了半天,T了一发,结果就以30分钟的罚时差距打了铁。
题目大意
给定一个n(1e6)长度的排列(1-n均只出现一次),每个位置向之后所有比他小的位置连一条边。形成一个图,对每个节点染色,要求相邻的节点不能染成同一种颜色,使所用颜色种类最少,并求一种方案。
做法
可以考虑一下最优的贪心策略
我们希望连最少的边,那我们尽可能和前面的点选一样的颜色。
如果你这个点前面没连过边,那就使用1
如果使用过1,那就用2
如果2也用过了,那就用3
…
也就是说,一个位置染成什么颜色,就是前面比它大的位置染成的最大颜色+1
可以发现,假如一个位置被染成了3,那么前面必然有一个位置被染成2,它才会被染成3
所以,可以保证,一个位置前面所有的比该位置元素值大的位置,被染成的值必然是从1到最大颜色都出现了
所以,这种染色方案应该是最优的
那么,求1个位置之前所有的出现的数的最大值,可以使用:
1.线段树维护
令线段树每个节点存每个元素值对应颜色的最大值
区间节点存从l到r元素值出现过的颜色最大值
叶子节点存该元素值染成的颜色(因为是排列,所以值唯一)
从1到n扫一遍
每次检查区间a[i]+1到n上的最大值,将其加1作为第i个位置的颜色
query(a[i]+1,n);
(如果a[i]+1到n的元素在之前出现过,那么必然被染色了,否则为初值0不影响统计)
然后将a[i]这个元素值的颜色添加到线段树上
c[i]=a[i]+1;
change(a[i],c[i]);
该死的卡常!
代码
#include<bits/stdc++.h>
using namespace std;
int n,T;
int t[4000005];
int a[1000005];
int c[1000005];
int ans;
void change(int l,int r,int v,int x,int y){
if(l>r) return;
if(l==r){
t[v]=y;
return;
}
int mid=(l+r)/2;
if(x<=mid) change(l,mid,2*v,x,y);
else change(mid+1,r,2*v+1,x,y);
t[v]=max(t[2*v],t[2*v+1]);
}
int query(int l,int r,int v,int ql,int qr){
if(ql>qr) return 0;
if(ql>r||qr<l||l>r) return 0;
if(ql<=l&&qr>=r){
return t[v];
}
int mid=(l+r)/2;
int a1=0,a2=0;
if(ql<=mid||qr>=l) a1=query(l,mid,2*v,ql,qr);
if(ql<=r||qr>=mid+1) a2=query(mid+1,r,2*v+1,ql,qr);
return max(a1,a2);
}
int main(){
cin>>T;
while(T--){
scanf("%d",&n);
for(int i=1;i<=4*n;i++) t[i]=0;
//build(1,n,1);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
ans=0;
for(int i=1;i<=n;i++){
int aa=query(1,n,1,a[i]+1,n);
c[i]=aa+1;
ans=max(ans,c[i]);
change(1,n,1,a[i],aa+1);
}
printf("%d\n",ans);
for(int i=1;i<=n;i++){
printf("%d ",c[i]);
}
printf("\n");
}
}
2.树状数组
树状数组的道理是一样的
但是树状数组维护的是1到i中的最大值
所以我们把数组“反过来”
即把a[i]存在n+1-a[i]的位置
这样我们求1-i的最大值的时候就相当于是a[i]+1到n的最大值啦
假如当时会这个多好呜呜呜打铁了
#include<bits/stdc++.h>
using namespace std;
int n,T,sum;
int ans[1000005];
int a[1000005],c[1000005];
int lowbit(int x){
return x&(-x);
}
void add(int u,int x){
while(u<=n){
c[u]=max(c[u],x);
u+=lowbit(u);
}
}
int query(int u){
int ret=0;
while(u){
ret=max(ret,c[u]);
u-=lowbit(u);
}
return ret;
}
int main(){
cin>>T;
while(T--){
scanf("%d",&n);
sum=1;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
a[i]=n+1-a[i];
ans[i]=query(a[i])+1;
add(a[i],ans[i]);
sum=max(sum,ans[i]);
}
printf("%d\n",sum);
for(int i=1;i<=n;i++){
printf("%d ",ans[i]);
c[i]=0;
}
printf("\n");
}
return 0;
}
据说有人会o(n)做,我觉得不行,反正我不会