2.18
2.18
- 越来越感觉在做图论了.......
- 首先本题运用了倍增的思想(特地去学了一下倍增),倍增主要是运用于lca(寻找结点的最近祖先结点)的问题中。简单来说我们维护一个一维线性数组,也就是\(dp[i][j]\)表示我们从i点向右走\(2^j\)个位置,那么我们其实是可以维护出一个转移方程的,也就是\(dp[i][j] = dp[dp[i][j-1]][j-1]\) 也就是我们从i点向右走\(2^j\)个位置,其实就等价于从i点向右走\(2^{j-1}\)后的点再走\(2^{j-1}\),其实就是走了\(2^j\)个点。
- 那么我们放在这个题可以考虑的是,如果我们从i点到j点能够达到\(2^j\)个距离的话,那么我们的\(dist[i][j] = 1\),那么我们思路就出来了,维护一个\(str[i,j,k]\)数组表示,i点到j点距离为\(2^k\)的路径是否可以直接实用跑路机。
- 所以我们在初始化时,只需要枚举k,t(中间结点),i,j四个变量,并满足\(str[i][t][k - 1] \&\& str[t][j][k - 1]\)时就满足\(str[i][j][k]=true\),满足我们在i点到j点直接添加一条边
- ok,那么我们直接对这个图使用一次floyd算法即可,多元点最短路,因为n<=50,所以是非常ok的
import java.io.*;
import java.util.*;
import java.util.zip.ZipEntry;
public class Main implements Runnable {
static StreamTokenizer cin = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter cout = new PrintWriter(new OutputStreamWriter(System.out));
static final int N = 55;
static final int M = 32;
static int[][] dist = new int[N][N];
static boolean[][][] str = new boolean[N][N][M];//i点到j点是否存在长度为2^k的一条边
static int n, m;
public static void main(String[] args) {
new Thread(null, new Main(), "", 1 << 29).start();
}
public static int nextInt() throws IOException {
cin.nextToken();
return (int) cin.nval;
}
public static void dfs(int parent, int cnt) {
}
public static void init() {
for (int k = 1; k < M; k++) {
for (int t = 1; t <= n; t++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (str[i][t][k - 1] && str[t][j][k - 1]) {
str[i][j][k] = true;
dist[i][j] = 1;
}
}
}
}
}
}
public static void floyd() {
for (int k = 1; k <= n; k++) {
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
dist[i][j] = Math.min(dist[i][j], dist[i][k] + dist[k][j]);
}
}
}
}
@Override
public void run() {
try {
n = nextInt();m = nextInt();
for (int i = 1; i <= n; i++) {
Arrays.fill(dist[i], 0x3f3f3f3f);
}
for (int i = 1; i <= m; i++) {
int x = nextInt(); int y = nextInt();
str[x][y][0] = true;
dist[x][y] = 1;
}
init();
floyd();
cout.println(dist[1][n]);
cout.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
- 属于是给概率dp打了一下基础
- 求概率是从初始状态转移到结束状态
import java.io.*;
import java.util.*;
import java.util.zip.ZipEntry;
public class Main implements Runnable {
static StreamTokenizer cin = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter cout = new PrintWriter(new OutputStreamWriter(System.out));
static int w, b;
static final int N = (int) (1e3 + 10);
static double[][] dp = new double[N][N];
public static void main(String[] args) {
new Thread(null, new Main(), "", 1 << 29).start();
}
public static int nextInt() throws IOException {
cin.nextToken();
return (int) cin.nval;
}
@Override
public void run() {
try {
w = nextInt();b = nextInt();
for (int i = 1; i <= w; i++) {
dp[i][0] = 1;
}
for (int i = 1; i <= w; i++) {
for (int j = 1; j <= b; j++) {
dp[i][j] += (double) i / (i + j);//先手拿白鼠
if (j >= 3) {
dp[i][j] += ((double)j / (i + j)) * ((double)(j - 1) / (i + j - 1)) * ((double)(j - 2) / (i + j - 2)) * dp[i][j - 3];
}
if (i >= 1 && j >= 2) {
dp[i][j] += ((double)j / (i + j)) * ((double)(j - 1) / (i + j - 1)) * ((double)(i) / (i + j - 2)) * dp[i - 1][j - 2];
}
}
}
cout.println(String.format("%.9f", dp[w][b]));
cout.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
P4316 绿豆蛙的归宿 - 洛谷 (luogu.com.cn)

- 与上一道题不一样,本题是求解期望,由上图所示,这是期望的公式,分析公式我们知道就是我们需要求出两个值,也就是概率p和权重x(对于每个概率),要理清x的概念,在不同情况都有不同的运用。比如求解路径长度的期望值,那么我们的权重其实就是这条路径的长度
- 考虑本题是求解期望,其实末尾状态时较好得出的,也就是我们经典的倒推,也就是从结尾状态得出初始状态。\(dp[i]\)就为当前结点i转为最终状态结点的期望。因为\(dp[n]=0\),也就是最终状态期望为0,很好想到就是n到n点,路径为0,1*0=0。
- 那么考虑转移公式,假设我们有i点到j点的一个情况,也就是逆推的话就是j点推i点。那么\(dp[i] += (dp[j] + w[i][j]) * p[i][j]\),然后就是如果i点可以到达很多j点(j点为i点的子节点,可以考虑到i点会有很多子节点),全部累加就好。
- ok,那么看看数据范围$ 1<= n <= 1e5$,显然如果采用二维数组去存的话会爆空间。考虑存边,这里推荐一个写法,直接用Vector存储一个结构体(该边的另一个结点和边权),详细看代码吧,感觉挺好的,目前没有发现弊端
- 注意就是因为我们倒推,添加方向调转一下就好了,因为题目保证了有向无环图,也就是我们说的DAG,那么考虑拓扑排序即可,复杂度达到\(O(n + m)\),顺利ac
import java.io.*;
import java.util.*;
import java.util.zip.ZipEntry;
public class Main implements Runnable {
static StreamTokenizer cin = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
static PrintWriter cout = new PrintWriter(new OutputStreamWriter(System.out));
static int n, m;
static final int N = (int) (1e5 + 10);
static int[] in = new int[N];
static double[] dp = new double[N];
static double[] p = new double[N];
static Queue<Integer> queue = new PriorityQueue<>();
static Vector<node>[] nodes = new Vector[N];
public static void main(String[] args) {
new Thread(null, new Main(), "", 1 << 29).start();
}
public static int nextInt() throws IOException {
cin.nextToken();
return (int) cin.nval;
}
public static void topSort() {
while (!queue.isEmpty()) {
Integer now = queue.poll();
for (node son : nodes[now]) {
if (--in[son.x] == 0) queue.add(son.x);
dp[son.x] += (dp[now] + son.val) * p[son.x];
}
}
}
@Override
public void run() {
try {
n = nextInt();m = nextInt();
for (int i = 1; i <= n; i++) nodes[i] = new Vector<>();
for (int i = 1; i <= m; i++) {
int x = nextInt(); int y = nextInt();int val = nextInt();
nodes[y].add(new node(x, val));
in[x]++;
}
for (int i = 1; i <= n - 1; i++) {
p[i] = (1.0 / in[i]);
}
queue.add(n);dp[n] = 0;
topSort();
cout.println(String.format("%.2f", dp[1]));
cout.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
static class node {
int x;
int val;
public node(int x, int val) {
this.x = x;
this.val = val;
}
}
}
浙公网安备 33010602011771号