2021 ICPC 昆明 L Simone and graph coloring(树状数组OR线段树)

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)做,我觉得不行,反正我不会

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值