bzoj4388: JOI2012 invitation(线段树+堆)

27 篇文章 0 订阅
15 篇文章 0 订阅

题面在这里
代码题。。

题意

n n 个男生, m 个女生。给出 k k 条关系,每个关系形如 ai,bi,ci,di,ti ,表示 [ai,bi] [ a i , b i ] 的男生和 [ci,di] [ c i , d i ] 的女生是好朋友,幸福指数为 ti t i 。现在选择了一个男生 C C 加入集合,接着要每次选择一个集合中的人的好朋友加入到集合中,并且权值+=他们的幸福指数,直到所有人都被选择。问怎样选才能使得幸福指数和最大。若无法将所有人选择输出-1

做法

观察到题目其实在模拟一个prim求最大生成树的过程。考虑用数据结构优化这个prim。

首先离散化,关键点之间的权值和可以直接线段树维护最大值计算出来,然后将所有点缩为4n个关键点。

开两个线段树维护所有未在集合中的点,以及他们连出去的边。

前者可以保存一个区间最右边还未选择的点,如果区间中的数都被选择就是0;

后者可以对于线段树每个节点开一个链表。

S 为已选择的点的集合, T T 为未选择的点的集合,用一个堆维护 ST 的最大幸福指数,保存那个关系的编号。

每次弹出堆顶,用这个关系不停去更新,直到更新不了为止。

注意一些细节,在代码中有相应注释。

代码

#include<bits/stdc++.h>
#define rep(i,x,y) for (int i=(x); i<=(y); i++)
#define N 131072
#define M 2097152
#define ll long long
#define pii pair<int,int>
#define lc (o<<1)
#define rc (o<<1|1)
using namespace std;
ll read(){
    char ch=getchar(); ll x=0; int op=1;
    for (; !isdigit(ch); ch=getchar()) if (ch=='-') op=-1;
    for (; isdigit(ch); ch=getchar()) x=(x<<1)+(x<<3)+ch-'0';
    return x*op;
}
int n1,C,seq[N<<1],val[M]; ll ans; bool del[N];
void gg(){
    puts("-1"); exit(0);
}
priority_queue<pii> q;
struct node{ int a,b,c,d,t; }d[N];
void cmax(int &x,int y){
    if (y>x) x=y;
}
struct seg{
    int n,m,cnt,b[N<<1],val[M],head[M];
    seg(){
        cnt=0; m=0;
        memset(head,0,sizeof(head));
    }
    struct linker{
        int to,nxt;
    }e[M];
    void add(int x,int y){
        b[++m]=x; b[++m]=y;
    }
    void pre(){
        b[++m]=1; b[++m]=n; sort(b+1,b+1+m);
        int m_=m; m=0;
        rep (i,1,m_) if (!m || b[i]!=b[m]) b[++m]=b[i];
    }
    void get(int &x,int &y){
        x=lower_bound(b+1,b+1+m,x)-b;
        y=lower_bound(b+1,b+1+m,y)-b;
    }
    void cal(){
        rep (i,1,m-1) if (b[i+1]-b[i]-1){
            if (!seq[i]) gg();
            ans+=(ll)seq[i]*(b[i+1]-b[i]-1);
        }
    }
    void build(int o,int l,int r){
        val[o]=r; if (l==r) return;
        int mid=l+r>>1;
        build(lc,l,mid); build(rc,mid+1,r);
    }
    void ins(int o,int l,int r,int x,int y,int z){
        if (x<=l && r<=y){
            e[++cnt].to=z; e[cnt].nxt=head[o]; head[o]=cnt;
            return;
        }
        int mid=l+r>>1;
        if (x<=mid) ins(lc,l,mid,x,y,z);
        if (y>mid) ins(rc,mid+1,r,x,y,z);
    }
    void change(int o,int l,int r,int p){
        for (int &i=head[o]; i; i=e[i].nxt){//一定要加‘&’!这样才不会重复遍历。
            int v=e[i].to;
            if (!del[v]){
                q.push(make_pair(d[v].t,v));
                del[v]=1;
            }
        }
        if (l==r){ val[o]=0; return; }
        int mid=l+r>>1;
        if (p<=mid) change(lc,l,mid,p); else change(rc,mid+1,r,p);
        val[o]=max(val[lc],val[rc]);
    }
    int ask(int o,int l,int r,int x,int y){
        if (x<=l && r<=y) return val[o];
        int mid=l+r>>1,ret=0;
        if (x<=mid) cmax(ret,ask(lc,l,mid,x,y));
        if (y>mid) cmax(ret,ask(rc,mid+1,r,x,y));
        return ret;
    }
}A,B;//A,B存的是所有未在集合中的点,以及他们连出去的边
void build(int o,int l,int r){
    val[o]=0; if (l==r) return;
    int mid=l+r>>1;
    build(lc,l,mid); build(rc,mid+1,r);
}
void change(int o,int l,int r,int x,int y,int v){
    if (x<=l && r<=y){
        cmax(val[o],v); return;//标记永久化
    }
    int mid=l+r>>1;
    if (x<=mid) change(lc,l,mid,x,y,v);
    if (y>mid) change(rc,mid+1,r,x,y,v);
}
void dfs(int o,int l,int r){
    if (l==r){ seq[l]=val[o]; return; }
    int mid=l+r>>1;
    cmax(val[lc],val[o]); cmax(val[rc],val[o]);
    dfs(lc,l,mid); dfs(rc,mid+1,r);
}
int main(){
    A.n=read(); B.n=read(); C=read();
    n1=read();
    rep (i,1,n1){
        d[i].a=read(); d[i].b=read(); d[i].c=read(); d[i].d=read(); d[i].t=read();
        A.add(d[i].a,d[i].b); B.add(d[i].c,d[i].d);
    }
    A.pre();//离散化
    rep (i,1,n1) A.get(d[i].a,d[i].b);
    build(1,1,A.m);
    rep (i,1,n1) if (d[i].a<d[i].b) change(1,1,A.m,d[i].a,d[i].b-1,d[i].t);
    dfs(1,1,A.m); A.cal();
    B.pre();//离散化
    rep (i,1,n1) B.get(d[i].c,d[i].d);
    build(1,1,B.m);
    rep (i,1,n1) if (d[i].c<d[i].d) change(1,1,B.m,d[i].c,d[i].d-1,d[i].t);
    dfs(1,1,B.m); B.cal();
    //以上处理了相邻关键点之间的权值
    A.build(1,1,A.m); B.build(1,1,B.m);
    rep (i,1,n1){
        A.ins(1,1,A.m,d[i].a,d[i].b,i);//建立每个点对应的关系
        B.ins(1,1,B.m,d[i].c,d[i].d,i);
    }
    A.change(1,1,A.m,C);
    while (!q.empty()){
        int x=q.top().second,y; q.pop();
        if (y=A.ask(1,1,A.m,d[x].a,d[x].b)){
            ans+=d[x].t; A.change(1,1,A.m,y);
            q.push(make_pair(d[x].t,x));//一直要push进去,直到没有可以更新的人为止
            continue;
        }
        if (y=B.ask(1,1,B.m,d[x].c,d[x].d)){
            ans+=d[x].t; B.change(1,1,B.m,y);
            q.push(make_pair(d[x].t,x));
        }
    }
    if (A.val[1] || B.val[1]) gg();
    printf("%lld\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值