首页 > 其他分享 >【学习笔记】山东省队第三轮集训

【学习笔记】山东省队第三轮集训

时间:2023-07-16 09:16:07浏览次数:29  
标签:fa int 第三轮 笔记 dep dfn 集训 dp define

Day 2

A.sequence

题目描述:

题目分析:

考虑一个很简单的 \(dp\) 就是设 \(f[i]\) 表示考虑了前 \(i\) 个位置最多可以划分为多少个序列。
转移就是可以直接从 \(f[i-1]\) 继承,或者从 \(j\) 满足 \(\sum_{k=j+1}^{i} c_i = 0\),也就是前缀和相等。
可以发现的是对于从 \(j\) 转移这种方式我们没有必要从所有符合条件的 \(j\) 转移,只需要找一个距离 \(i\) 最近的 \(j\) 即可,证明显然。
所以对于一组询问我们可以直接跑一遍就好了,对于多组询问的话肯定需要把 \(dp\) 搞成 \(dp\) 自动机的形式,即将 \(dp\) 的转移理解为边,\(dp\) 的状态理解为点,这样比较方便观察性质。
但是这样我们会发现我们直接弄的话转移边同时包含取 \(\max\) 和加 \(1\) 操作就很不行,那么考虑这个 \(dp\) 转移本质是什么:选择一个最近的点 \(j\) 使得 \([j,i]\) 存在一个子区间的区间和为 \(0\),然后就直接转移 \(f[j]+1 \to f[i]\)。
这样弄完了我们自动机的转移边就只包含加 \(1\) 操作就很可做了。
稍微总结一下就是设 \(nxt_i = j\) 表示最小的 \(j\) 使得 \([i,j]\) 存在子区间区间和为 \(0\),则连边 \((i,nxt_i+1)\),答案即从 \(l\) 出发向上跳跳到第一个大于 \(r\) 的点的跳跃次数。
因为这个题卡空间,所以直接倍增的话空间就炸了,所以改成树剖加速跳跃的过程就好了。

代码:

(我写挂了,就随便扒一个看上去不错的放在这里了)

点击查看代码
bool M1;
#include<bits/stdc++.h>
//#include<ext/pb_ds/assoc_container.hpp>
//#include<ext/pb_ds/hash_policy.hpp>
//#include<ext/pb_ds/priority_queue.hpp>
#define fi first
#define se second
#define LD double
#define name(x) #x
#define ll long long
#define Vector Point
#define I128 __int128
#define ull unsigned ll
#define pii pair<int,int>
#define pb(x) push_back(x)
#define dek(x) debug(x)<<" "
#define deh(x) debug(x)<<endl
#define syt cerr<<"sytakioi\n"
#define debug(x) cout<<name(x)<<":"<<x
#define F(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define UF(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
#define look_memory cerr<<abs(&M2-&M1)/1024.0/1024<<'\n'
#define rd_i(l,r) uniform_int_distribution<int>(l,r)(rd)
#define rd_r(l,r) uniform_real_distribution<double>(l,r)(rd)
#define look_time cerr<<(clock()-Time)*1.0/CLOCKS_PER_SEC<<'\n'
using namespace std;
//using namespace __gnu_cxx;
mt19937 rd(time(0));
const int N=4e5+5;
int n,m;
ll a[N];
struct edge{int to,next;}e[N];
int link[N],tot;
inline void add(int x,int y){e[++tot]={y,link[x]};link[x]=tot;}
unordered_map<ll,int> id;
int fa[N],dep[N],dfn[N],rk[N],top[N],cnt;
inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
inline void dfs(int u,int fath){
	dfn[u]=1;
	fa[u]=fath;
	dep[u]=dep[fath]+1;
	for(int i=link[u];i;i=e[i].next){
		int v=e[i].to;
		dfs(v,u);
		dfn[u]+=dfn[v];
		if(dfn[v]>dfn[top[u]]) top[u]=v;
	}
}
inline void dfs1(int u,int topf){
	int s=top[u];
	dfn[u]=++cnt;
	rk[cnt]=u;
	top[u]=topf;
	if(s) dfs1(s,topf);
	for(int i=link[u];i;i=e[i].next){
		int v=e[i].to;
		if(v!=s) dfs1(v,v);
	}
}
inline int ask(int x,int y){
	int res=0;
	while(fa[top[x]]<=y) res+=dep[x]-dep[top[x]]+1,x=fa[top[x]];
	int l=dfn[top[x]],r=dfn[x],ans=r+1;
	while(l<=r){
		int mid=(l+r)>>1;
		if(rk[mid]<=y) ans=mid,r=mid-1;
		else l=mid+1;
	}
	return res+dfn[x]-ans+1;
}
inline int query(int l,int r){return a[l]<=r?ask(a[l],r):0;}
bool M2;
int main(){
	int Time=clock();
	look_memory;
	cin.tie(nullptr)->sync_with_stdio(false);
//	freopen("sequence.in","r",stdin);
//	freopen("1.out","w",stdout);
//	freopen("sequence.out","w",stdout);
	cin>>n;
	id[0]=0;
	iota(fa,fa+n+2,0);
	F(i,1,n){
		cin>>a[i];
		a[i]+=a[i-1];
		if(id.count(a[i])) dep[i]=id[a[i]]+1;
		else dep[i]=-1;
		if(~dep[i]) add(dep[i],i);
		else fa[i]=i+1;
		id[a[i]]=i;
	}
	F(i,1,n+1){
		a[i]=find(1);
		for(int j=link[i];j;j=e[j].next) fa[e[j].to]=e[j].to+1;
	}
	tot=0;memset(link,0,sizeof link);
	F(i,1,n) if(dep[i]) add(a[i+1],i);
	dep[n+1]=0;
	dfs(n+1,n+1);dfs1(n+1,n+1);
	cin>>m;
	F(i,1,m){
		int l,r;cin>>l>>r;
		cout<<query(l,r)<<'\n';
	}
	look_time;
	return 0;
}

B.select

题目描述:

题目分析:

可以发现,我们一定可以找到一个点 \(r\) 作为根,使得 \(u,v,w\) 分别位于三棵子树内,所以就考虑枚举 \(r\)。
那么 \(u,v,w\) 一定是某三棵子树内深度最深的叶子节点,设分别为 \(a,b,c(a \le b \le c)\)
那么可以发现 \(f(u,v) = c \times(a + b)\)
证明可以考虑:\(c \times (a + b) = ac + bc = (ac +bc + ab) - ab\)
括号内的项随 \(a,b,c\) 的重排不变,所以就要求 \(ab\) 最小即可。
这样的话第一问就解决了,对于第二问来说就是沿用这种方式。
换根得到子树内最深的叶子和个数,然后分讨 \(dep_u=dep_v\) 和 \(dep_v = dep_w\) 即可。

考场上只会第一问,就给出一种更好想的解决第一问的方式:
我们可以发现的是我们一定存在一种最优方案使得直径的两个端点就是我们选择的三个点之一,因为如果不是的话,我们将某一个点改为直径的端点一定不会变劣。
这样就确定了两个点,对于第三个点我们直接枚举就好了。

标签:fa,int,第三轮,笔记,dep,dfn,集训,dp,define
From: https://www.cnblogs.com/linyihdfj/p/17556922.html

相关文章

  • 你省(福建)省队集训 Day5 T1 题解
    简要题意有两个正整数\(a<b\le10^9\),给出\(\dfrac{a}{b}\)的小数点后\(19\)位,要求还原\(a,b\),保证有解。solution一个科技:\(\texttt{Stern-Brocottree}(SBT)\),可以参考这个博客学习。先给出\(O(n)\)找的代码:......
  • tarjan 学习笔记
    tarjan学习笔记求解强联通分量我们从一个点开始建立dfs树,有如下四种边:树边若\(u\)到\(v\)有边,且满足\(v\)没有被访问过,则这条边为树边返祖边若\(u\)到\(v\)有边,且满足\(v\)已被访问过,则这条边为返祖边横叉边若\(u\)到\(v\)有边,且满足\(u\)和......
  • 《架构整洁之道》学习笔记 Part 1 概述
    本书主题介绍什么是优秀的软件架构,以提高软件架构质量介绍系统架构的各种属性与成本和生产力的关系,以采用好的设计和架构以便减少构建成本好的软件架构可以带来什么?大大节省软件项目构建与维护的人力成本每次变更:改动少,易于实施,不容易出bug用最小的成本,最大程度满足功能......
  • 2023暑假集训杂题
    2023暑假集训杂题解题报告UOJNOIRound#7Day1那些你不要的题目链接题目描述给定长度为\(n\)的序列\(A\),保证\(n\)为奇数,你是先手,每次先手与后手分别取相邻的\(2\)个数,并将剩下的数合并。先手希望最后剩下的数最大,后手希望剩下的数最小,在最优策略下,最后剩下的数是多......
  • Perl学习笔记6_进制转换
    目录1.使用sprintf,printf1.1:10进制->非10进制1.2:非10进制->10进制2.使用函数oct,hex2.1非10进制->10进制1.使用sprintf,printf1.1:10进制->非10进制my$num=10;my$s_hex_low=sprintf"%04x",$num;#000a,10进制->16进制小写my$s_hex_high=sprin......
  • Perl学习笔记5_命令行选项
    目录1.Getopt::Long2.Getopt::Std1.Getopt::Long#使用模块useGetopt::Long;#选项初始值my$length=24;my$file="file.dat";my@run=();my$verbose=0;#处理选项#如果参数解析成功,$result=1,#如果参数解析失败(有未知选项或不符合要求),$result=0......
  • 后缀数组学习笔记
    后缀数组是什么后缀数组就是主要处理字符串后缀问题的,它的实现算法主要有两种:倍增法和DC3,复杂度分别是\(O(n\logn)\)和\(O(n)\)。这里由于DC3代码答辩且难以理解,我就只写了倍增法的实现。例题引入P3809【模板】后缀排序题目大意读入一个长度为\(n\)的由大小写英文......
  • Perl学习笔记3_条件语句循环
    1.条件语句:if(boolean_expr0){#expr0为true时执行}elsif(boolean_expr1){#expr1为true时执行}else{#没条件匹配时执行}unless(boolean_expr0){#expr0为false时执行}elsif(boolean_expr1){#expr1为true时执行}else{#没......
  • Perl学习笔记4_命令行运行perl语句
    命令行选项例子:catfile.txt|perl-ne'$a+=s/pattern//g;END{print"$a\n"}'作用:计算文件file.txt中匹配“pattern”的个数。解释:1.cat显示文件内容,通过管道将内容送给perl程序处理;如果使用perl-e''file.txt的方式,file.txt将会被修改。使用管道,可以保证原文件......
  • 2023ACM暑期集训 DAY 1
    目前进度——动态规划1:线性dp、背包问题,区间好题1003可爱の星空标签递推分治思路记\(dp_i\)表示将\(i\)颗点合并为一个连通块所需的最小代价。根据贪心思想:若目前的总点数\(n\)为偶数,则\(dp_n=2*dp_{\frac{n}{2}}\);若目前的总点数\(n\)为奇数,则\(dp_n=dp_{[\fr......