[NOI2005]BZOJ1415 聪聪和可可

121 篇文章 0 订阅
15 篇文章 0 订阅

传送门

题解:

这个题,开始本着拿50分去写,结果写完了在vijos和tyvj上提交竟然都得了80分而且速度好像都很快,

然后调试了一些奇怪的bug(坑),就AC了。事后又想了几分钟才想明白复杂度为什么是对的QwQ我真是太弱辣自己写的正解都不知道QAQ

首先,按照题意预处理dist[i][j](就是i,j的距离),to[i][j]表示从i到j下一步要走的编号最小的点。

然后dp[s][t]记作答案。

首先如果dist[s][t]==0那么dp[s][t]=0显然。

否则如果dist[s][t]<=2那么dp[s][t]=1显然。

否则,因为是s先走,走到了to[to[s][t]][t],记作x,然后t等概率走到P(t)和t。(P(t)就是t周围的点的集合)

所以dp[s][t]=sigma{dp[x][y]}/(|P|+1)+1。其中y=t或者y∈P(t)。

这样记忆搜索一下就好了。

事实上注意到dist(s,t)随着递归在变小(因为一个走两步,一个走一步,距离肯定在减小),也可以像区间dp一样递推。

但是这个题有几个坑点(对本蒟蒻来讲)

第一个坑,要求编号最小(不知道有几个没看见的QwQ)

第二个坑,不能直接计算出走两步到达的最小编号,因为它是一步一步走的。

第三个坑,求的是期望时间而不是期望步数QwQ

最后分析一下复杂度,状态是O(N^2)的显然。

递推貌似最坏可以使O(N)的?所以复杂度是O(N^3)?

并不是,实际上每个点作为答案被统计了O(这个点的度数+1)=O(这个点相连的边的数量+1)。

然后发现每条边被统计了两次,所以第一部分复杂度是O(2*边数)=O(M)的

那个+1由于有n个点所以+1的部分是O(N)的。

所以复杂度是O(N^2+M)的。

代码:

//BZOJ 1415
//NOI 2005
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<vector>
#define MAXN 1010
using namespace std;
double dp[MAXN][MAXN];
bool calc[MAXN][MAXN],vis[MAXN];
int dist[MAXN][MAXN],to[MAXN][MAXN];
vector<int> g[MAXN];queue<int> q;
int bfs(int s)
{
	memset(vis,false,sizeof(vis));
	while(!q.empty()) q.pop();
	vis[s]=true;to[s][s]=s;
	dist[s][s]=0;q.push(s);
	while(!q.empty())
	{
		int x=q.front();q.pop();
		for(int i=g[x].size()-1;i>=0;i--)
			if(!vis[g[x][i]])
			{
				int t=g[x][i];vis[t]=true;
				dist[s][t]=dist[s][x]+1;
				if(dist[s][t]<=1) to[s][t]=t;
				else to[s][t]=to[s][x];
				q.push(t);
			}
			else{
				int t=g[x][i];
				if(dist[s][t]<=1) continue;
				if(dist[s][t]!=dist[s][x]+1) continue;
				to[s][t]=min(to[s][t],to[s][x]);
			}
	}
	return 0;
}
double getdp(int x,int y)
{
	if(calc[x][y]) return dp[x][y];
	calc[x][y]=true;
	if(dist[x][y]==0) return dp[x][y]=0.0;
	if(dist[x][y]<=2) return dp[x][y]=1.0;
	dp[x][y]=getdp(to[to[x][y]][y],y);
	for(int i=g[y].size()-1;i>=0;i--)
		dp[x][y]+=getdp(to[to[x][y]][y],g[y][i]);
	return dp[x][y]=dp[x][y]/(1.0+g[y].size())+1.0;
}
int main()
{
	int n,m,s,t;scanf("%d%d%d%d",&n,&m,&s,&t);
	for(int i=1;i<=m;i++)
	{
		int u,v;scanf("%d%d",&u,&v);
		g[u].push_back(v);g[v].push_back(u);
	}
	for(int i=1;i<=n;i++) bfs(i);
	printf("%.3f\n",getdp(s,t));
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值