bzoj 4383: [POI2015]Pustynia(线段树优化建图+拓扑序)

本文介绍了一种解决特定构造序列问题的方法,通过建立图模型来表示元素间的大小关系,并使用线段树进行优化,最终通过拓扑排序找到满足条件的序列。文章详细解释了算法流程并提供了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目描述

传送门

题目大意:给定一个长度为n的正整数序列a,每个数都在1到 109 范围内,告诉你其中s个数,并给出m条信息,每条信息包含三个数 l,r,k 以及接下来k个正整数,表示 a[l],a[l+1],...,a[r1],a[r] 里这k个数中的任意一个都比任意一个剩下的 rl+1k 个数大(严格大于,即没有等号)。
请任意构造出一组满足条件的方案,或者判断无解。

题解

想办法将题目中给出的大小关系转化成一张图。
对于每条信息,我们新建一个结点now.对于[l,r]中所有给出的较大的位置x[i],now->x[i]边权为0。
所有[l,r]中除x以外的位置y[i],y[i]->now边权为1。这样我们就能表示出x[i]与所有y[i]的关系,至少大1。
然后按照拓扑序求解f[i],表示对结点i限制最紧的答案,及所有能到达i的路径最大权值和。
如果直接暴力建图的话,边太多了,所有利用线段树优化建图,x[i]把区间[l,r]分成了好几段,每次连边的时候直接让线段树中对应的区间连向now。
因为很多点已经给出了权值,那么f[i]应该对a[i]取max,如果f[x]>a[x],那么无解。
如果有某个点的权值超过 109 ,那么也是无解。
如果图中存在环,同样也是无解。

代码

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue> 
#define N 400003
using namespace std;
int ls[N*10],rs[N*10],sz,tot,point[N*10],nxt[N*10],v[N*10];
int n,m,s,c[N*10],pos[N],f[N],a[N],du[N],root,mark[N];
void add(int x,int y,int k)
{
    tot++; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; c[tot]=k; du[y]++;
}
void build(int &i,int l,int r)
{
    i=++sz;
    if (l==r) {
        pos[l]=i;
        return;
    }
    int mid=(l+r)/2;
    build(ls[i],l,mid); build(rs[i],mid+1,r);
    add(ls[i],i,0); add(rs[i],i,0);
}
void query(int now,int l,int r,int ll,int rr)
{
    if (ll<=l&&r<=rr) {
        add(now,sz,1);
        return;
    }
    int mid=(l+r)/2;
    if (ll<=mid) query(ls[now],l,mid,ll,rr);
    if (rr>mid) query(rs[now],mid+1,r,ll,rr);
}
int main()
{
    freopen("a.in","r",stdin);
    scanf("%d%d%d",&n,&s,&m);
    build(root,1,n);
    for (int i=1;i<=s;i++){
        int x,v; scanf("%d%d",&x,&v);
        a[pos[x]]=v;
    }
    for (int i=1;i<=m;i++){
        ++sz;
        int l,r,k; scanf("%d%d%d",&l,&r,&k);
        int L=l-1;
        for (int j=1;j<=k;j++) {
            int x; scanf("%d",&x);
            add(sz,pos[x],0);
            if(L+1<x) query(root,1,n,L+1,x-1);
            L=x;
        }
        if (L<r) query(root,1,n,L+1,r);
    }
    queue<int> p;
    for (int i=1;i<=sz;i++) 
     if (!du[i]) {
        f[i]=1; p.push(i);
        mark[i]=1;
     } 
    while (!p.empty()){
        int x=p.front(); p.pop();
        if (f[x]>a[x]&&a[x]) {
            printf("NIE\n");
            return 0;
        }
        else f[x]=max(a[x],f[x]);
        if (f[x]>1000000000) {
            printf("NIE\n");
            return 0;
        }
        for (int i=point[x];i;i=nxt[i]){
            f[v[i]]=max(f[v[i]],f[x]+c[i]);
            du[v[i]]--;
            if(!du[v[i]]) {
                p.push(v[i]);
                mark[v[i]]=1;
            }
        }
    }
    int cnt=0;
    for (int i=1;i<=sz;i++) cnt+=mark[i];
    if (cnt!=sz) {
        printf("NIE\n");
        return 0;
    }
    printf("TAK\n");
    for (int i=1;i<=n;i++) printf("%d ",f[pos[i]]);
    printf("\n");
} 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值