P5356 [Ynoi Easy Round 2017] 由乃打扑克 题解?
前言
- 做法大概率是乱搞的,狠数据应该可以轻松卡掉,主要是为了记一下动态区间第 \(K\) 小怎么做
msjing差点发明出来了
\(solve\)
- 首先不带修的就是主席树,单推就是树套树,
这题的多修就不会了 - 所以我们尝试发明一下
- 如果就不带修查一次,直接排序可以 \(O(1)\) 找
- 对于一个区间的修,我们修完后再排序还可以 \(O(1)\) 找,但是复杂度巨劣
- 发现排序复杂度太高了,由于你并不能减少排序次数,只能减少排序长度,所以我们在排序长度上下手
- 欸,分块
哒哒哒哒哒,好想写分块- 我们直接让排序区间变成根号长,复杂度就降下来了
- 那就好说了
欸我好像是这么发明的吧- 我们对于每个块局部排序(这样如果修区间包括这个块就不用重新排了,也方便后面操作),记录原下标(方便修)
- 你可以维护两个数组,一个原数组,一个排序数组,方便维护整块散块
- 对于修的操作,块内好说,直接改
- 查询包括多个块,先将散块直接暴力改,改完直接对所在块排序,注意修的时候要分清原数组还是排序数组,排序数组需要按原来的下标对应修,整块直接打 \(tag\),像线段树一样
- 查询非常麻烦
- 如果查询在块内好说,但是包括一堆块就不好办了,我们可以方便处理每一个块内的所有排名,但是多个完全不好做
- msjing就发明到这了
- 等一下,每个块内都是排好序的,即可以直接二分
- 那我们先二分第 \(K\) 小的数是多少,散块暴力统计有几个数小于等于二分值,整块直接二分找,如果总共小于 \(K\),小了,将 \(L\) 向右推,否则将 \(R\) 向左推,并更新答案,由于这个常数巨大,所以我们要加个剪枝:如果一个块内最小的都比二分值大,不可能产生贡献,跳过;如果块内最大的都比二分值小,整块都可以做贡献,全加上,跳过
- 注意你的外层二分范围,别开小了,要不然你就现求范围
- 无解是 \(K\) 比区间长还大,或者查询比 \(1\) 小
- 查询二分部分参考尺子大神的题解,
因为msjing二分写炸了 - 其他就没啥了,注意一下是用原序列还是排序序列,记得加上 \(tag\) 的值
- 对了,题的样例跟滚木一样,msjing手搓了俩
样例
- 1.in
5 3
1 5 8 2 6
1 2 3 1
2 1 3 3
1 2 4 2
- 1.out
5
8
- 2.in
10 5
1 1 4 5 1 4 9 1 7 8
1 2 8 2
2 1 5 2
1 2 8 2
2 3 10 1
1 5 10 3
- 2.out
1
3
5
\(code\)
- 复杂度大概是 \(O(n \sqrt n \log ^2 n)\) 的,其实挺劣
点击查看代码
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define fi first
#define se second
using namespace std;
constexpr int maxn=1e6+10;
int read()
{
int x=0,f=1;
char ch=getchar();
while (ch<'0' || ch>'9')
{
if (ch == '-') f=-1;
ch=getchar();
}
while (ch>='0' && ch<='9')
{
x=(x<<1)+(x<<3)+ch-'0';
ch=getchar();
}
return x*f;
}
int n,m;
int a[maxn];
int L[maxn],R[maxn],pos[maxn],cnt;
pii b[maxn];
void init()
{
cnt=sqrt(n);
for (int i=1;i<=cnt;i++)
L[i]=R[i-1]+1,R[i]=cnt*i;
if (R[cnt]<n)
cnt++,L[cnt]=R[cnt-1]+1,R[cnt]=n;
for (int i=1;i<=cnt;i++)
for (int j=L[i];j<=R[i];j++)
pos[j]=i;
}
int tag[maxn];
void Upd(int l,int r,int v)
{
if (pos[l] == pos[r])
{
for (int i=L[pos[l]];i<=R[pos[r]];i++)
if (b[i].se>=l && b[i].se<=r)
b[i].fi+=v;
for (int i=l;i<=r;i++) a[i]+=v;
sort(b+L[pos[l]],b+R[pos[r]]+1);
return;
}
for (int i=pos[l]+1;i<=pos[r]-1;i++)
tag[i]+=v;
for (int i=L[pos[l]];i<=R[pos[l]];i++)
if (b[i].se>=l && b[i].se<=R[pos[l]])
b[i].fi+=v;
for (int i=l;i<=R[pos[l]];i++) a[i]+=v;
sort(b+L[pos[l]],b+R[pos[l]]+1);
for (int i=L[pos[r]];i<=R[pos[r]];i++)
if (b[i].se>=L[pos[r]] && b[i].se<=r)
b[i].fi+=v;
for (int i=L[pos[r]];i<=r;i++) a[i]+=v;
sort(b+L[pos[r]],b+R[pos[r]]+1);
}
int chk(int l,int r,int x,int k)
{
int sum=0;
if (pos[l] == pos[r])
{
for (int i=l;i<=r;i++)
if (a[i]+tag[pos[l]]<=x)
sum++;
if (sum<k) return 1;
else return 0;
}
for (int i=l;i<=R[pos[l]];i++)
if (a[i]+tag[pos[l]]<=x)
sum++;
for (int i=L[pos[r]];i<=r;i++)
if (a[i]+tag[pos[r]]<=x)
sum++;
for (int i=pos[l]+1;i<=pos[r]-1;i++)
{
int mn=L[i],mx=R[i];
if (b[mn].fi+tag[i]>x) continue;
if (b[mx].fi+tag[i]<=x) {sum+=R[i]-L[i]+1;continue;}
while (mn<mx)
{
int mid=(mn+mx)>>1;
mid++;
if (b[mid].fi+tag[i]<=x) mn=mid;
else mx=mid-1;
}
if (b[mn].fi+tag[i]<=x) sum+=mn-L[i]+1;
}
if (sum<k) return 1;
else return 0;
}
int c[maxn],tot;
int que(int l,int r,int k)
{
if (r-l+1<k || k<1) return -1;
int L=-2e9,R=2e9,ans=0;
while (L<=R)
{
int mid=(L+R) >> 1;
if (chk(l,r,mid,k)) L=mid+1;
else R=mid-1,ans=mid;
}
return ans;
}
signed main()
{
n=read(),m=read();
for (int i=1;i<=n;i++) a[i]=read(),b[i]={a[i],i};
init();
for (int i=1;i<=cnt;i++)
sort(b+L[i],b+R[i]+1);
while (m--)
{
int op=read(),l=read(),r=read(),k=read();
if (op == 2) Upd(l,r,k);
else printf("%lld\n",que(l,r,k));
}
return 0;
}
后话
- 啊啊啊啊啊差点发明了我为啥忘了二分值啊
$\mathscr{msjing}$

浙公网安备 33010602011771号