您的位置:首页 > 其它

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

2017-07-20 16:59 253 查看
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可能会对应多个询问,所以如果你在加入的时候当前节点不是上一颗树的节点,就不要继承了,直接在这上面累加就好。
代码:
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: