【题目】
原题地址
有
【题目分析】
看到题目第一眼就是贪心了。但是贪心的策略以及正确性还是要商讨的。
【解题思路】
先将
对于每一个
否则我们可以丢掉一个
做完这个操作以后我们会得到一个选择最多队友情况下的最优解,但不一定是本题要求的最优(
那我们怎么解决这个问题呢?
显然就可以每次丢掉一个
而且这样显然也是满足题目要求的。(因为本来就是一一对应的关系,先删小的队友不会不合法)
这样这道题目就可以通过了。
你以为这就结束了吗?(实际上确实结束了)
看了正解以后感觉自己还是有点偷鸡qwq
以下就直接放解题报告里面的题解:
不难发现
为了方便起见,我们设
将玩家和房子混在一起,按
如果我们将方案中选取的玩家看成右括号,房子看成左括号,那么方案必定是一个合法的括号序列,
即设
因为
我们希望找到一个没用过的收益最大的房子,满足加入那个房子后仍然是一个合法的括号序列,假设房子位于位置
因此只要
按
如果它非法,那么因为我们按照
因为每个房子只会被考虑一 次,因此复杂度为
注意到上述问题本质是在用线段树模拟费用流的增广,因此当增广路长度
时间复杂度
我看完以后WA地一声就哭了出来。
于是很不甘心地把线duang树版本也写了一次。
【参考代码】
贪心
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=2e5+10;
int n,m,head,tail;
int w[N];
LL ans,sum;
priority_queue<int>q;
priority_queue<int,vector<int>,greater<int> >p;
struct Tdata
{
int x,y,typ;
};
Tdata a[N];
bool cmp(Tdata A,Tdata B)
{
if(A.x==B.x)
return A.typ<B.typ;
return A.x<B.x;
}
int main()
{
freopen("BZOJ4977.in","r",stdin);
freopen("BZOJ4977.out","w",stdout);
scanf("%d%d",&n,&m);
for(int i=1;i<=n;++i)
scanf("%d",&a[i].x),a[i].typ=0;
for(int i=1;i<=m;++i)
{
++n;
scanf("%d%d",&a[n].x,&a[n].y);
a[n].typ=1;a[n].y-=a[n].x;
}
sort(a+1,a+n+1,cmp);
head=1;
for(int i=1;i<=n;++i)
{
if(a[i].typ)
q.push(a[i].y);
else
{
if(q.empty())
{
if(head<=tail)
{
sum+=a[i].x-w[head++];
w[++tail]=a[i].x;
}
}
else
{
int tmp=q.top();q.pop();
sum+=a[i].x+tmp;
p.push(tmp);
w[++tail]=a[i].x;
}
}
}
ans=sum;
for(int i=head;i<=tail;++i)
{
sum-=p.top()+w[i];
p.pop();
ans=max(ans,sum);
}
printf("%lld\n",ans);
return 0;
}
线段树
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int INF=1e9;
const int N=1e5+10;
struct Tnode
{
int w,f;
};
Tnode g[N<<1],d[N];
LL ans;
int i,j,k,n,m,a[N],x,y,h,w[N<<1],c[N<<3],p[N<<3];
inline bool Cmp(Tnode a,Tnode b)
{
if(a.w==b.w)
return a.f<b.f;
return a.w<b.w;
}
inline void pushdown(int x)
{
if(p[x])
{
c[x<<1]+=p[x];p[x<<1]+=p[x];
c[x<<1|1]+=p[x];p[x<<1|1]+=p[x];
p[x]=0;
}
}
inline void update(int x,int l,int r,int L,int R,int y)
{
if(l>R || r<L)
return;
if(l>=L && r<=R)
{
c[x]+=y;p[x]+=y;
return;
}
pushdown(x);
int mid=(l+r)>>1;
if(L<=mid)
update(x<<1,l,mid,L,R,y);
if(R>mid)
update(x<<1|1,mid+1,r,L,R,y);
c[x]=min(c[x<<1],c[x<<1|1]);
}
inline int query(int x,int l,int r,int L,int R)
{
if(l>R || r<L)
return INF;
if(l>=L&&r<=R)
return c[x];
pushdown(x);
int mid=(l+r)>>1,ret=INF;
if(L<=mid)
ret=min(ret,query(x<<1,l,mid,L,R));
if(R>mid)
ret=min(ret,query(x<<1|1,mid+1,r,L,R));
return ret;
}
int main()
{
freopen("BZOJ4977.in","r",stdin);
freopen("BZOJ4977.out","w",stdout);
scanf("%d%d",&n,&m);
h=n+m;
for(i=1;i<=n;++i)
scanf("%d",&a[i]);
sort(a+1,a+n+1);
for(i=1;i<=n;++i)
g[i].w=a[i],g[i].f=i;
for(i=1;i<=m;++i)
{
scanf("%d%d",&x,&y);
g[i+n].w=x;g[i+n].f=i+n;
d[i].w=y-x;d[i].f=i;
}
sort(g+1,g+h+1,Cmp);sort(d+1,d+m+1,Cmp);
for(i=1;i<=h;++i)
w[g[i].f]=i;
for(i=n,j=m;i;--i,--j)
{
update(1,1,h,w[i],h,-1);
for(;j;--j)
{
k=w[d[j].f+n];
if(query(1,1,h,1,k-1)>=0 && query(1,1,h,k,h)>=-1&&a[i]+d[j].w>0)
{
ans+=a[i]+d[j].w;
update(1,1,h,k,h,1);
break;
}
}
if(!j)
break;
}
printf("%lld\n",ans);
return 0;
}
【总结】
仔细思考了以后,发觉贪心的思想和这个线duang树的思想本质上是一样的。
那么我觉得还是写贪心吧qwq。
不过这个线段树模拟费用流增广的思想还是很实用的,毕竟罗指导之前是给我们讲过另一道这种思想的题目呢qwq。