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