后效性dp入门~~
我发现我越来与热衷于写博客
codeforces 24D Broken Robot
个人认为这道题适合 去除后效性 入门,思维难度不大,适合复习高斯消元
题面:
你作为礼物收到一个非常聪明的机器人走在矩形板上。不幸的是,你明白它已经破碎并且行为相当奇怪(随机)。该板由N行和M列单元组成。机器人最初位于第i行和第j列的某个单元格中。然后在每一步,机器人都可以去另一个细胞。目的是走到最底层(N.排。机器人可以停留在当前单元格中,向左移动,向右移动或移动到当前单元格下方的单元格。如果机器人位于最左侧的列中,则它不能向左移动,如果它位于最右侧的列中,则它不能向右移动。在每一步中,所有可能的动作都是同样可能的。返回预期的步数以到达最下面一行。
分析:
先列方程式
设f[i][j]表示从(i,j)这个点走到第n行的期望步数
首先当i=n是f[n][j]=0
当j=1时机器人只能在原地或往下或往右
f
[
i
]
[
1
]
=
1
/
3
∗
(
f
[
i
]
[
2
]
+
f
[
i
]
[
1
]
+
f
[
i
+
1
]
[
1
]
)
+
1
(
i
<
n
)
f[i][1]=1/3*(f[i][2]+f[i][1]+f[i+1][1])+1 (i<n)
f[i][1]=1/3∗(f[i][2]+f[i][1]+f[i+1][1])+1(i<n)
当j=m是机器人只能在原地或往下或往左
f
[
i
]
[
m
]
=
1
/
3
∗
(
f
[
i
]
[
m
]
+
f
[
i
]
[
m
−
1
]
+
f
[
i
+
1
]
[
m
]
)
+
1
(
i
<
n
)
f[i][m]=1/3*(f[i][m]+f[i][m-1]+f[i+1][m])+1 (i<n)
f[i][m]=1/3∗(f[i][m]+f[i][m−1]+f[i+1][m])+1(i<n)
否则四种情况
f
[
i
]
[
j
]
=
1
/
4
∗
(
f
[
i
]
[
j
]
+
f
[
i
]
[
j
−
1
]
+
f
[
i
]
[
j
+
1
]
+
f
[
i
+
1
]
[
j
]
)
+
1
(
i
<
n
,
1
<
j
<
m
)
f[i][j]=1/4*(f[i][j]+f[i][j-1]+f[i][j+1]+f[i+1][j])+1 (i<n,1<j<m)
f[i][j]=1/4∗(f[i][j]+f[i][j−1]+f[i][j+1]+f[i+1][j])+1(i<n,1<j<m)
但是这样的话会有后效性,所以高斯消元
移项一下
例如
f
[
i
]
[
j
]
=
1
/
4
∗
(
f
[
i
]
[
j
]
+
f
[
i
]
[
j
−
1
]
+
f
[
i
]
[
j
+
1
]
+
f
[
i
+
1
]
[
j
]
)
+
1
f[i][j]=1/4*(f[i][j]+f[i][j-1]+f[i][j+1]+f[i+1][j])+1
f[i][j]=1/4∗(f[i][j]+f[i][j−1]+f[i][j+1]+f[i+1][j])+1
1
/
4
∗
(
f
[
i
]
[
j
−
1
]
+
f
[
i
]
[
j
+
1
)
−
3
/
4
∗
f
[
i
]
[
j
]
=
−
1
/
4
∗
f
[
i
+
1
]
[
1
]
−
1
1/4*(f[i][j-1]+f[i][j+1)-3/4*f[i][j]=-1/4*f[i+1][1]-1
1/4∗(f[i][j−1]+f[i][j+1)−3/4∗f[i][j]=−1/4∗f[i+1][1]−1
可以发现f[i+1][1]是不受影响已知的,并且这就像一个方程
于是就可以将f[i][1…m]看成m个位置数,并且相应的有m个方程,系数和常数都可以计算
然后高斯消元求解
但是高斯消元是 o(m^3)
可以发现有m个未知数,但每个方程都只涉及2~3个未知数,类似于这样(1表示有系数,0表示没有)
1 1 0 0 0
1 1 1 0 0
0 1 1 1 0
0 0 0 1 1
然后就可以发现对角线上都有系数,那就不用去找了
消么可以先消成上三角矩阵,然后在消一遍(我努力想一遍消完,没做到)
注意m=1时要特判
代码:
#include <bits/stdc++.h>
using namespace std;
int n,m,x,y;
double f[1010][1010],c[1010][1010],b[1010];
void build()
{
for (int i=1;i<=m;i++)
{
if (i==1) c[i][1]=-(2.0/3.0),c[i][2]=1.0/3.0;
else if (i==m) c[i][m-1]=1.0/3.0,c[i][m]=-(2.0/3.0);
else c[i][i-1]=1.0/4.0,c[i][i]=-(3.0/4.0),c[i][i+1]=1.0/4.0;
}
}
int main()
{
freopen("in.in","r",stdin);
freopen("out.out","w",stdout);
scanf("%d%d%d%d",&n,&m,&x,&y);
if (m==1) {printf("%lf",(double)(n-x)*2);return 0;}
for (int i=1;i<=m;i++)
f[n][i]=0;
for (int i=n-1;i>=1;i--)
{
build();
for (int j=1;j<=m;j++)
if (j==1||j==m) b[j]=-f[i+1][j]/3-1;
else b[j]=-f[i+1][j]/4-1;
for (int j=1;j<m;j++)
{
double k=c[j+1][j]/c[j][j];
c[j+1][j]=0;
c[j+1][j+1]-=k*c[j][j+1];
b[j+1]-=k*b[j];
}
for (int j=m;j>1;j--)
{
double k=c[j-1][j]/c[j][j];
c[j-1][j]=0;
b[j-1]-=b[j]*k;
}
for (int j=1;j<=m;j++)
f[i][j]=b[j]/c[j][j];
}
printf("%lf",f[x][y]);
return 0;
}
XOR和路径
这个好像思维难度也不大
题目
给定一个无向连通图,其节点编号为1到N,其边的权值为非负整数。
试求出一条从1号节点都N号节点的路径,使得该路径上经过的边的权值的XOR和最大。
该路径可以重复经过某些节点或边,当一条边在路径中出现多次时,其权值在计算XOR和时也应被重复计算相应多的次数。
直接求解上述问题比较困难,于是你决定使用非完美算法。
具体来说,从1号节点开始,以相等的概率,随机选择与当前节点相关联的某条边,并沿着这条边走到下一个节点,重复这个过程直到走到N号节点为止,便得到一条从1号节点到N号节点的路径。
显然得到每条这样的路径的概率是不同的,并且每条这样的路径的XOR和也不一样。
现在请你求出该算法得到的路径的XOR和的期望值。
n<=100
分析
貌似不可以小数进行xor运算??
考虑吧xor和分解成二进制
最终得到的xor和路径不会超过2^30,所以分别考虑每一位,计算最后的到的xor和中这一位是1的概率,对答案的贡献就是概率*(1<<x) (期望公式??)
考虑对于第x位时的dp方程
f[i]表示从第i个点走到点n xor和第x位是1的概率,那么第x为是0的概率就是1-f[i]
d[i]表示点i的度,点j为与i相连的点,w(i,j)表示点i到点j的边权二进制下第x位的值
f
[
i
]
=
1
/
d
[
i
]
∗
(
∑
w
(
i
,
j
)
=
1
(
1
−
f
[
j
]
)
+
∑
w
(
i
,
j
)
=
0
f
[
j
]
)
f[i]=1/d[i]*(\sum_{w(i,j)=1}(1-f[j])+\sum_{w(i,j)=0}f[j])
f[i]=1/d[i]∗(w(i,j)=1∑(1−f[j])+w(i,j)=0∑f[j])
于是得到方程
d
[
i
]
∗
f
[
i
]
−
∑
w
(
i
,
j
)
=
0
f
[
j
]
+
∑
w
(
i
,
j
)
=
1
f
[
j
]
=
∑
w
(
i
,
j
)
=
1
1
d[i]*f[i]-\sum_{w(i,j)=0}f[j]+\sum_{w(i,j)=1}f[j]=\sum_{w(i,j)=1}1
d[i]∗f[i]−w(i,j)=0∑f[j]+w(i,j)=1∑f[j]=w(i,j)=1∑1
方程右边是常数,对于每个点都列一个方程
然后就高斯消元
本题数据n<=100,直接上就好了
需要注意的是
f[n]=0,不能再从其它点转移
所以n的方程就是1*f[n]=0
自环只要连一条边,度数值算1(据说自环都是这样的,但我不知道。。。)
#include <bits/stdc++.h>
using namespace std;
int t,Link[110],n,m,d[110];
double c[110][110],b[110],ans;
struct dsa
{
int nex,v,w;
}e[20010];
void insert(int xx,int yy,int zz)
{
e[++t].nex=Link[xx];
e[t].v=yy;
e[t].w=zz;
Link[xx]=t;
}
void build(int x)
{
memset(c,0,sizeof(c));
for (int i=1;i<n;i++)
{
int s=0;
c[i][i]=(double)d[i];
for (int j=Link[i];j;j=e[j].nex)
if ((e[j].w>>x)&1) c[i][e[j].v]++,s++;
else c[i][e[j].v]--;
b[i]=s;
}
c[n][n]=1;b[n]=0;
}
double gause()
{
for (int i=1;i<=n;i++)
{
for (int j=i;j<=n;j++)
if (fabs(c[j][i])>1e-8)
{
for (int k=1;k<=n;k++) swap(c[i][k],c[j][k]);
swap(b[i],b[j]);
break;
}
if (fabs(c[i][i])<1e-8) continue;
for (int j=1;j<=n;j++)
{
if (i==j) continue;
double h=c[j][i]/c[i][i];
for (int k=i;k<=n;k++)
c[j][k]-=h*c[i][k];
b[j]-=b[i]*h;
}
}
return b[1]/c[1][1];
}
int main()
{
scanf("%d%d",&n,&m);
for (int i=1;i<=m;i++)
{
int xx,yy,zz;
scanf("%d%d%d",&xx,&yy,&zz);
insert(xx,yy,zz),d[xx]++;
if (xx!=yy) insert(yy,xx,zz),d[yy]++;
}
for (int i=0;i<=30;i++)
{
build(i);
double x=gause();
ans+=x*(1<<i);
}
printf("%.3lf",ans);
return 0;
}