题解 [APIO2009]会议中心
作者:互联网
\(题目传送门\)
提供一种 \(O(n\log^2(n))\) 的解法。
第一问是经典的最大不交线段集问题,可以用 \(O(n\log(n))\) 的贪心做法和 \(O(n^2)\) 的 \(dp\) 做法解决,但显然贪心处理字典序非常棘手,这里考虑 \(O(n^2)\)的\(dp\) 。
对于一个会议,结束时间越早越好,因此将所有会议离散化后按照结束时间升序排序,这时原来 \(O(n)\) 枚举转移可以用一棵支持单点修改,区间查询的权值线段树维护结束时间为 \(x\) 的最小的\(dp\)值做到 \(O(\log(n))\) ,但结束时间可能会相同,因此在线段树上还要维护最小的字典序。一个显然的方法是以 \(dp\) 值为第一关键字,以会议编号为第二关键字取最优值,代码如下:
int n,tmp[N<<1],cnt;
bool operator<(const pair<int,int>&x,const pair<int,int>&y){
return(x.first==y.first)?x.second<y.second:x.first>y.first;
}
namespace SGT{
pair<int,int>Max[N<<2];
void pushup(int k){
Max[k]=(Max[k<<1]<Max[k<<1|1])?Max[k<<1]:Max[k<<1|1];
}
void modify(int k,int l,int r,int x,pair<int,int>pr){
if(l==r){
if(pr<Max[k])Max[k]=pr;
return;
}
int mid=(l+r)>>1;
if(x<=mid)modify(k<<1,l,mid,x,pr);
else modify(k<<1|1,mid+1,r,x,pr);
pushup(k);
}
pair<int,int>query(int k,int l,int r,int x,int y){
if(r<x||l>y)return make_pair(0,0);
if(l>=x&&r<=y)return Max[k];
int mid=(l+r)>>1;
if(y<=mid)return query(k<<1,l,mid,x,y);
if(mid<x)return query(k<<1|1,mid+1,r,x,y);
if(query(k<<1,l,mid,x,y)<query(k<<1|1,mid+1,r,x,y))return query(k<<1,l,mid,x,y);
return query(k<<1|1,mid+1,r,x,y);
}
}
using namespace SGT;
namespace Solve{
struct Node{int l,r,id;}meet[N];
int dp[N],pre[N],ans[N];
void print(){
int id=0,tot=0;
for(int i=1;i<=n;i++)
if(make_pair(dp[i],i)<make_pair(dp[id],id))id=i;
printf("%d\n",dp[id]);
while(id)ans[++tot]=id,id=pre[id];
sort(ans+1,ans+tot+1);
for(int i=1;i<=tot;i++)printf("%d ",ans[i]);
}
void DP(){
sort(meet+1,meet+n+1,[&](Node&x,Node&y){return x.r<y.r;});
for(int i=1;i<=n;i++){
pair<int,int>u=query(1,1,cnt,1,meet[i].l-1);
dp[meet[i].id]=dp[u.second]+1,pre[meet[i].id]=u.second;
modify(1,1,cnt,meet[i].r,make_pair(dp[meet[i].id],meet[i].id));
}
print();
}
}
using namespace Solve;
int main(){
n=read();
for(int i=1;i<=n;i++)meet[i].l=read(),meet[i].r=read(),meet[i].id=i;
for(int i=1;i<=n;i++){
tmp[++cnt]=meet[i].l;
tmp[++cnt]=meet[i].r;
}
sort(tmp+1,tmp+cnt+1);
cnt=unique(tmp+1,tmp+cnt+1)-tmp-1;
for(int i=1;i<=n;i++){
meet[i].l=lower_bound(tmp+1,tmp+cnt+1,meet[i].l)-tmp;
meet[i].r=lower_bound(tmp+1,tmp+cnt+1,meet[i].r)-tmp;
}
DP();
return 0;
}
但这样的做法有问题,考虑下面这组数据:
输入:
6
1 3
5 7
4 5
6 10
10 12
13 15
输出:
4
1 2 5 6
代码输出
4
1 3 4 6
究其原因在于当前策略在之前的字典序中不一定最优,那么这样的做法是错的吗?注意到 \(dp\) 值与其转移构成一棵森林,而相同 \(dp\) 值比较字典序时在所在树中的深度一定相等,想到树上倍增,具体地,每次 \(dp_u\) 转移后记 \(fa_{u,0}=pre_u\) , \(Min_{u,0}=u\) ,更新 \(u\) 的倍增数组,线段树内比较时在树上跳,跳到路径最小值相等时停止,树上到根路径上的最小值为字典序。
似乎把线段树去掉就是一个 \(\log\) ?没实现过
代码如下:
#include<bits/stdc++.h>
#define int long long
#define INF 0x3f3f3f3f
#define N 400005
#define ls k<<1
#define rs k<<1|1
#define mid ((l+r)>>1)
#define mp make_pair
#define pb push_back
#define fi first
#define se second
#define pii pair<int,int>
#define il inline
#define file(x) freopen(x".in","r",stdin);freopen(x".out","w",stdout);
using namespace std;
il int read(){
int w=0,h=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')h=-h;ch=getchar();}
while(ch>='0'&&ch<='9'){w=w*10+ch-'0';ch=getchar();}
return w*h;
}
struct Node{
int l,r,id;
bool operator<(const Node&p)const{
return r<p.r;
}
}mt[N];
int n,tot,tmp[N];
int dp[N],from[N],ans[N];
namespace Jump{
int fa[N][25],Min[N][25];
void build(int u){
for(int i=1;i<=21;i++){
fa[u][i]=fa[fa[u][i-1]][i-1];
Min[u][i]=min(Min[u][i-1],Min[fa[u][i-1]][i-1]);
}
}
bool check(int u,int v){
int minx=INF,miny=INF;
for(int i=21;i>=0;i--)
if(Min[u][i]!=Min[v][i]){
minx=min(minx,Min[u][i]);
miny=min(miny,Min[v][i]);
u=fa[u][i];v=fa[v][i];
}
return minx>miny;
}
}
bool operator<(const pii&x,const pii&y){
return(x.fi==y.fi)?Jump::check(x.se,y.se):x.fi<y.fi;
}
namespace SGT{
pii Max[N<<2];
pii pushup(pii l,pii r){return(l<r)?r:l;}
void modify(int k,int l,int r,int x,pii pa){
if(l==r){
if(Max[k]<pa)Max[k]=pa;
return;
}
if(x<=mid)modify(ls,l,mid,x,pa);
if(mid<x)modify(rs,mid+1,r,x,pa);
Max[k]=pushup(Max[ls],Max[rs]);
}
pii query(int k,int l,int r,int x,int y){
if(l>y||r<x)return mp(0,0);
if(l>=x&&r<=y)return Max[k];
if(y<=mid)return query(ls,l,mid,x,y);
if(mid<x)return query(rs,mid+1,r,x,y);
return pushup(query(ls,l,mid,x,y),query(rs,mid+1,r,x,y));
}
}
void out(int u){
if(u==0)return;
out(from[u]);
ans[++tot]=u;
}
void print(){
int id=0;
for(int i=1;i<=n;i++)
if(mp(dp[id],id)<mp(dp[i],i))id=i;
printf("%lld\n",dp[id]);
tot=0;
out(id);
sort(ans+1,ans+tot+1);
for(int i=1;i<=tot;i++)cout<<ans[i]<<' ';
}
signed main(){
n=read();
for(int i=1;i<=n;i++){
mt[i].l=read();mt[i].r=read();mt[i].id=i;
tmp[++tot]=mt[i].l;
tmp[++tot]=mt[i].r;
}
sort(tmp+1,tmp+tot+1);
tot=unique(tmp+1,tmp+tot+1)-tmp-1;
for(int i=1;i<=n;i++){
mt[i].l=lower_bound(tmp+1,tmp+tot+1,mt[i].l)-tmp;
mt[i].r=lower_bound(tmp+1,tmp+tot+1,mt[i].r)-tmp;
}
sort(mt+1,mt+n+1);
for(int i=1;i<=n;i++){
pii u=SGT::query(1,1,tot,1,mt[i].l-1);
int id=mt[i].id;
if(u.se==0)dp[id]=1,from[id]=0;
else dp[id]=dp[u.se]+1,from[id]=u.se;
// cout<<id<<' '<<dp[id]<<' '<<from[id]<<endl;
Jump::fa[id][0]=(u.se)?u.se:id;
Jump::Min[id][0]=id;
Jump::build(id);
SGT::modify(1,1,tot,mt[i].r,mp(dp[id],id));
}
print();
return 0;
}
标签:会议,ch,APIO2009,Min,int,题解,meet,dp,define 来源: https://www.cnblogs.com/pidan123/p/15503117.html