链接:https://ac.nowcoder.com/acm/contest/887/C
来源:牛客网
时间限制:C/C++ 3秒,其他语言6秒
空间限制:C/C++ 65536K,其他语言131072K
64bit IO Format: %lld
题目描述
The Wow village is often hit by wind and sand,the sandstorm seriously hindered the economic development of the Wow village.
There is a forest in front of the Wowo village, this forest can prevent the invasion of wind and sand. But there is a rule that the number of tallest trees in the forest should be more than half of all trees, so that it can prevent the invasion of wind and sand. Cutting down a tree need to cost a certain amount of money. Different kinds of trees cost different amounts of money. Wow village is also poor.
There are n kinds of trees. The number of i-th kind of trees is P_iP i , the height of i-th kind of trees is H_iHi , the cost of cutting down one i-th kind of trees is C_iC i .(Note: “cutting down a tree” means removing the tree from the forest, you can not cut the tree into another height.)
输入描述:
The problem is multiple inputs (no more than 30 groups).
For each test case.
The first line contines one positive integers n (1 \leq n \leq 10^5)n(1≤n≤10 5 ),the kinds of trees.
Then followed n lines with each line three integers H_i (1 \leq H_i \leq 10^9)H i (1≤H i ≤10 9 )-the height of each tree, C_i (1 \leq C_i \leq 200)C i (1≤C i ≤200)-the cost of cutting down each tree, and P_i(1 \leq P_i\leq 10^9)P i(1≤P i≤10 9 )-the number of the tree.
输出描述:
For each test case, you should output the minimum cost.
示例1
输入
复制
2
5 1 1
1 10 1
2
5 1 2
3 2 3
输出
复制
1
2
题意:n种树,每种树有一个高度,砍掉需要的价值,以及数量。
现在我们要把最高的树的数目严格大于所有树数目的一半。问所需要的最小价值是多少。
一开始以为是贪心,后来又想是dp。最后想的主席树。。。
用主席树还是权值线段树都行,因为我们求的是1~i区间的。
(主席树做法)我们先把高度由小到大排个序之后,建立各个版本的主席树,我们就从高到低去遍历所有种类的树。假如当前的树的高度是H,那么所有大于H的树都应该被砍掉。除此之外,假如最高的树的数目不严格大于一半,那么我们应该去砍小于H的树木,并且使这个值最小。这个就交给主席树去做,我们在主席树中已经统计出来了树的数目,以及需要的花费。就可以统计出砍k颗树所需要的最小价值。
主席树代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
using namespace std;
const int maxx=2e5+100;
struct Node{
ll h;
ll v;
ll num;
bool operator <(const Node &a)const
{
return a.h>h;
}
}t[maxx];
struct node{
ll l;
ll r;
ll sum;
ll num;
}p[maxx*40];
ll v[maxx];
ll root[maxx];
int n;ll numm;
ll update(ll rot,ll l,ll r,ll pos,ll num)
{
ll cur=++numm;
p[cur]=p[rot];
p[cur].num+=(ll)num;
p[cur].sum+=(ll)pos*(ll)num;
if(l==r) return cur;
ll mid=l+r>>1;
if(pos<=mid) p[cur].l=update(p[rot].l,l,mid,pos,num);
else p[cur].r=update(p[rot].r,mid+1,r,pos,num);
return cur;
}
ll query(ll rot,ll l,ll r,ll num)//砍掉num棵树
{
if(l==r) return (ll)l*(ll)num;//如果到达了叶子节点,就砍掉num棵叶子节点。
ll ans=p[p[rot].l].num;
ll mid=l+r>>1;
if(num<=ans) return query(p[rot].l,l,mid,num);//如果小于左子树的树数目
else
{
return p[p[rot].l].sum+query(p[rot].r,mid+1,r,num-ans);//左子树的数目不够,就把左子树的全都砍掉,剩下的去砍右子树
}
}
int main()
{
int tt;
while(scanf("%d",&n)!=EOF)
{
memset(v,0,sizeof(v));
numm=0;
for(int i=1;i<=n;i++) scanf("%lld%lld%lld",&t[i].h,&t[i].v,&t[i].num);
sort(t+1,t+1+n);
for(int i=1;i<=n;i++) v[i]+=v[i-1]+t[i].num;
for(int i=1;i<=n;i++)
{
root[i]=update(root[i-1],1,210,t[i].v,t[i].num);//各个版本的主席树
}
ll Max=1e18;
ll tempnum=0;
ll sum=0;
ll money=0;
for(int i=n;i>=1;i--)
{
tempnum=0;
while(t[i].h==t[i-1].h)//如果有高度相同的树,就一起加上,因为原题只说是高度一样的树,没有说非得是种类相同的树
{
tempnum+=t[i].v*t[i].num;
sum+=t[i].num;
i--;
}
sum+=t[i].num;
tempnum+=t[i].v*t[i].num;
ll temp=0;
if(v[i-1]>=sum) temp=money+query(root[i-1],1,210,v[i-1]-sum+1);//至少要砍这些树才能符合题意
else temp=money;
Max=min(Max,temp);
money+=tempnum;
sum=0;
}
printf("%lld\n",Max);
}
}
权值线段树做法。
我承认权值线段树的做法做麻烦了。。因为我是按着主席树的思想做下去的。
我一开始把所有的信息都插入到了线段树里。但是线段树不像主席树那样有多个版本。所以统计完一棵树之前就要把它删掉。一开始没有这样做,wa成狗。0.00%的绝望。
代码如下:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#define ll long long
using namespace std;
const int maxx=2e5+100;
struct Node{
ll h;
ll v;
ll num;
bool operator<(const Node &a)const{
return a.h>h;
}
}t[maxx];
struct node{
ll l;
ll r;
ll sum;
ll num;
}p[maxx<<2];
ll v[maxx];
int n;
void pushup(int cur)
{
p[cur].sum=p[cur<<1].sum+p[cur<<1|1].sum;
p[cur].num=p[cur<<1].num+p[cur<<1|1].num;
}
void build(ll l,ll r,int cur)
{
p[cur].l=l;
p[cur].r=r;
p[cur].num=p[cur].sum=0;
if(l==r) return ;
ll mid=l+r>>1;
build(l,mid,cur<<1);
build(mid+1,r,cur<<1|1);
pushup(cur);
}
void update(ll l,ll r,ll pos,ll num,int cur,int flag)//flag==0时代表着是删除,flag==1时代表着添加。
{
if(l==r)
{
if(flag)
{
p[cur].num+=num;
p[cur].sum+=1LL*(l*num);
}
else
{
p[cur].num-=num;
p[cur].sum-=1LL*num*l;
}
return ;
}
int mid=l+r>>1;
if(pos<=mid) update(l,mid,pos,num,cur<<1,flag);
else update(mid+1,r,pos,num,cur<<1|1,flag);
pushup(cur);
}
ll query(ll l,ll r,int cur,ll num)//意思类似于主席树的操作。
{
if(num<=0) return 0;
if(l==r) return (ll)num*(ll)l;
ll mid=l+r>>1;
ll ant=p[cur<<1].num;
if(num<=ant) return query(l,mid,cur<<1,num);
else return p[cur<<1].sum+query(mid+1,r,cur<<1|1,num-ant);
}
int main()
{
while(scanf("%d",&n)!=EOF)
{
memset(v,0,sizeof(v));
for(int i=1;i<=n;i++) scanf("%lld%lld%lld",&t[i].h,&t[i].v,&t[i].num);
sort(t+1,t+1+n);
build(1,210,1);
for(int i=1;i<=n;i++)
{
v[i]+=v[i-1]+t[i].num;
update(1LL,210LL,t[i].v,t[i].num,1,1);//一开始把所有的节点信息都加进去了,因为我是从n到1遍历的。
}
ll Max=1e18;
ll Money=0;
ll sum=0;
for(int i=n;i>=1;i--)
{
ll cmoney=0;
while(t[i].h==t[i-1].h)
{
cmoney+=t[i].num*t[i].v;
sum+=t[i].num;
update(1LL,210LL,t[i].v,t[i].num,1,0);
i--;
}
cmoney+=t[i].num*t[i].v;
sum+=t[i].num;
update(1LL,210LL,t[i].v,t[i].num,1,0);//在统计之前就要把它给删除掉,因为不删除有可能在删比它高度小的树的时候出现错误。。因为高度比它小,价值不一定比它小。那么就算小了,就不对了。剩下的和主席树大同小异,权值线段树就是主席树的前身嘛!!
ll temp=0;
if(v[i-1]>=sum) temp=Money+query(1,210,1,v[i-1]-sum+1);
else temp=Money;
Max=min(Max,temp);
Money+=cmoney;
sum=0;
}
printf("%lld\n",Max);
}
return 0;
}
还是太菜了,要理解一会儿才能彻底明白。。
努力加油a啊,(o)/~