CF1070C Solution

题目链接

题解

可以想到,这道题需要维护每日的内核数,并利用贪心思想找出该日价值从小到大的前(k)个内核。第一想法是优先队列+二分,但无法处理出队,于是卡住了(# -_ゝ-)。此后阅读了一篇题解,发现可以使用权值线段树。

线段树中维护当日每个价值的内核个数(因为(p_ile 10^6),不需要离散化),提前存储每日的变动(vector和链式前向星均可),进行单点修改即可。至于查询更像是树上二分,寻找第(k)个内核所在节点与小于其的价值总和:设(t)为当前节点需要查找的内核个数,如果左儿子的内核数(le t),则递归左儿子;反之则说明最接近(k)的节点,加上左儿子的总价值,递归右儿子。

最终答案(=)每日花费总和。

AC代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e6+10;
struct node {int l,r,num,v;} tr[4*N];//tr[i].num:i号节点的内核个数,tr[i].v:i号节点的内核总价值 
int l[N],r[N],c[N],p[N]; 
vector<int> st[N],ed[N];//st[i]:从i日开始的方案,ed[i]:至i日结束的方案
void build(int x,int l,int r)
{
	tr[x].l=l; tr[x].r=r;
	if(l==r) return;
	int mid=(l+r)/2;
	build(x*2,l,mid); build(x*2+1,mid+1,r);
}
void add(int x,int pos,int d)
{
	if(tr[x].l==tr[x].r) 
	{
		tr[x].num+=d; tr[x].v+=d*tr[x].l;
		return;
	}
	int mid=(tr[x].r+tr[x].l)/2;
	if(pos<=mid) add(x*2,pos,d);
	else add(x*2+1,pos,d);
	tr[x].num=tr[x*2].num+tr[x*2+1].num;
	tr[x].v=tr[x*2].v+tr[x*2+1].v;
}
int query(int x,int k)//(此处k同上文中的t)
{
    //如果当前内核个数大于t,或总个数不足k时,取较小值
	if(tr[x].l==tr[x].r) return min(tr[x].num,k)*tr[x].l;
	if(k<=tr[x*2].num) return query(x*2,k);
	else return tr[x*2].v+query(x*2+1,k-tr[x*2].num);
}
signed main()
{
	int n,m,k,ans=0,maxn=0;
	scanf("%lld%lld%lld",&n,&k,&m);
	for(int i=1;i<=m;i++) 
	{
		scanf("%lld%lld%lld%lld",&l[i],&r[i],&c[i],&p[i]);
		st[l[i]].push_back(i); ed[r[i]].push_back(i);
		maxn=max(maxn,p[i]);
	}
	build(1,1,maxn);
	for(int i=1;i<=n;i++)
	{
        //因为含r[i],所以先统计答案再处理结束的方案
		int siz=st[i].size();
		for(int j=0;j<siz;j++) add(1,p[st[i][j]],c[st[i][j]]);
		ans+=query(1,k);
		siz=ed[i].size();
		for(int j=0;j<siz;j++) add(1,p[ed[i][j]],-c[ed[i][j]]);
	}
	printf("%lld",ans);
	return 0;
}
原文地址:https://www.cnblogs.com/violetholmes/p/14317147.html