题目大意:
网址:https://daniu.luogu.org/problemnew/show/2468
大意:本题有两问:
[1] 给定一个\(R*C\)的带权矩阵,询问\(2×10^5\)次在一个子矩阵内至少选择多少个点使他们的权值和大于\(H\)。
[2] 给定长度为L的一条链,询问\(2×10^4\)次在一个区间\([L,R]\)中至少选择多少个点使他们的权值和大于\(H\)。
数据范围:对于两问,都有权值\(P<=1000\),第一问中\(R、C<=200\),第二问中\(L<=5\times 10^5\)。
题目解法
第一问(Solve1):
观察到\(R×C×P\)无论是在时间还是在空间上都是可以接受的。
直接开两个前缀和:
\(data[d][i][j]\)为值到了d时的权值和,\(sum[d][i][j]\)为值到了d时的人数和。
那么每次二分一个权值d,然后二维前缀和查询Check即可。
第二问(Solve2):
正解是主席树,然而我自己 yy 出了一个 .... 总统树 ?
还是线段树为基础,进行可持久化,从大往小对于每一个权值建立一棵树。
直接建1000棵线段树肯定是不现实的。所以考虑用主席树的那种搞法。
对于更新路径上的节点。如果还没有新建节点,就建立一个新结点。
那么向下走的时候,走的那个方向如果还没有建立新结点,则要新建,否则走本轮建的结点。
不走的那个方向直接用前一历史版本的即可。
if(ps<=mid){
if(!rs[ro])rs[ro] = rs[lst]; //沿用上一版本
if(ls[ro] == ls[lst])ls[ro] = 0; //更新的路径上所有节点必须新建。
Update2(ls[ro],l,mid,ps,dt,ls[lst]);
}
上面这个代码中lst是上一个历史版本,ro为本轮版本。
与这题类似的还有[SDOI2014]旅行,都是要自己 yy 可持久化或动态的数据结构。
完成建树后,一样的对与每一个询问二分一个权值d,然后Check即可。
具体实现代码:
#include<bits/stdc++.h>
#define maxn 10000000
#define gi(x) scanf("%d",&x);
using namespace std;
int oo,R,C,M,H,x11,x22,y11,y22;
int data[1005][205][205],sum[1005][205][205];
int ls[maxn],rs[maxn],rt[1005],val[maxn],cnt[maxn];
vector<int>g[1005];
int Calc(int hg,int od){
if(od == 0)return data[hg][x22][y22] + data[hg][x11-1][y11-1]
- data[hg][x22][y11-1] - data[hg][x11-1][y22];
if(od == 1)return sum[hg][x22][y22] + sum[hg][x11-1][y11-1]
- sum[hg][x22][y11-1] - sum[hg][x11-1][y22];
}
int GetAns1(){
if(Calc(1,0) < H)return -1;
int L = 1,R = 1000,Res=-1,Ans=0,Last = 0;
while(L<=R){
int mid = (L+R)>>1;
if(Calc(mid,0) >= H){Res = mid; L =mid+1;}
else R = mid-1;
}
Ans = Calc(Res+1,1),Last = Calc(Res+1,0);
return Ans + (H-Last+(Res-1))/Res;
}
void Solve1(){
for(int i = 1,d; i <= R; i ++)
for(int j = 1; j <= C; j ++){
gi(d);
data[d][i][j] = d;
sum[d][i][j] = 1;
}
for(int d = 1; d <= 1000; d ++)
for(int i = 1; i <= R; i ++)
for(int j = 1; j <= C; j ++)
data[d][i][j] += data[d][i][j-1] + data[d][i-1][j] - data[d][i-1][j-1],
sum[d][i][j] += sum[d][i][j-1] + sum[d][i-1][j] - sum[d][i-1][j-1];
for(int d = 999; d >= 1; d --)
for(int i = 1; i <= R; i ++)
for(int j = 1; j <= C; j ++)
data[d][i][j] += data[d+1][i][j], sum[d][i][j] += sum[d+1][i][j];
for(int sq = 1,f; sq <= M; sq ++){
gi(x11); gi(y11); gi(x22); gi(y22); gi(H);
f = GetAns1();
if(f == -1)printf("Poor QLW\n");
else printf("%d\n",f);
}
return;
}
void Build2(int &ro,int l,int r){
ro = ++oo;
if(l == r)return;
int mid = (l+r)>>1;
Build2(ls[ro],l,mid); Build2(rs[ro],mid+1,r);
}
void PushUp2(int ro){
val[ro] = val[ls[ro]] + val[rs[ro]];
cnt[ro] = cnt[ls[ro]] + cnt[rs[ro]];
}
int Ret2(int ro,int typ){return (!typ) ? val[ro] : cnt[ro];}
int Query2(int ro,int l,int r,int tl,int tr,int typ){
if(!ro || r<tl || l>tr)return 0;
if(tl<=l && r<=tr)return Ret2(ro,typ);
int mid = (l+r)>>1;
return Query2(ls[ro],l,mid,tl,tr,typ)+Query2(rs[ro],mid+1,r,tl,tr,typ);
}
void Update2(int &ro,int l,int r,int ps,int dt,int lst){
if(!ro){
ro = ++oo;
val[ro] = val[lst]; cnt[ro] = cnt[lst];
}
if(l == r){cnt[ro]+=1; val[ro]+=dt; return;}
int mid = (l+r)>>1;
if(ps<=mid){
if(!rs[ro])rs[ro] = rs[lst];
if(ls[ro] == ls[lst])ls[ro] = 0;
Update2(ls[ro],l,mid,ps,dt,ls[lst]);
}
else if(ps>mid){
if(!ls[ro])ls[ro] = ls[lst];
if(rs[ro] == rs[lst])rs[ro] = 0;
Update2(rs[ro],mid+1,r,ps,dt,rs[lst]);
}
PushUp2(ro);
}
void Solve2(){
for(int i = 1,d; i <= C; i ++)
{gi(d); g[d].push_back(i);}
Build2(rt[1001],1,C);
for(int i = 1000; i >= 1; i --){
if(g[i].empty())rt[i] = rt[i+1];
for(int j = 0; j < g[i].size(); j ++)
Update2(rt[i],1,C,g[i][j],i,rt[i+1]);
}
for(int sq = 1; sq <= M; sq ++){
gi(x11); gi(y11); gi(x22); gi(y22); gi(H);
if(Query2(rt[1],1,C,y11,y22,0) < H){printf("Poor QLW\n");continue;}
int L = 1,R = 1000,Res = -1,Ans,Last;
while(L<=R){
int mid = (L+R)>>1;
if(Query2(rt[mid],1,C,y11,y22,0) >= H){Res = mid; L = mid+1;}
else R = mid - 1;
}
Ans = Query2(rt[Res+1],1,C,y11,y22,1),Last = Query2(rt[Res+1],1,C,y11,y22,0);
printf("%d\n",Ans + (H-Last+(Res-1))/Res);
}
return;
}
int main(){
gi(R); gi(C); gi(M);
if(R!=1)Solve1();
if(R == 1)Solve2();
return 0;
}