2021.10.04pm

10.04PM

预期 实际
A 0 0
B 0 0
C 0 0
D 0 0
S 0 0

属于是爆零了。
策略上出大问题,先做的D····。
难度我觉得有点不太均衡,前三道难度低了一些,但整体感觉还不错。

A Luogu P4552 [Poetize6] IncDec Sequence \(\blacktriangle\)

  1. 区间修改,容易想到差分。由于又可以加又可以减,显然最优情况是找差分数组中的一个正数和一个负数进行抵消。进一步就是用所有的正数和负数之和进行抵消,但很有可能有剩余,这时候选择头或者尾进行修改,这样能得到绝对值之差加一种不同的序列。(\(b_1\) 不一样,而其它都是0)

B 绿豆蛙的归宿进阶(逆元)\(\blacktriangle\)

  1. 可以正推,也可以逆推。
  2. 正推是长度*概率,逆推直接就是期望。
  3. 对于逆推,显然有\(ex[u]=\sum (ex[v]+edge[u\to v])/out[u]\)
  4. 同时由于是有向无环图,为了避免重复访问,自然拓扑排序。

C 乒乓 \(\blacktriangle\!\blacktriangledown\)

题意:

选取乒乓球台的任意一个平面,打出了n个球,初始速度分别为 ,为一个二维向量\((x_i,y_i)\) ,定义AK指数为:

\[AK(l,r)=\sum_{1 \le i < j \le r}(x_iy_j-x_jy_i)^2 \]

要求支持两种操作:点修改,查询AK

题解:

  1. \(AK(l,r)=\sum_{1 \le i < j \le r}(x_i^2y_j^2+x_j^2y_i^2-2*x_iy_ix_jy_j)\)

  2. 再稍作修改,\(AK(l,r)=\sum_{i=l}^rx_i*\sum_{j=l}^ry_j-\sum_{i=l}^rx_i^2y_i^2-(\sum_{i=l}^rx_iy_i*\sum_{j=l}^rx_jy_j-\sum_{i=l}^rx_i^2y_i^2)\)

  3. \(AK(l,r)=\sum_{i=l}^rx_i*\sum_{j=l}^ry_j-\sum_{i=l}^rx_iy_i*\sum_{j=l}^rx_jy_j\)

  4. 维护三个支持单点修改区间查询的数据结构(树状数组)。

D 阿尔塔拉 \(\blacktriangle\!\blacktriangledown\!\blacktriangle\!\blacktriangledown\)

题意:

一棵树,1为父亲节点,支持查找两点之间是否有值大于K的点,有多个则输出编号最小的点,同时支持从节点到根区间修改1。 \(m\) 次查询,每次输出\(Ask(x,y)\),并 \(add(y)\)

题解

  1. 这道题第一直觉肯定是树剖,但是 \(O(n\log_n^2)\) 会被卡(更别说\(O(n \sqrt n)\)),强制要求 \(O(n\log n)\)
  2. \(P.S.\) 这个程序是在阅读”非常潮“的标程之后写出的。
  3. 有这样一个性质:如果一个点的值大于K,它的父亲的值也肯定大于K,满足递增性。同时,又可以用 \(ST\) 维护区间最小编号,\(O(1)\) 查询。
  4. 而一个点的值大于K,必然是儿子的操作次数大于K,用树状数组维护前缀和,查询子树大小。
  5. 但这样时间复杂度还是有问题。我们需要在 \(add\) 的同时更新子树。而从下往上修改复杂度还是 \(\log\) 显然该T还是T,得要从第一个没有破坏的点往下更新。这需要提前找到每个点的儿子,以找到从根到该点的路径。在 dfn 序中,显然有 \(dfn[son_1]<dfn[son_{{son}_1}]<dfn[son_2]\),所以可以二分来查找,同时打上标记,这样时间复杂度是均摊 \(O(n\log n)\) 且和询问次数没有多大关系。
  6. 以下是连zyc都能看懂的AC代码(笑死,stdT掉了)
#include<cstdio>
#include<string>
#include<cstring>
#include<algorithm>
#define N 300010
using namespace std;
int n,m,k,last,cnt,tot;
struct u2003{//edge
	int b,nx;
}e[2*N];
int head[N];//建边 
int dep[N],deg[N];//深度,度数 
int son[N],name[N];//动态开数组,vector又丑又慢,name[i]->i在son中的头指针
int dfn[N],out[N];//dfn序,出栈序
int f[22][N];//倍增父亲
int minn[22][N];//倍增区间最小编号 
int bit[N];//树状数组
bool over[N];//判断破坏值是否大于k

inline void read(int &x){//快读 
	x=0;char c=getchar();
	for(;!isdigit(c);c=getchar());
	for(; isdigit(c);c=getchar())x*=10,x+=c-'0';
}

inline void build(int a,int b){//建边 
	e[++last].b=b;
	e[last].nx=head[a];
	head[a]=last;
}

inline void dfs(int x,int fa){//O(n)
	deg[x]--;//提前减去父亲的一个位置,剩下的是出度,即儿子个数 
	dfn[x]=++tot;//dfn序 
	f[0][x]=fa;//x的第2^0个父亲节点为fa 
	dep[x]=dep[fa]+1;//深度 
	int p=cnt;
	name[x]=p+1;//头指针,尾指针可以用name[x]+deg[x]-1表示 
	cnt+=deg[x];//可以改成vector,但是太难受了
	for(int i=head[x];i;i=e[i].nx){
		int y=e[i].b;
		if(y==fa)continue;
		son[++p]=y;//标记有一个儿子为y 
		dfs(y,x);
	}
	out[x]=tot;//出栈序,改成求子树大小size也行 
}

inline int find(int x){//找到最深的没被破坏的点,O(log n)
	if(!over[1])return 1;//小优化,不要也罢,加了更慢(wu) 
	for(int i=20;i>=0;i--)//倍增找第一个没被破坏的点 
		if(f[i][x]&&!over[f[i][x]])//存在且没被破坏 
			x=f[i][x];
	return x;
}
inline int path(int x,int y){//可以理解为O(log n)倍增找LCA同时找答案
	//由于我平时都写的是ST,所以这个地方借鉴std比较多 
	if(dep[x]<dep[y])
		swap(x,y);//交换 
	int dlt=dep[x]-dep[y];
	int ret=n+1;//显然是最坏答案 
	for(int i=0;dlt;i++){//位运算可能更快? 
		if(dlt&1){
			ret=min(ret,minn[i][x]);//更同时新答案
			x=f[i][x];
		}
		dlt>>=1;
	}
	ret=min(ret,x);//比较最后一个可能的点
	if(x==y)return ret;//两点相同,找到LCA,直接返回(不加也行)
	for(int i=20;i>=0;i--){//O(log n)
		int u=f[i][x],v=f[i][y];
		if(u!=v){//找到LCA,一开始由于不了解倍增求LCA写挂了··· 
			ret=min(ret,min(minn[i][x],minn[i][y])); 
			x=u;y=v;//同时向上跳
		}
	}
	return min(min(ret,f[0][x]),min(x,y));//f[0][x]即LCA 
}
inline void bit_add(int x){//树状数组修改O(log n)
	for(;x<=n;x+=x&-x)bit[x]++;
}
inline int bit_ask(int x){//树状数组查询O(log n)
	int ret=0;
	for(;x;x-=x&-x)ret+=bit[x];
	return ret;
}
inline int ask(int x,int y){
	if(!over[1])return 0;//小优化,不加也行 
	x=find(x);y=find(y);//51行 找到最深的没被破坏的点 
	if(!over[x]&&x==y)return 0;//两个点的LCA以上才有被破坏的点,那么显然没有破点 
	return path(over[x]?x:f[0][x],over[y]?y:f[0][y]);//可能x,y自己就已经超过K了,find只能找到自己 
}
inline int find_son(int x,int v){
	if(v==x)return x;//一个小优化,不影响正确性 
	int l=name[x],r=name[x]+deg[x]-1;//找到记录x的儿子的区间 
	while(l<=r){//找到第一个比自己小的,二分可以按自己的写···他们都说我的丑 
		int mid=(l+r)/2;
		if(dfn[son[mid]]>dfn[v])r=mid-1;//各个儿子根据搜索序加入,显然dfn递增,具有二分性 
		else l=mid+1;
	}
	return son[l-1];
}
inline void add(int x){
	bit_add(dfn[x]);//82行 树状数组修改 
	int y=find(x);//51行 找到最上面的没有被破坏的点,看是否能把它修改成已破坏 
	while(bit_ask(out[y])/*85行*/-bit_ask(dfn[y]-1)>k){//子树内修改次数大于k,则显然y的修改次数大于k 
		over[y]=1;
		if(y==x)break;//只可能x及以上需要修改 
		y=find_son(y,x);//96行 按照y->x的路径走 
	}//由于每个点都只会标记一次,所以是均摊O(n log n),和m没啥关系 
}
inline void scan(){//输入O(n)
	read(n);read(m);read(k);
	int a,b;
	for(register int i=1;i<n;i++){
		read(a);read(b);
		build(a,b);build(b,a);//建边 
		deg[a]++;deg[b]++;//练的边数 
	}
}
inline void pre(){//O(n log n)
	deg[1]++;//1号节点没有父亲 
	dfs(1,0);//32行 
	for(register int i=1;i<=n;i++)minn[0][i]=i;//初始化自己向上的最小值,到自己最小为自己 
	for(register int i=1;i<=20;i++)//O(log n) 
		for(register int j=1;j<=n;j++){
			f[i][j]=f[i-1][f[i-1][j]];//倍增父亲 
			minn[i][j]=min(minn[i-1][j],minn[i-1][f[i-1][j]]);
		}
}
inline void rescan(){//O (m log n)
	int x,y;
	for(register int i=0;i<m;i++){
		read(x);read(y);
		int ret=ask(x,y);//88行 O(log n)
		if(ret==0)puts("NO");
		else printf("%d\n",ret);
		if(i>k)over[1]=1;//只有i>k时才可能成立,垃圾优化,不要也行 
		add(y);//104行 O(log n)
		
	}
}
int main(){
	//freopen("alltale.in","r",stdin);
	//freopen("alltale.out","w",stdout);
	scan();//113行 
	pre();//122行 
	rescan();//132行 
	return 0;
}

image

\(\cal {Made} \ {by} \ {YuGe}\)

posted @ 2021-10-05 13:00  u2003  阅读(75)  评论(0)    收藏  举报