//https://img2018.cnblogs.com/blog/1646268/201908/1646268-20190806114008215-138720377.jpg

qbxt做题记录

一些题目

有空的时候来补上。

洛谷P4550

首先看一下这道期望题(期望是个什么勾八)。
这里我偷懒把别人的题解搬过来了原题解(当然改了一下)
设我们最后买了\(x\)张邮票,那么答案就应该是

\[\begin{matrix}\sum_{i=1}^x i\end{matrix}=\frac{x^2+x}{2} \]

所以我们需要分别维护\(x\)的期望和\(x^2\)的期望
我们用\(f[i]\)表示买了\(i\)种不同的邮票的次数的期望,\(g[i]\)表示买了\(i\)种不同的邮票的次数的平方的期望
显然,\(f[0]=g[0]=0\)
考虑使用\(f[i]\)转移\(f[i+1]\)
我们有\(\frac{i}{n}\)的可能买到一个之前买过的邮票,那么此时次数应该\(+1\),同时我们还有\(\frac{n-i}{n}\)的可能买到一个之前没有买过的邮票,那么应该是上一次的次数\(+1\),也就是说,我们可以得到这样的一个等式

\[f[i+1]=\frac{i}{n}(f[i+1]+1)+\frac{n-i}{n}(f[i]+1) \]

拆开之后得到

\[f[i+1]=\frac{i}{n}+\frac{i\times f[i+1]}{n}+\frac{(n-i)\times f[i]}{n}+\frac{n-i}{n} \]

合并同类项可以得到

\[f[i+1]=\frac{i+i\times f[i+1]+n\times f[i]-i\times f[i]+n-i}{n} \]

然后得到

\[f[i+1]=\frac{i\times f[i+1]-i\times f[i]}{n}+f[i]+1 \]

因为\(f[i+1]-f[i]=1\)所以

\[f[i+1]=\frac{i}{n-i}+f[i]+1 \]

在转移\(g[i+1]\)的时候,类比转移\(f\)的时候的等式,我们可以得到
(这一块太多了不想完善了)

\[g[i+1]=\frac{i}{n}(f[i+1]+1)^2+\frac{n-i}{n}(f[i]+1)^2 \]

利用完全平方公式展开

\[g[i+1]=\frac{i}{n}(g[i+1]+2f[i+1]+1)+\frac{n-i}{n}(g[i]+2f[i]+1) \]

化简得

\[g[i+1]=\frac{2i}{n-i}f[i+1]+\frac{i}{n-i}+g[i]+2f[i]+1 \]

我们从\(0\)\(n-1\)进行递推就可以得到最后的答案了

#include<bits/stdc++.h>
#define N 100010
using namespace std;
int n;
double f[N],g[N];
int main()
{
	cin>>n;
	f[0]=g[0]=0;//边界(虽然没什么用 
	for(int i=0;i<n;i++)
	{
		f[i+1]=1.0*i/(n-i)+f[i]+1;//根据推出的式子来计算 
		g[i+1]=2.0*i/(n-i)*f[i+1]+1.0*i/(n-i)+g[i]+2*f[i]+1;
	}
	printf("%.2lf\n",(f[n]+g[n])/2);//保留两位小数输出 
	return 0;//好习惯 
}

https://www.luogu.com.cn/problem/P4206
https://www.luogu.com.cn/problem/P1850
https://www.luogu.com.cn/problem/P3205
https://www.luogu.com.cn/problem/SP2829

洛谷P2051

恶心的 DP 题。
首先看到题目很容易就可以想出用 \(f[i][j]\) 表示前\(i\)\(j\)列的方案数,但他好像不好转移,所以换一种思考方式。
我们用 \(f[i][j][k]\) 来表示前 \(i\) 行有 \(j\) 列放了一个炮,\(k\) 列放了两个炮。
需要用到的函数:

inline int C(int aa)//求组合的函数 
{return (aa*(aa-1)/2)%P;}

然后开始分类讨论:

  1. 当前行什么都不放
    \(f[i][j][k]=f[i][j][k]+f[i-1][j][k]\);
  2. 当前行放在有一个炮的一列里放一个炮,\(j+1\) 是当前炮能放到有一个炮的列中的方案数
    \(f[i][j][k]=f[i][j][k]+f[i-1][j+1][k-1]\times (j+1)\)
  3. 当前行放一个炮在空列里面,很容易就可以想到空列数量为\(m-(j-1)-k\)
    \(f[i][j][k]=f[i][j][k]+f[i-1][j-1][k]\times (m-j-k+1)\)
  4. 当前行放两个炮在空列里面,需要用到排列组合的知识:\(C_{2}^{a}\),a表示当前空列数量也就是\(m-(j-2)-k\)
    \(f[i][j][k]=f[i][j][k]+f[i][j-2][k]\times C(m-j-k+2)\)
  5. 当前行放一个炮在有一个炮的列里,放一个炮在空列里
    \(f[i][j][k]=f[i][j][k]+f[i][j][k-1]\times j \times (m-j-k+1)\)
  6. 当前行放两个炮在有一个炮的列里
    \(f[i][j][k]=f[i][j][k]+f[i][j+2][k-2]\times C(j+2)\)
    最后就是别忘了取模。

code

#include<bits/stdc++.h>
#define int long long//long long。。。 
#define P 9999973
using namespace std;
int n,m,f[110][110][110],ans;//表示前i行有j列只有一个炮,有k列有两个炮 
inline int C(int aa)//求排列的函数 
{return (aa*(aa-1)/2)%P;}
signed main()
{
	cin>>n>>m;
	f[0][0][0]=1;
	for(int i=1;i<=n;i++)
	  for(int j=0;j<=m;j++)
		for(int k=0;k+j<=2*i&&j+k<=m;k++)//边界条件,不能超过i的两倍因为一行最多有俩炮,加起来不能超过m因为最多只有m列 
		{
			f[i][j][k]=(f[i][j][k]+f[i-1][j][k])%P;//不放 
			if(j>=1)f[i][j][k]=(f[i][j][k]+f[i-1][j-1][k]*(m-j-k+1))%P;//在空列中放一个炮 
			if(k>=1)f[i][j][k]=(f[i][j][k]+f[i-1][j+1][k-1]*(j+1))%P;//在j列里放一个炮 
			if(j>=2)f[i][j][k]=(f[i][j][k]+f[i-1][j-2][k]*C(m-j-k+2))%P;//在空列里放两个炮 
			if(k>=1)f[i][j][k]=(f[i][j][k]+f[i-1][j][k-1]*j*(m-j-k+1))%P;//j里放一个炮,空列里放一个炮
			if(k>=2)f[i][j][k]=(f[i][j][k]+f[i-1][j+2][k-2]*C(j+2))%P;//j里放两个炮 
		}
	for(int j=0;j<=m;j++)//枚举每一个只有一个炮的列 
	  for(int k=0;j+k<=2*n&&j+k<=m;k++)//枚举每一个只有两个炮的列 
	    ans=(ans+f[n][j][k])%P;//累加答案取模 
	cout<<ans<<endl;//输出 
	return 0;//好习惯 
}

CF837D
P2458
CF490F
P4316
https://www.luogu.com.cn/problem/CF1120D
https://www.luogu.com.cn/problem/CF160D
https://www.luogu.com.cn/problem/P2423
https://www.luogu.com.cn/problem/P4782
CF1253F
本题需要以下知识点

  1. 求单源最短路
  2. kruskal 算法
  3. 倍增求LCA(用于求树上两点距离)
    首先可以知道有 \(k\) 个充电点,我们如果要保证电池容量尽可能的小的话就要尽量走当前点到另一个充电点的距离尽量小的那一条路,然后我们就要先预处理出每一个点到离他最近的充电站的距离,这样的话肯定是不能每一个点跑一遍迪杰斯特拉,那样就对不起他紫的标签了;所以我们来考虑一下该如何预处理能快,我们可以发现当前的充电点是 \(1\)~\(k\),从哪一个点开始都不能够满足我们的需求,最简单的方式就是我们把这 \(k\) 个点向一个源点连一条边权为 \(0\) 的边,此时跑一个最短路就可以完美的处理出所有的点到离他最近的充电点的距离了。为什么一定要连充电点呢?思考一下,如果是连其他点的话,那样会导致不知道哪一个是充电点,这样就很难处理,因为除充电点以外都可以看作是一个起点,我们从充电点这个终点开始走的话,那么无论走到哪里都是起点,这样就可以完美解决 TLE 的痛苦。
    预处理完之后就要进行边权的更新,因为我们已经处理出了 \(dis\) 数组(存放每一个点到最近充电点的距离),设我们当前剩余的电池电量是 \(x\),那么我们可以得出一个结论就是当前从起点到终点上的点的 \(dis[i]\) 值要小于等于 \(x\),设我们要求的电池容量为 \(c\) 那么可以得出这个式子 \(dis[i]\le x\le c\),走到下一个充电点至少要走 \(dis[i]\),所以得出 \(dis[i]\le x\le c-dis[i]\),设下一个点是 \(j\),那么走到点 \(j\) 至少就要 \(w_{ij}\),所以可以得到 \(c - dis[j] \ge x - w_{i,j} \ge dis[j]\),结合一下可以得到 \(dis[j] \le x - w_{i,j}\),也就是 \(dis[j] \le c - dis[i] - w_{i,j}\),交换一下位置得到 \(c \ge dis[i] + dis[j] + w_{i,j}\),所以我们就可以把边权改成他。
    接下来来考虑如何进行最小生成树:这已经很显而易见了,把之前的边权已更新然后直接跑最小生成树。
    然后就是倍增求 LCA 了,这个有一点不同的是我们要用一个跟倍增数组一样的维护最大值的数组,这样就可以在求两点距离的时候处理出最大值然后直接输出即可。
    为什么存副本只存单边?因为后面建最小生成树的时候会建两条,所以没有必要去存正反两边,费时又费空间。
    对了一定不要在 dfs 的里面进行 \(init\) 函数,会 TLE。
#include<bits/stdc++.h>
#define re register 
#define bug cout<<"WTF?"<<'\n'
#define int long long
#define N 1000100
using namespace std;
struct sb{int u,v,w,next;}e[N],e1[N],e2[N];//e存放一开始输入的边,e1存放一开始的边的一个副本(单边),e2存放生成的最小生成树的边 
int head1[N],cnt1=0,head2[N],cnt2=0;//链式前向星用 
struct SB{int id,val;bool operator < (const SB &b)const{return val>b.val;}};//dij堆优化用,按边权从小到大排序 
int n,m,k,qq,vis[N],num;//vis标记当前点是否已求出最短路,num最小生成树建边计数器 
int dis[N],fa[N],f[N][21],dep[N],maxn[N][21];//
priority_queue<SB>q;//优先队列 
inline int read(){int x=0,f=1;char ch=getchar();while(!isdigit(ch)){f=ch!='-';ch=getchar();}while(isdigit(ch)){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}return f?x:-x;}
int cmp(sb a,sb b){return a.w<b.w;}//结构体比较函数,按边权从小到大排序 
void add(int u,int v,int w){e[++cnt1]=(sb){u,v,w,head1[u]};head1[u]=cnt1; }//一开始的存图 
void Add(int u,int v,int w){e2[++cnt2]=(sb){u,v,w,head2[u]};head2[u]=cnt2;}//建好最小生成树后存图 
int fid(int x){return fa[x]==x?x:fa[x]=fid(fa[x]);}//并查集find函数 
void dij()
{
//	bug;
	memset(dis,0x3f,sizeof dis);//dis赋初值 
	{
		dis[0]=0;//标记起点不用走 
		q.push((SB){0,0});//起点入列 
		while(!q.empty())//只要队列不空 
		{
//			bug;
			SB qwq=q.top();q.pop();//取出队头元素 
			int u=qwq.id;//取出起点编号 
			if(vis[u])continue;//如果已经有最短路了就放弃 
			vis[u]=1;//标记 
			for(int i=head1[u];i;i=e[i].next)//遍历每一条与之相连的边 
			{
//				bug;
				int v=e[i].v;//取出终点 
				if(dis[v]>dis[u]+e[i].w)//松弛操作 
				  dis[v]=dis[u]+e[i].w;
				if(!vis[v])q.push((SB){v,dis[v]});//如果没有最短路的话就放入队列 
			}
		}
	}
}
void kruskal()
{
//	bug;
	for(re int i=1;i<=n;i++)
	  fa[i]=i;//初始化fa数组 
	for(re int i=1;i<=m;i++)
	{
//		bug;
		int xx=fid(e1[i].u);
		int yy=fid(e1[i].v);//取出起点和终点的代表节点 
		if(xx!=yy)//不相等 
		{
			fa[xx]=yy;//合并并建边 
			Add(e1[i].u,e1[i].v,e1[i].w);
			Add(e1[i].v,e1[i].u,e1[i].w);
			num++;//建边数加一 
			if(num==n-1)return ;//够n-1条就退出 
		}
	}
}
void init()//预处理倍增数组 
{
	for(re int j=1;j<=20;j++)//千万不能两个反过来 
	  for(re int i=1;i<=n;i++)
	  {
	    f[i][j]=f[f[i][j-1]][j-1];//祖先倍增 
		maxn[i][j]=max(maxn[i][j-1],maxn[f[i][j-1]][j-1]);//最长路径倍增 
	  }
}
void dfs(int u,int fa)//处理每一个点的深度 
{
//	init(); 
//	bug;
	f[u][0]=fa;//这里先把父亲节点给处理出来 
	for(re int i=head2[u];i;i=e2[i].next)//枚举每一条与之相连的边 
	{
//		bug;
		int v=e2[i].v;//取出终点 
		if(v==fa)continue;//如果终点就是父节点就跳过 
		dep[v]=dep[u]+1;//处理深度 
		maxn[v][0]=e2[i].w;//maxn也要赋初值 
		dfs(v,u);//继续搜索 
	}
}
int get_max(int x,int y)//x到y路径的最大值 
{
	int ans=0;//ans存放答案 
	if(dep[x]<dep[y])swap(x,y);//保证x的深度比y大 
	for(re int i=20;i>=0;i--)
	{
		if(dep[f[x][i]]<dep[y])continue;//如果会超过y就不跳 
		ans=max(ans,maxn[x][i]);//更新路径上边权最大值 
		x=f[x][i];//更新x点的编号 
	}
	if(x==y)return ans;//如果xy在一条链上就直接返回答案 
	for(re int i=20;i>=0;i--)
	{
		if(f[x][i]==f[y][i])continue;//如果跳到公共祖先就返回 
		ans=max(ans,max(maxn[x][i],maxn[y][i]));//否则就更新最大值 
		x=f[x][i];y=f[y][i];//更新xy点编号 
	}
	ans=max(ans,max(maxn[x][0],maxn[y][0]));//更新最大值 
	return ans;//返回答案 
}
signed main()
{
	n=read();m=read();k=read();qq=read();
	for(re int i=1;i<=m;i++)
	{
//		bug;
		int u=read(),v=read(),w=read();
		add(u,v,w),add(v,u,w);//输入边 
		e1[i]=(sb){u,v,w};//存副本 
	}
	for(re int i=1;i<=k;i++)
//	  bug,
	  add(0,i,0),add(i,0,0);//建超级源点并连边 
	dij();//跑一遍dij 
//	cout<<'\n';
//	for(int i=1;i<=n;i++)
//	  cout<<dis[i]<<'\n';
	for(re int i=1;i<=m;i++)
//	  bug,
	  e1[i].w+=dis[e1[i].u]+dis[e1[i].v];//更新边的权值 
	sort(e1+1,e1+m+1,cmp);//排序 
	kruskal();//生成最小生成树 
	dep[1]=1;//标记第一个点的深度为1 
//	cout<<'\n';
//	for(int i=1;i<=m;i++)
//		cout<<e2[i].u<<" "<<e2[i].v<<" "<<e2[i].w<<" "<<e2[i].next<<'\n';
	dfs(1,-1);//开始搜索处理深度 
	init();//预处理出两个倍增数组 
	for(re int i=1;i<=qq;i++)
	{
		int x=read(),y=read();
		cout<<get_max(x,y)<<'\n';//直接输出询问 
	}
	return 0;//好习惯 
}
posted @ 2022-10-03 22:14  北烛青澜  阅读(23)  评论(0)    收藏  举报