bzoj 3143 [Hnoi2013]游走
题目大意
一个无向连通图,顶点从1编号到N,边从1编号到M。
小Z在该图上进行随机游走,初始时小Z在1号顶点,每一步小Z以相等的概率随机选 择当前顶点的某条边,沿着这条边走到下一个顶点,获得等于这条边的编号的分数。当小Z 到达N号顶点时游走结束,总分为所有获得的分数之和。
现在,请你对这M条边进行编号,使得小Z获得的总分的期望值最小。
n<=600
分析
直接搞边比较困难
我们考虑到如果一条边期望经过次数多,就应该给它一个小的编号
而一条边(x,y)期望经过次数就是\(\frac {e[x]} {du[x]}+\frac {e[y]} {du[y]}\)
e[x]用高斯消元求
方法1
设e[i]为i期望有多少次机会往旁边走
注意到:走到n后无法走回来
所以即使有连边i-n,i也无法从n转移过来
\(e[n]=0\)
\(e[1]=(\sum \frac {e[y]} {du[y]} )+1\)(每种方案一都多走一次)
\(e[x]=\sum \frac {e[y]} {du[y]} (x!=1)\)
方法2
设e[i]为i期望经过多少次
注意到:走到n后无法走回来
\(e[n]=\sum \frac {e[y]} {du[y]}=1\)
\(e[x]=\sum \frac {e[y]} {du[y]}\)
最后算边的次数时注意不能从n走来
solution1
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long double db;
const int M=507;
const db eps=1e-10;
inline int rd(){
int x=0;bool f=1;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
for(;isdigit(c);c=getchar()) x=x*10+c-48;
return f?x:-x;
}
int n,m;
int du[M];
struct node{
int x,y;
db z;
node(int xx=0,int yy=0,db zz=0){
x=xx;
y=yy;
}
}ed[M*M*2];
struct Guass{
db a[M][M];
db res[M];
int n,m;
void init(int nn){
n=nn;
m=nn+1;
memset(res,0,sizeof(res));
memset(a,0,sizeof(a));
}
void xiao(int x,db ff,int y){
for(int i=1;i<=m;i++)
a[y][i]-=a[x][i]*ff;
}
void getres(){
int i,j;
db tp;
for(i=n;i>0;i--){
tp=0;
for(j=i+1;j<m;j++) tp+=a[i][j]*res[j];
res[i]=(-tp-a[i][m])/a[i][i];
}
}
void gauss(){
int i,j,k=0;
for(i=1;i<m;i++){
k++;
for(j=k;j<=n;j++)
if(fabs(a[j][i])>eps){
swap(a[j],a[k]);
break;
}
for(j=1;j<=n;j++)
if(fabs(a[j][i])>eps&&j!=k)
xiao(k,a[j][i]/a[k][i],j);
}
getres();
}
}GS;
bool cmpw(node x,node y){
return x.z>y.z;
}
int main(){
n=rd(),m=rd();
int i,x,y;
for(i=1;i<=m;i++){
x=rd(),y=rd();
du[x]++; du[y]++;
ed[i]=node(x,y);
}
GS.init(n);
for(i=1;i<=m;i++){
x=ed[i].x, y=ed[i].y;
if(x!=n) GS.a[x][y]+=1.0/du[y];
if(y!=n) GS.a[y][x]+=1.0/du[x];
}
for(i=1;i<=n;i++) GS.a[i][i]-=1.0;
GS.a[1][n+1]+=1.0;
GS.gauss();
for(i=1;i<=m;i++){
x=ed[i].x, y=ed[i].y;
ed[i].z=0;
ed[i].z+=GS.res[x]/du[x];
ed[i].z+=GS.res[y]/du[y];
}
sort(ed+1,ed+m+1,cmpw);
db ans=0;
for(i=1;i<=m;i++) ans+=ed[i].z*i;
printf("%.3Lf",ans+eps);
return 0;
}
solution2
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <cctype>
#include <cmath>
#include <algorithm>
using namespace std;
typedef long double db;
const int M=507;
inline int rd(){
int x=0;bool f=1;char c=getchar();
for(;!isdigit(c);c=getchar()) if(c=='-') f=0;
for(;isdigit(c);c=getchar()) x=x*10+c-48;
return f?x:-x;
}
int n,m;
int du[M];
struct node{
int x,y;
db z;
node(int xx=0,int yy=0,db zz=0){
x=xx;
y=yy;
}
}ed[M*M*2];
struct Gauss{
db a[M][M];
db res[M];
int n,m;
void init(int nn){
n=nn;
m=nn+1;
memset(res,0,sizeof(res));
memset(a,0,sizeof(a));
}
void xiao(int x,db ff,int y){
for(int i=1;i<=m;i++)
a[y][i]-=a[x][i]*ff;
}
void getres(){
int i,j;
db tp;
res[m]=1;
for(i=n;i>0;i--){
tp=0;
for(j=i+1;j<=m;j++) tp+=a[i][j]*res[j];
res[i]=-tp/a[i][i];
}
res[m]=0;//****
}
void gauss(){
int i,j,k=0;
for(i=1;i<m;i++){
k++;
for(j=k;j<=n;j++)
if(a[j][i]){
swap(a[j],a[k]);
break;
}
for(j=1;j<=n;j++)
if(a[j][i]&&j!=k)
xiao(k,a[j][i]/a[k][i],j);
}
getres();
}
}GS;
bool cmpw(node x,node y){
return x.z>y.z;
}
int main(){
n=rd(),m=rd();
int i,x,y;
for(i=1;i<=m;i++){
x=rd(),y=rd();
du[x]++; du[y]++;
ed[i]=node(x,y);
}
GS.init(n-1);//只用解1...n-1,下面的代码中-1都只是将第一条方程去掉后每条方程往上移一下而已
for(i=1;i<=m;i++){
x=ed[i].x, y=ed[i].y;
if(y!=n) GS.a[x-1][y]+=1.0/du[y];
if(x!=n) GS.a[y-1][x]+=1.0/du[x];
}
for(i=2;i<n;i++) GS.a[i-1][i]-=1.0;
GS.a[n-1][n]-=1.0;//第n项为常数项
GS.gauss();
GS.res[n]=0;
for(i=1;i<=m;i++){
x=ed[i].x, y=ed[i].y;
ed[i].z=0;
ed[i].z+=GS.res[x]/du[x];
ed[i].z+=GS.res[y]/du[y];
}
sort(ed+1,ed+m+1,cmpw);
db ans=0;
for(i=1;i<=m;i++) ans+=ed[i].z*i;
printf("%.3Lf\n",ans);
return 0;
}