Codeforces Round #737 (Div. 2)D. Ezzat and Grid (dp+线段树)

传送门

在这里插入图片描述

给了n个01串,每个串都是1e9的长度。
一个01串集合是优美的当且仅当相邻的任意两个串中,至少存在一列,满足在这两个串里面都是1。
问最少删去多少个串,才能使得剩下的串构成一个优美集合。


题目等价于可以从中挑出多少个串,使得任意相邻的两个串都满足条件。

我们先思考一个暴力解法:
dp[i]表示前i个串中,最多可以挑出多少个串构成以串i结尾的优美集合。

求解dp[i]:先枚举串i的每一个1的位置j,然后枚举k,(1<=k<=i-1),且串k的j位置上也是1,取dp[i]=max(dp[i],dp[k]+1)
当然,这里实质就是在满足条件的dp[k]中取max。

时间复杂度n * 1e9 * n

怎么去优化呢。

我们可以对01串的[1,1e9]区间建权值线段树,维护当前位置的最大答案,如果以串i结尾的答案求出来等于A,那么在线段树中把串i每个1的位置都更新,与A取max 。
相当于用线段树维护max,记录了dp[i],且保存了取最大值的结尾串下标。
由于一共只有m个区间是1,所以复杂度够,很巧妙。

具体流程:

线段树维护j位置目前的最大答案,由于要输出方案,我们再记录一下这个最大答案是以哪个串作为结尾贡献的。

我们从上往下枚举串i,
对于串i,枚举他的每个1的区间,然后区间查询最大值,取max,
最大值加1,就是串i结尾的最大答案。
重新枚举他的每个1区间,用串i的答案去区间更新。

如果不离散化的话时间复杂度是O(M * logINF )

#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define ull unsigned long long
#define ll long long
#define pii pair<int, int>
const int maxn = 6e5 + 10;
const ll mod = 998244353;
const ll inf = (ll)4e16+5;
const int INF = 1e9 + 7;
const double pi = acos(-1.0);
ll inv(ll b){if(b==1)return 1;return(mod-mod/b)*inv(mod%b)%mod;}
inline ll read()
{
    ll x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
int b[maxn];
int n,n0,m;
vector<pii> a[maxn];
struct node 
{
    #define lc rt<<1
    #define rc rt<<1|1
    int l,r;
    pii f;//max idx
}tree[maxn<<2];
pii tag[maxn<<2];
inline void pushup(int rt)
{
    tree[rt].f = max(tree[lc].f , tree[rc].f);
}
void build(int rt,int l,int r)
{
    tag[rt]={0,0};
    tree[rt].l=l,tree[rt].r=r;
    if(l==r) 
    {   
        tree[rt].f={0,0};
    }
    else 
    {
        int mid=l+r>>1;
        build(lc,l,mid);
        build(rc,mid+1,r);
    }
}
inline void change(int rt,pii v) 
{
    tree[rt].f=max(tree[rt].f,v);
    tag[rt]=max(tag[rt],v);
}
inline void pushdown(int rt) 
{
    if(tag[rt].first) 
    {
        change(lc,tag[rt]);
        change(rc,tag[rt]);
        tag[rt]={0,0};
    }
}
inline void upd(int rt,int vl,int vr,pii v)
{
    int l=tree[rt].l,r=tree[rt].r;
    if(r<vl || l>vr) return ;
    if(vl<=l && r<=vr) 
    {
        change(rt,v);
        return ;
    }
    pushdown(rt);
    upd(lc,vl,vr,v);
    upd(rc,vl,vr,v);
    pushup(rt);
}
inline pii qry(int rt,int vl,int vr)
{
    int l=tree[rt].l,r=tree[rt].r;
    if(vl<=l && r<=vr) 
    {
        return tree[rt].f;
    }
    pushdown(rt);
    int mid=l+r>>1;
    if(vr<=mid) return qry(lc,vl,vr);
    else if(vl>mid) return qry(rc,vl,vr);
    return max(qry(lc,vl,vr),qry(rc,vl,vr));
}
int pre[maxn];
bool vis[maxn];
int main()
{
    scanf("%d %d",&n,&m);
    for(int i=1,t,x,y;i<=m;i++)
    {
        scanf("%d %d %d",&t,&x,&y);
        a[t].push_back({x,y});
        b[++n0]=x;
        b[++n0]=y;
    }
    //离散化
    sort(b+1,b+n0+1);
    n0=unique(b+1,b+1+n0)-b-1;
    build(1,1,n0);
    for(int i=1;i<=n;i++)
    {
        for(pii &t:a[i]) 
        {
            t.first=lower_bound(b+1,b+n0+1,t.first)-b;
            t.second=lower_bound(b+1,b+n0+1,t.second)-b;
        }
    }
    for(int i=1;i<=n;i++)
    {
        pii mx={0,0};
        if(a[i].empty()) continue;
        for(pii &t:a[i]) 
        {
            mx=max(mx,qry(1,t.first,t.second));//每段1的对应的最大答案
        }
        if(!mx.first) mx.second=i;//没有  那么目前这个位置的最大答案就是1,在i串产生
        mx.first++;
        //mx.first:以串i结尾的最大答案
        pre[i]=mx.second;//由mx.second这个串结尾的答案转移而来
        for(pii &t:a[i]) 
        {
            upd(1,t.first,t.second,make_pair(mx.first,i));
        }
    }
    pii mx=qry(1,1,n0);
    printf("%d\n",n-mx.first);
    int idx=mx.second;
    do
    {
        vis[idx]=1;
        int t=idx;
        idx=pre[idx];
        if(t==idx) break;
    } while(1);
    for(int i=1;i<=n;i++)
        if(!vis[i]) printf("%d ",i);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值