【loli的胡策】NOIP训练7.20(二分+主席树)

本文解析两道算法挑战赛题目,一是寻找序列中第k小的平均值,二是处理动态序列查询,采用二分查找与静态主席树等高级数据结构解决复杂问题。

T1题目:

问题描述】
有一天, A 得到了一个长度为 n 的序列。
他把这个序列的所有连续子序列都列了出来,并对每一个子序列
都求了其平均值,然后他把这些平均值写在纸上,并对它们进行排序,
最后他报出了第 k 小的平均值。
你要做的就是模仿他的过程。
输入格式】
第一行两个整数 n,k意义如题中所述。
第二行 n 个正整数,即为小 A 得到的序列。
输出格式】
一行一个实数,表示第 k 小的平均值,保留到小数点后 4 位。
样例输入输出】

ave.in

ave.out

6 10
3 5 4 6 1 2

3.6667

数据范围与约定】

对于 40%的数据, n≤1000

对于 100%的数据, n≤100000 k≤n*(n+1)/2序列中的数≤109 

 题解:

40分的暴力挺好想:用优先队列啥的乱搞一下

100:因为k的值很大,我们可以二分一下,最小的平均数是里面的最小数,最大的平均数是里面的最大数,求完mid的时候把每一个数减mid,看看有多少区间<0了,如果是k

个,那恭喜你找到answer了,但如果我们枚举每一个区间,那就前功尽弃了(你会T的),你可以先求个前缀和,然后区间<0的条件是sum[r]<sum[l-1]所以我们只要求逆序对

个数就ok啦

这里特别要注意:k和a[i]的和是long long......心碎......

代码:

#include <cstdio>
#include <queue>
#include <cstring>
#include <iostream>
#define LL long long 
using namespace std;
int n,a[100005];LL ans,k;
double b[100005],c[100005];
const double eps=1e-5;
int maxx=0,minn=1e9+7;
void sishi() 
{
	int i,j;
	priority_queue<double>q;
	for (i=1;i<=n;i++)
	{
	  LL last=0;
	  for (j=i;j<=n;j++)
	  {
	  	last+=a[j]; 
	  	q.push(1.0*last/(j-i+1));
	  }
	}
	k=n*(n+1)/2-k;
	for (i=1;i<=k;i++)  
	  q.pop();
	printf("%.4lf",q.top());
}
void gb(int l,int r)//归并求逆序对 
{
	if (l==r) return;
	int mid=(l+r)>>1,i;
	gb(l,mid); gb(mid+1,r);
	int l1=l,l2=mid+1,ll=0;
	
	while (l1<=mid && l2<=r)
	  if (b[l1]<=b[l2])
	    c[++ll]=b[l1++];
	  else
		{
	      c[++ll]=b[l2++];  ans+=mid-l1+1;
		}		
		  
	while (l1<=mid)
	  c[++ll]=b[l1++];
	while (l2<=r)
	  c[++ll]=b[l2++];
	ll=0;
	for (i=l;i<=r;i++)
	  b[i]=c[++ll]; 
}
LL yibai(double mid)
{
	int i;
	for (i=1;i<=n;i++)
	  b[i]=a[i]-mid+b[i-1];
	ans=0;
	gb(0,n);//这里一定要是0,因为单个区间也可以<0 
	return ans;
}
int main()
{
	freopen("ave.in","r",stdin);
	freopen("ave.out","w",stdout);
	scanf("%d%lld",&n,&k);
	for (int i=1;i<=n;i++) scanf("%d",&a[i]),maxx=max(maxx,a[i]),minn=min(minn,a[i]);
//	sishi();
	double l=minn,r=maxx,mid;
	while (r-l>eps)
	{
		mid=(l+r)/2;
		if (yibai(mid)<k) l=mid;
		else r=mid; 
	}
	printf("%.4lf",l);
}


 T3题目:

A把自己之前得到的序列展示给了小B,不过这一次,他并不要求小B模仿他之前的行为。他给了小B一些询问,每个询问都是l r x的形式,要求小B数出在序列的第l个到第r个元素中有多少是不小于x的。小B很快就算出来了。小A很不甘心,于是要求动态修改这个序列……这样,他只要求每次修改后求出所有询问答案的和即可。然而小B还是算出来了,小A很生气,于是把问题抛给了你。

intput

由于一些原因,本题采取一定的方式加密了修改操作。第一行三个整数nmq,分别表示序列长度、询问个数和修改次数。第二行n个正整数描述序列。接下来m行每行三个

l r x,表示一次询问。最后q行每行两个数pv,表示把p^lastans这个位置上的数修改成v^lastans(其中lastans指上次修改之后的答案,初始即为没有修改过的原序列的询

问答案,^为异或符号,C/C++中为^pascal中为xor)。

数据范围与约定】

对于 20%的数据, n,m,q≤100

对于 40%的数据, n,m,q≤1000

对于 100%的数据, n,m,q≤100000序列中的数(包括修改后的)均为正数且不超过 n保证数据合法。

output

q+1行每行一个整数,第一行表示原序列的所有询问答案之和,后面q行表示每次修改之后的序列的所有询问答案之和。

input

4 2 2

1 4 2 3

2 4 3

1 3 2

6 6

2 7

output

4

3

4

 题解:

我们先考虑如何预处理出最初的答案,我们要查询区间中大于等于x的数的个数,那么应该很容易想到这个可以用静态主席树来求解。将每个位置的数按顺序加入主席树(权值套区间)中,每次都是在前一颗树的基础上更新一条树链,这样的其实每个位置都是维护的前缀和,树的形态相同自然可以用作差的方式求区间和。

对于每一个更改我们考虑对答案的影响。设a>b,如果是把a改成b那么对答案产生的影响就是应该减去x的范围在(b,a]的包含当前位置的询问数,如果是从b改成a,那么对答案的影响就是应该加上x范围在(b,a]的包含当前位置的询问数。这个有关x的区间询问数我们还是可以用静态主席树来维护,我们把x从小到大排序,依次加入x=i(i=1...n)的询问,与上一颗主席树记录的信息不同的一点时,有且只有在当前区间完全包含在[l,r]范围内的时候区间答案才+1,这样在查找一个位置的时候需要将路径上经过的区间的答案累加就能得到答案。在继承上一个节点的信息的时候,因为一个i可能会对应多个询问,所以如果你在加入的时候当前节点不是上一颗树的节点,就不要继承了,直接在这上面累加就好。

代码:




评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值