您的位置:首页 > 其它

HDU 5001 Walk 概率DP BFS 矩阵递推 暴力

2014-09-14 01:06 495 查看
题意:给出一张图,图上有N个点。开始时,等概率的选取起点。之后对于某个点,会从和他相连的所有点中,等概率的选取其中的一个点,继续走。一共要走D步。问,对于每个点,有多大的概率,使路径不包含该点。

思路:可以算概率DP,也能算BFS吧。赛中想到了一个点,但是没有想到删点重新建图。

因为要求出每个点的概率,我们必须枚举每个点。对于该点,我们要求所有不到该点路径的概率。也就相当于,将该点从图上删除,其他节点到达的概率总和。

需要注意的是,虽然该点在图上被删除了(其实是不能被访问),但每个点的度都没有变,如果改变了,会导致计算错误。

状态转移就是对距离计数,直接看代码吧。

代码如下:

#include <cstdio>
#include <algorithm>
#include <cstring>

using namespace std;

const int MAX = 6000;

int T,N,M,D;
int head[MAX];
int nxt[MAX];
int to[MAX];
int deg[MAX];
int tot;
double dp[10010][55];

void init()
{
memset(head,-1,sizeof(head));
memset(deg,0,sizeof(deg));
tot = 0;
}

void addedge(int u, int v)
{
to[tot] = v; nxt[tot] = head[u];
head[u] = tot++; deg[u]++;
to[tot] = u; nxt[tot] = head[v];
head[v] = tot++; deg[v]++;
}

int main(void)
{
//freopen("input.txt","r",stdin);
//freopen("out.txt","w",stdout);
int u,v;
scanf("%d", &T);
while(T--){
scanf("%d %d %d",&N,&M,&D);
init();
for(int i = 0 ; i < M; ++i){
scanf("%d %d", &u,&v);
addedge(u,v);
}
for(int i = 1; i <= N; ++i){
memset(dp,0.0,sizeof(dp));
for(int j = 1; j <= N; ++j) if(j != i)
dp[0][j] = 1.0 / N;
for(int d = 0; d < D; ++d){
for(int j = 1; j <= N; ++j) if(j != i)
for(int v = head[j]; ~v; v = nxt[v])
dp[d+1][to[v]] += dp[d][j] * (1.0 / deg[j]);
}
double ans = 0.0;
for(int j = 1; j <= N; ++j)
if(j != i) ans += dp[D][j];
printf("%.10f\n",ans);
}

}
return 0;
}

同样,我们也可以用BFS的方式写转移;

#include <cstdio>
#include <algorithm>
#include <cstring>
#include <utility>

using namespace std;

typedef pair<int,int> pii;

const int MAX = 6000;

int T,N,M,D;
int head[MAX],nxt[MAX],to[MAX],deg[MAX];
int tot;
double dp[10010][55];
bool vis[10010][55];
int front, tail;
pii que[10010 * 55];

void init()
{
memset(head,-1,sizeof(head));
memset(deg,0,sizeof(deg));
tot = 0;
}

void addedge(int u, int v)
{
to[tot] = v; nxt[tot] = head[u];
head[u] = tot++; deg[u]++;
to[tot] = u; nxt[tot] = head[v];
head[v] = tot++; deg[v]++;
}

int main(void)
{
//freopen("input.txt","r",stdin);
//freopen("out.txt","w",stdout);
int u,v;
scanf("%d", &T);
while(T--){
scanf("%d %d %d",&N,&M,&D);
init();
for(int i = 0 ; i < M; ++i){
scanf("%d %d", &u,&v);
addedge(u,v);
}
for(int i = 1; i <= N; ++i){
memset(dp,0.0,sizeof(dp));
memset(vis,false,sizeof(vis));
front = tail = 0;
for(int j = 1; j <= N; ++j)
if(j != i){
dp[0][j] = 1.0 / N;
que[tail++] = pii(0,j);
vis[0][j] = true;
}
while(front < tail){
pii tmp = que[front++];
int d = tmp.first, u = tmp.second;
if(u == i) continue;
if(d >= D) break;
for(int v = head[u]; ~v; v = nxt[v]){
dp[d+1][to[v]] += dp[d][u] / deg[u];
if(!vis[d+1][to[v]]){
vis[d+1][to[v]] = true;
que[tail++] = pii(d+1,to[v]);
}
}
}
double ans = 0.0;
for(int j = 1; j <= N; ++j)
if(j != i) ans += dp[D][j];
printf("%.10f\n",ans);
}
}
return 0;
}

这个题也可以用矩阵来求;

首先可以求出每个点到其他点的概率。这样就组成了一个矩阵。

一个点不可达,等价于其他点到它和它到其他点的概率为0。所以,对于每次枚举不可达点,我们可以将对应的矩阵的某些元素的概率设为0.这样求D次矩阵快速幂,就得到了, 长度为D,除去点i,其他点到达的概率,这样,我们在对所有的情况求和即可。

代码如下:

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
using namespace std;
int a[55][55];
int deg[55];
double mat[55][55];

struct Matrix{
double mat[55][55];
int n, m;

void init(int n, int m){
this->n = n;
this->m = m;
memset(mat, 0, sizeof(mat));
}

Matrix operator * (const Matrix& B){
Matrix C;
C.init(n, B.m);
for(int i=0; i<n; i++){
for(int j=0; j<B.m; j++){
for(int k=0; k<m; k++){
C.mat[i][j] += mat[i][k] * B.mat[k][j];
}
}
}
return C;
}

Matrix quick_pow(int x){
Matrix Base, ret;
Base.init(n,m);
ret.init(n,m);
for(int i=0; i<n; i++){
for(int j=0; j<m; j++){
Base.mat[i][j]=mat[i][j];
if(i==j) ret.mat[i][j]=1;
}
}

while(x){
if(x&1) ret=ret*Base;
Base=Base*Base;
x>>=1;
}

return ret;
}

void print(){
for(int i=0; i<n; i++){
for(int j=0; j<m; j++){
cout<<mat[i][j]<<" ";
} cout<<endl;
}
}
};

double b[55];

Matrix s;
int main()
{
int t, n, m, d, x, y;
scanf("%d",&t);
while(t--){
scanf("%d%d%d",&n,&m,&d);
memset(a,0,sizeof a);
memset(deg,0,sizeof deg);
memset(mat,0,sizeof mat);
for(int i=0; i<m; i++){
scanf("%d%d",&x,&y);
x--, y--;
a[x][y]=a[y][x]=1;
deg[x]++, deg[y]++;
}
for(int c=0; c<n; c++){
double sum=0;
for(int i=0; i<n; i++){
for(int j=0; j<n; j++){
if(a[i][j]) mat[i][j]=1.0/(double)deg[j];
}
}
for(int i=0; i<n; i++){
mat[c][i]=mat[i][c]=0.0;
}
s.init(n,n);
for(int j=0; j<n; j++){
for(int k=0; k<n; k++){
s.mat[j][k]=mat[j][k];
}
}
s=s.quick_pow(d);

for(int i=0; i<n; i++){
if(i==c) continue;
for(int j=0; j<n; j++){
if(j==i) b[j]=1.0/(double)n;
else b[j]=0.0;
}

for(int j=0; j<n; j++){
for(int k=0; k<n; k++){
sum+=b[j]*s.mat[k][j];
}
//puts("");
}
}

printf("%.10f\n",sum);
}
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: