title: 3月3日考试总结
data: 2018-3-3 20:18:40
tags:
- 线段树
- 图论
- 最短路
- Floyd
- 二分答案
- 倍增
- 贪心
description: 第一道题目是线段树维护区间和区间平方和支持区间修改;第二道题目是用一条边连接两个联通块使得联通块的直径最小;第三道题是二份答案加机智处理.
Luogu P1471 方差
题目背景
滚粗了的HansBug在收拾旧数学书,然而他发现了什么奇妙的东西。
题目描述
蒟蒻HansBug在一本数学书里面发现了一个神奇的数列,包含N个实数。他想算算这个数列的平均数和方差。
输入输出格式
输入格式:
第一行包含两个正整数N、M,分别表示数列中实数的个数和操作的个数。第二行包含N个实数,其中第i个实数表示数列的第i项。
接下来M行,每行为一条操作,格式为以下两种之一:
- 操作1:1 x y k ,表示将第x到第y项每项加上k,k为一实数。
- 操作2:2 x y ,表示求出第x到第y项这一子数列的平均数。
- 操作3:3 x y ,表示求出第x到第y项这一子数列的方差。
输出格式:
输出包含若干行,每行为一个实数,即依次为每一次操作2或操作3所得的结果(所有结果四舍五入保留4位小数)。
输入输出样例
输入样例#1:
5 5
1 5 4 2 3
2 1 4
3 1 5
1 1 1 1
1 2 2 -1
3 1 5
输出样例#1:
3.0000
2.0000
0.8000
说明
样例说明:
数据规模:
做法:
首先区间平均数是很容易维护的, 用线段树或者分块维护区间和除去区间大小就是区间平均数.区间加也是线段树的常见操作.
考虑如何维护方差, 我们观察到方差的式子不仅和元素有关还和区间平均数有关, 这样就没办法在一个很快的时间内处理出来, 可以试着将其化简.
\[
\begin{align}
S^2=&\frac{1}{r-l+1}\sum_{i=l}^r|A_i-\bar{A}|^2\=&\frac{1}{r-l+1}\sum_{i=l}^r(A_i-\bar{A})^2\=&\frac{1}{r-l+1}(\sum_{i=l}^rA_i^2-2A_i\bar{A}+\bar{A}^2)\\end{align}
\]
我们观察到这个式子只和\(\sum\limits_{i=l}^rA_i^2,\sum\limits_{i=l}^rA_i,\bar{A}\)有关, 所以可以直接维护区间平方和和区间和, 在根据其计算出方差即可.
还需要考虑在区间加下维护区间平方和的问题,仍然推公式.
\[
\begin{align}
\sum_{i=l}^r(A_i+\Delta A)^2=&\sum_{i=l}^r(A_i^2+2A_i\Delta A+\Delta A^2)\\end{align}
\]
Code
// luogu-judger-enable-o2
#include<algorithm>
#include<iostream>
#include<cstdio>
#define N 100005
using namespace std;
namespace Input{
inline void read(int &s){
char ch=getchar();int f=1;
for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
for(s=0;isdigit(ch);s=s*10+ch-'0',ch=getchar());
s*=f;
}
void read(double &s){
cin>>s;
}
};using namespace Input;
struct Node{
double data,flag,pingfang;
int l,r;
}t[N<<2];
double merge(int l,int r){
return t[l].data+t[r].data;
}
void pushdown(int ret){
if(!t[ret].flag)return ;
int l=t[ret].l,r=t[ret].r;
int mid=(l+r)>>1;
t[ret<<1].flag+=t[ret].flag;
t[ret<<1|1].flag+=t[ret].flag;
t[ret<<1].pingfang+=(mid-l+1)*t[ret].flag*t[ret].flag+2*t[ret<<1].data*t[ret].flag;
t[ret<<1].data+=(mid-l+1)*t[ret].flag;
t[ret<<1|1].pingfang+=(r-mid)*t[ret].flag*t[ret].flag+2*t[ret<<1|1].data*t[ret].flag;
t[ret<<1|1].data+=(r-mid)*t[ret].flag;
t[ret].flag=0;
}
void Build(int l,int r,int ret){
t[ret]=(Node){0.0,0.0,0.0,l,r};
if(l==r){
read(t[ret].data);
t[ret].pingfang=t[ret].data*t[ret].data;
return ;
}
int mid=(l+r)>>1;
Build(l,mid,ret<<1);
Build(mid+1,r,ret<<1|1);
t[ret].data=merge(ret<<1,ret<<1|1);
t[ret].pingfang=t[ret<<1].pingfang+t[ret<<1|1].pingfang;
}
void Addition(int L,int R,int ret,double delta){
int l=t[ret].l,r=t[ret].r;
if(l>=L&&r<=R){
t[ret].flag+=delta;
t[ret].pingfang+=(r-l+1)*delta*delta+2*t[ret].data*delta;
t[ret].data+=delta*(r-l+1);
return;
}
int mid=(l+r)>>1;
pushdown(ret);
if(L<=mid)Addition(L,R,ret<<1,delta);
if(R>mid)Addition(L,R,ret<<1|1,delta);
t[ret].data=merge(ret<<1,ret<<1|1);
t[ret].pingfang=t[ret<<1].pingfang+t[ret<<1|1].pingfang;
}
double Query(int L,int R,int ret){
int l=t[ret].l,r=t[ret].r;
if(l>=L&&r<=R)
return t[ret].data;
double ans=0;
pushdown(ret);
int mid=(l+r)>>1;
if(L<=mid)ans+=Query(L,R,ret<<1);
if(R>mid)ans+=Query(L,R,ret<<1|1);
return ans;
}
double QueryF(int L,int R,int ret){
int l=t[ret].l,r=t[ret].r;
if(l>=L&&r<=R)
return t[ret].pingfang;
double ans=0;
pushdown(ret);
int mid=(l+r)>>1;
if(L<=mid)ans+=QueryF(L,R,ret<<1);
if(R>mid)ans+=QueryF(L,R,ret<<1|1);
return ans;
}
double pingjunshu(int l,int r){
return (double)Query(l,r,1)/(double)(r-l+1);
}
double fangcha(int l,int r){
double sigma=Query(l,r,1);
double truepj=sigma/(r-l+1);
return (QueryF(l,r,1)-2*truepj*sigma+truepj*sigma)/(r-l+1);
}
int main(){
int n,m;
int apt;
int a,b;
double delta;
read(n);read(m);
Build(1,n,1);
for(int i=1;i<=m;++i){
read(apt);read(a);read(b);
if(apt==1){
read(delta);
Addition(a,b,1,delta);
}
else if(apt==2){
printf("%.4lf\n",pingjunshu(a,b));
}
else {
printf("%.4lf\n",fangcha(a,b));
}
}
return 0;
}
Luogu P1522 牛的旅行 Cow Tours
题目描述
农民 John的农场里有很多牧区。有的路径连接一些特定的牧区。一片所有连通的牧区称为一个牧场。但是就目前而言,你能看到至少有两个牧区通过任何路径都不连通。这样,Farmer John就有多个牧场了。
John想在牧场里添加一条路径(注意,恰好一条)。对这条路径有以下限制:
一个牧场的直径就是牧场中最远的两个牧区的距离(本题中所提到的所有距离指的都是最短的距离)。考虑如下的有5个牧区的牧场,牧区用“*”表示,路径用直线表示。每一个牧区都有自己的坐标:
(15,15) (20,15)
D E
*-------*
| _/|
| _/ |
| _/ |
|/ |
*--------*-------*
A B C
(10,10) (15,10) (20,10)
【请将以上图符复制到记事本中以求更好的观看效果,下同】
这个牧场的直径大约是12.07106, 最远的两个牧区是A和E,它们之间的最短路径是A-B-E。
这里是另一个牧场:
*F(30,15)
/
_/
_/
/
*------*
G H
(25,10) (30,10)
在目前的情景中,他刚好有两个牧场。John将会在两个牧场中各选一个牧区,然后用一条路径连起来,使得连通后这个新的更大的牧场有最小的直径。
注意,如果两条路径中途相交,我们不认为它们是连通的。只有两条路径在同一个牧区相交,我们才认为它们是连通的。
输入文件包括牧区、它们各自的坐标,还有一个如下的对称邻接矩阵:
A B C D E F G H
A 0 1 0 0 0 0 0 0
B 1 0 1 1 1 0 0 0
C 0 1 0 0 1 0 0 0
D 0 1 0 0 1 0 0 0
E 0 1 1 1 0 0 0 0
F 0 0 0 0 0 0 1 0
G 0 0 0 0 0 1 0 1
H 0 0 0 0 0 0 1 0
其他邻接表中可能直接使用行列而不使用字母来表示每一个牧区。输入数据中不包括牧区的名字。
输入文件至少包括两个不连通的牧区。
请编程找出一条连接两个不同牧场的路径,使得连上这条路径后,这个更大的新牧场有最小的直径。输出在所有牧场中最小的可能的直径。
输入输出格式
输入格式:
第1行: 一个整数N (1 <= N <= 150), 表示牧区数
第2到N+1行: 每行两个整数X,Y (0 <= X ,Y<= 100000), 表示N个牧区的坐标。注意每个 牧区的坐标都是不一样的。
第N+2行到第2*N+1行: 每行包括N个数字(0或1) 表示如上文描述的对称邻接矩阵。
输出格式:
只有一行,包括一个实数,表示所求直径。数字保留六位小数。
只需要打到小数点后六位即可,不要做任何特别的四舍五入处理。
输入输出样例
输入样例#1:
8
10 10
15 10
20 10
15 15
20 15
30 15
25 10
30 10
01000000
10111000
01001000
01001000
01110000
00000010
00000101
00000010
输出样例#1:
22.071068
说明
翻译来自NOCOW
USACO 2.4
做法:
要求在两个任意联通块之间连上一条边, 让其直径最短.所以就可以依次枚举两个联通块并枚举它们的任意两点连上一条边,求出直径更新答案即可.
要快速求出连边之后联通块的直径,只需要求出两个联通块到达所连边的点的最大距离即可, 注意直径不一定通过所连接的边,所以直径可能会是某个其它联通块的直径, 再加上连边的长度就是直径.至于如何求出到达一个点的最大距离, 用最短路处理即可,这里用Floyd
算法.
为什么这样做复杂度是正确的?我们观察到枚举连边需要枚举两个联通块并枚举其中的边, 但是图的总点数是\(n\leq 150\), 而不会枚举一个点多次, 所以这一步的复杂度是\(O(n^2)\),Floyd
的算法复杂度是\(O(n^3)0\), 在加上处理联通块的深搜\(O(n)\),总复杂度是\(O(n^3)\).
Code
// luogu-judger-enable-o2
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define N 155
using namespace std;
double dis[N][N];
int here[N][N];
int map[N][N];
int x[N],y[N];
double lon[N];
int color;
int be[N];
int n;
double distan(int i,int j){
if(i==j)return 0.000;
return sqrt((double)(x[i]-x[j])*(double)(x[i]-x[j])+(double)(y[i]-y[j])*(double)(y[i]-y[j]));
}
void dfs(int x,int c){
be[x]=c;here[c][++here[c][0]]=x;
for(int i=1;i<=n;++i)
if(!be[i]&&map[i][x])
dfs(i,c);
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;++i)
scanf("%d%d",&x[i],&y[i]);
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j){
char ch=getchar();
while(!isdigit(ch))ch=getchar();
map[i][j]=(ch=='1'?true:false);
if(map[i][j])dis[i][j]=distan(i,j);
else dis[i][j]=0x3f3f3f3f;
}
for(int i=1;i<=n;++i)
dis[i][i]=0;
for(int i=1;i<=n;++i)
if(!be[i])
dfs(i,++color);
for(int k=1;k<=n;k++)
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(dis[i][k]+dis[k][j]<dis[i][j])
dis[i][j]=dis[i][k]+dis[k][j];
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j)
if(dis[i][j]!=0x3f3f3f3f){
lon[i]=max(lon[i],dis[i][j]);
lon[j]=max(lon[j],dis[i][j]);
}
double ans=123456789000;
for(int i=1;i<=color;++i)
for(int j=1;j<=color;++j){
if(i==j)continue;
for(int k=1;k<=here[i][0];++k)
for(int l=1;l<=here[j][0];++l){
double o=0.0;
o=max(o,lon[here[i][k]]+lon[here[j][l]]+distan(here[j][l],here[i][k]));
if(o<ans)ans=o;
}
}
for(int i=1;i<=n;++i)
ans=max(ans,lon[i]);
printf("%.6f",ans);
return 0;
}
Logu P1084 疫情控制
题目描述
H 国有 n 个城市,这 n 个城市用 n-1 条双向道路相互连通构成一棵树,1 号城市是首都,也是树中的根节点。
H 国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,首都是不能建立检查点的。
现在,在 H 国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。
请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。
输入输出格式
输入格式:
第一行一个整数 n,表示城市个数。
接下来的 n-1 行,每行 3 个整数,u、v、w,每两个整数之间用一个空格隔开,表示从城市 u 到城市 v 有一条长为 w 的道路。数据保证输入的是一棵树,且根节点编号为 1。
接下来一行一个整数 m,表示军队个数。
接下来一行 m 个整数,每两个整数之间用一个空格隔开,分别表示这 m 个军队所驻扎的城市的编号。
输出格式:
共一行,包含一个整数,表示控制疫情所需要的最少时间。如果无法控制疫情则输出-1。
输入输出样例
输入样例#1:
4
1 2 1
1 3 2
3 4 3
2
2 2
输出样例#1:
3
说明
【输入输出样例说明】
第一支军队在 2 号点设立检查点,第二支军队从 2 号点移动到 3 号点设立检查点,所需时间为 3 个小时。
【数据范围】
保证军队不会驻扎在首都。
对于 20%的数据,\(2≤ n≤ 10\);
对于 40%的数据,\(2 ≤n≤50,0<w <10^5\);
对于 60%的数据,\(2 ≤ n≤1000,0<w <10^6\);
对于 80%的数据,\(2 ≤ n≤10,000\);
对于 100%的数据,\(2≤m≤n≤50,000,0<w <10^9\)。
NOIP 2012 提高组 第二天 第三题
做法:
这道题相对来说是这三道题中最难的,但是一旦掌握了一些方法和技巧就能轻松解决此题.
首先要求所需时间最少, 我们可以联想到二分答案, 二分所需时间, 只需要判断时间内能不能满足即可, 需要\(O(\log n)\)次判断.
我们还知道, 因为结点是同步移动的, 而军队越往上走覆盖的结点越多, 所以在不超过时间的情况下, 将结点尽可能的往上移动, 跳到根结点为止, 当然一个一个移动还是太慢了, 所以可以用倍增来加速.
在结点不能再跳跃时, 必定有一些结点还能再往其它根结点的子树跳跃, 也必定有一些结点没有被覆盖, 所以我们利用这些还能跳跃的节点进行覆盖, 根据显然的贪心策略, 选出剩余时间最多的军队和距离根节点最远的结点进行配对是最优的.