[APIO2012]
来自FallDream的博客,未经允许,请勿转载,谢谢。
---------------------------------------------------
A.dispatching 派遣
上次hzwer出了这道题所以做过了,搬一下以前的博文。
给定一棵n个点的树和一个费用m,每个点有一个忍者,派遣它的费用是ci,它的领导力是li。你要选择一个点作为领导,并且在它的子树中(包括它)选出尽可能多的点,满足费用不超过m且选出的点的数量*领导的领导力最大。n<=100000 m,c,l<=10^9
题解:这道题很多做法吧..首先费用少的肯定先选,我们每次肯定是从费用小的开始选,所以题目可以转换为求所有点的子树中最多能选几个点。
做法1:平衡树/优先队列+启发式合并
把费用装进一个平衡树/优先队列里面,然后启发式合并,合并次数是nlogn,总复杂度nlog^2n
我的做法:树剖那样子标号,满足子树的dfs序连续,然后主席树,复杂度是nlogn
#include<iostream> #include<cstdio> #include<queue> #define MN 100000 #define MM 5000000 #define INF 2000000000 #define ll long long using namespace std; inline int read() { int 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 n,dfn=0,cnt=0,mx[MN+5],size[MN+5],nl[MN+5],nr[MN+5],head[MN+5],id[MN+5],rt[MN+5]; ll c[MN+5],l[MN+5],ans=0,m; struct edge{ int to,next; }e[MN+5]; struct TREE{ int l,r,size;ll x; }T[MM+5]; void ins(int f,int t) { e[++cnt]=(edge){t,head[f]};head[f]=cnt; } void dfs1(int x) { size[x]=1;mx[x]=0;int maxn=0; for(int i=head[x];i;i=e[i].next) { dfs1(e[i].to); size[x]+=size[e[i].to]; if(size[e[i].to]>maxn){maxn=size[e[i].to];mx[x]=e[i].to;} } } void dfs2(int x) { nl[x]=++dfn;id[dfn]=x; if(mx[x]) dfs2(mx[x]); for(int i=head[x];i;i=e[i].next) if(e[i].to!=mx[x]) dfs2(e[i].to); nr[x]=dfn; } void ins(int x,int nx,ll c) { T[nx].size=T[x].size+1;T[nx].x=T[x].x+c; ll l=1,r=INF,mid; while(l<r) { mid=l+r>>1; if(c<=mid) { T[nx].r=T[x].r;T[nx].l=++cnt; r=mid;nx=T[nx].l;x=T[x].l; } else { T[nx].l=T[x].l;T[nx].r=++cnt; l=mid+1;nx=T[nx].r;x=T[x].r; } T[nx].size=T[x].size+1;T[nx].x=T[x].x+c; } } int query(int x,int nx,ll cc) { ll l=1,r=INF,mid,num=0; while(l<r) { mid=l+r>>1; if(T[T[nx].l].x-T[T[x].l].x<=cc) { cc-=T[T[nx].l].x-T[T[x].l].x; num+=T[T[nx].l].size-T[T[x].l].size; x=T[x].r;nx=T[nx].r;l=mid+1; } else { x=T[x].l;nx=T[nx].l; r=mid; } } if(T[nx].size-T[x].size>0) { int x=cc/((T[nx].x-T[x].x)/(T[nx].size-T[x].size)); num+=x; } return num; } main() { n=read();m=read(); for(int i=1;i<=n;i++) { int fa=read();c[i]=read();l[i]=read(); if(fa) ins(fa,i); } dfs1(1);dfs2(1);cnt=0; for(int i=1;i<=n;i++) ins(rt[i-1],rt[i]=++cnt,c[id[i]]); for(int i=1;i<=n;i++) ans=max(ans,l[i]*1LL*query(rt[nl[i]-1],rt[nr[i]],m)); cout<<ans; return 0; }
B.guard守卫
有n个草丛(#滑稽),里面有K个盖伦忍者,有m个条信息,表示一段区间内有没有盖伦忍者,你要求出一定有盖伦忍者的草丛。 $n,m,k\leqslant 10^{5}$
一开始我看到这道题就写了一个线段树+大判断,挂了3个点,想了好久才发现忍者数量可能会超过的情况没判。
讲讲正解:先删掉没用的,然后剩下的如果是K个,就直接输出,否则我们重新标号之后,排序,删除无用区间,这样满足信息的左右坐标都递增。很显然,对每段区间选择放在最右边的点是最优的。用F[i]表示前i个区间放在最右的节点至少放几个,g[i]表示后... 然后对每个区间,如果它长度为一,直接输出,否则我们认为放在第二右边是次优的,二分出不能覆盖第二右的节点的区间范围,假设是1..a 和b..m,那么判断f[a]+g[b]+1如果大于K,说明这个最右的节点必须放,否则会超过K个。
#include<iostream> #include<cstdio> #include<algorithm> #define MN 100000 using namespace std; inline int read() { int 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; } struct TREE{int l,r,val,x;}T[MN*4+5]; struct ques{int l,r; bool operator <(const ques&y)const{return l==y.l?r>y.r:l<y.l;} }q[MN+5],s[MN+5]; int n,K,m,cnt=0,f[MN+5],g[MN+5],nl[MN+5],nr[MN+5],cn=0,id[MN+5]; void build(int x,int l,int r) { if((T[x].l=l)==(T[x].r=r)) {T[x].x=1;return;} int mid=l+r>>1; build(x<<1,l,mid); build(x<<1|1,mid+1,r); T[x].x=T[x<<1].x+T[x<<1|1].x; } void modify(int x,int l,int r) { if(T[x].val)return; if(T[x].l==l&&T[x].r==r) { T[x].val=1;T[x].x=0; return; } int mid=T[x].l+T[x].r>>1; if(r<=mid) modify(x<<1,l,r); else if(l>mid) modify(x<<1|1,l,r); else {modify(x<<1,l,mid);modify(x<<1|1,mid+1,r);} T[x].x=T[x<<1].x+T[x<<1|1].x; } int query(int x,int k) { if(T[x].val)return 0; if(T[x].l==T[x].r)return T[x].x; int mid=T[x].l+T[x].r>>1; return k<=mid?query(x<<1,k):query(x<<1|1,k); } int get(int x,int l,int r) { int ans=0; for(int mid=l+r>>1;l<=r;mid=l+r>>1) if(s[mid].r<x) ans=mid,l=mid+1; else r=mid-1; return f[ans]; } int get2(int x,int l,int r) { int ans=0; for(int mid=l+r>>1;l<=r;mid=l+r>>1) if(s[mid].l>x) ans=mid,r=mid-1; else l=mid+1; return g[ans]; } int main() { n=read();K=read();m=read(); build(1,1,n); for(int i=1;i<=m;i++) { int l=read(),r=read(),x=read(); if(!x) modify(1,l,r); else q[++cnt]=(ques){l,r}; } if(T[1].x==K) { for(int i=1;i<=n;i++) if(query(1,i)==1) printf("%d\n",i); return 0; } for(int i=1;i<=n;i++) if(query(1,i)==1) nl[i]=++cn,id[cn]=i; for(int i=n;i;i--) nr[i]=(nl[i]?nl[i]:nr[i+1]); for(int i=1;i<=n;i++) nl[i]=(nl[i]?nl[i]:nl[i-1]); for(int i=1;i<=cnt;i++) q[i].l=nr[q[i].l],q[i].r=nl[q[i].r]; sort(q+1,q+cnt+1);int j=0; for(int i=1;i<=cnt;i++) { while(j&&q[i].r<=s[j].r)--j; s[++j]=q[i]; } for(int i=1,mx=0;i<=j;i++) if(s[i].l>mx) {mx=s[i].r;f[i]=f[i-1]+1;} else f[i]=f[i-1]; for(int k=j,mn=n+1;k;k--) if(s[k].r<mn) {mn=s[k].l;g[k]=g[k+1]+1;} else g[k]=g[k+1]; bool flag=0; for(int i=1;i<=j;i++) { if(f[i]!=f[i-1]+1) continue; if(s[i].l==s[i].r) {flag=1;printf("%d\n",id[s[i].l]);continue;} int lt=get(s[i].r-1,1,i-1),rt=get2(s[i].r-1,i+1,j); if(lt+rt+1>K) printf("%d\n",id[s[i].r]),flag=1; } if(!flag) puts("-1"); return 0; }
C.苦无
大概就是乱排序每种情况考虑一下,然后堆搞一搞,最后矩形面积并。
傻逼大码农,本来打算码的,现在代码已经被我删了.