题意:N个参加聚会,和一个数组a,ai表示第i个人讨厌的人,如果一个到聚会门口的时候发现他讨厌的人已经在聚会里面,则他不会参加聚会,否则他会参加聚会。ai==i表示他没有讨厌的人。N个人来的先后顺序是任意的,也就是说n个来的先后顺序构成的1到n的排列是任意的。问参加聚会的人的期望是多少?
Hero is inviting his friends to the party.
He has n friends, numbered 0 through n-1. For each of his friends there is at most one other person the friend dislikes. You are given this information as a int[] a with n elements. For each i, a[i] is either the number of the person disliked by friend i, we have a[i]=i if friend i likes everybody else.
Hero is inviting his friends one at a time. Whenever he invites friend i, they will accept if and only if the friend a[i] didn‘t accept an earlier invitation. (That includes two cases: either Hero didn‘t invite friend a[i] yet, or he did but the friend rejected the invitation.)
Hero noticed that the order in which he invites his friends matters: different orders may produce different numbers of accepted invitations. He thought about finding the best order but the task was too hard for him. Therefore he has decided that he will invite his friends in a randomly chosen order. (Each permutation of his friends is equally likely to be chosen.)
Return the expected number of friends that will accept Hero‘s invitation.
Now there are three friends. Friend 0 likes everybody else, friend 1 likes everybody else, and friend 2 dislikes friend 1.
Given three friends, there are six possible orders. Hero will choose one of these orders uniformly at random.
For example, if he invites them in the order (1,0,2), friend 1 will accept, friend 0 will accept, and friend 2 will reject the invitation. However, if he invites them in the order (2,1,0), all three friends will accept the invite.
解:分开来计算每个人来参加聚会的期望,n个人来参加聚会的期望的和就是最后的答案。那么现在的问题是如何计算一个人来参加聚会的概率?
对于第i个人是否来参加聚会,受到一些人的影响,他们是:w1,w2,w3....wk。其中w1==i,w1讨厌w2,w2讨厌w3,...wk讨厌w1到wk-1的某个人。
我们对图中可能的情况进行拆解分析(循序渐进):
There are many possible approaches here including some that are very mathematical. Instead I‘ll explain one that is heavy code-wise but is interesting as it builds up from simple cases to general ones.
Let‘s make this into a graph. If a friend dislikes another friend, there is a directed edge connecting the hater to the hated. Like in the division 2 version, we can rely on there being a limited number of possible shapes. Let‘s try each of them and see what we can do.
The simplest configuration would be sequence of friends who hate the next friend:
We can describe these situations by just the number of nodes. So imagine a function
For each friend, there is an
So we can try this for each node
int n = a.size(); vector<double> line_expected(n + 1, 1.0); line_expected[0] = 0.0; line_expected[1] = 1.0; line_expected[2] = 1.5; for (int i = 3; i <= n; i++) { // last is picked: line_expected[i] = 1.0 + line_expected[i - 2]; // first is picked: line_expected[i] += 1.0 + line_expected[i - 1]; // second is picked: line_expected[i] += 1.0 + line_expected[i - 2]; // the rest for (int j = 1; j < i - 2; j++) { line_expected[i] += 1.0 + line_expected[j] + line_expected[i - j - 2]; } line_expected[i] /= i; } // line_expected[n] now has the expected value for a linear sequence of size n
The second-simplest kind of graph we can find in this problem are rings:
In this case, notice the symmetry. All of the nodes will behave in a similar way. For the first friend, we will pick one of them, it doesn‘t matter which. The result is the cycle breaks:
The first node picked means a friend gets invited. The friend who hated them won‘t be invited. So now we have
But enough easy structures, what about this?
This shape is a bit more complex but maybe we can still use the same method? Represent these "armed rings" by two integers: The length of the cycle and the length of the arm. The first friend picked might be any of the nodes. If the node is in the arm, the reduction is easy:
This will typically split the graph into a linear sequence and an "armed ring" with a shorter arm. So we can reuse
The start of the cycle is a more interesting case:
This will generate two linear sequences. One will have a length equal to the arm length minus 1 and the other a length equal to the cycle length minus 2.
Something similar happens when the friend that‘s hated by the start of the cycle is picked. Once again two linear sequences. Cutting other sections of the graph generates something more special:
This generates a Y-like shape. The good news is we can solve it too.
We could use the same approach, this time make it an
So if we wanted to find the expected number of friends that accept the invitation in a Y-shaped figure, we could calculate it as the sum of each probability the friend accepts an invitation. How is calculating these probabilities easier? Consider this:
The probabilities that each red node accepts the invitation are independent of the blue nodes. If a blue node accepts the invitation or not it doesn‘t change the red nodes‘ ability to accept. So if we want to find the probabilities for the red nodes, we can just consider a linear sequence of 6 nodes and calculate the probabilities.
We can go one step further and reuse the linearity of expectation. Remember that in the case of a linear sequence of 6 nodes, we already know how to calculate the expected number of friends that accept:
We can use the same logic and say that
We can translate all of this into a dynamic programming approach that works for any arm and cycle length:
vector<vector<double>> armed_ring_expected(n+1, vector<double>(n+1)); for (int i = 1; i <= n; i++) { // in the input, the final nodes in a sequence are represented as loops // cycles of length 1. We can solve that special case as if it was just a line. armed_ring_expected[1][i - 1] = line_expected[i]; } for (int cycle_len = 2; cycle_len <= n; cycle_len++) { armed_ring_expected[ cycle_len ][0] = 1.0 + line_expected[cycle_len - 2]; for (int arm_len = 1; arm_len + cycle_len <= n; arm_len++) { double & res = armed_ring_expected[ cycle_len ][ arm_len ]; res = 0.0; // a node in the arm is picked for (int i = 0; i < arm_len; i++) { res += 1.0 + line_expected[ max(arm_len - i - 2, 0) ] + armed_ring_expected[cycle_len][i]; } // a node in the cycle is picked // cycle node 0 (connected to the arm) res += 1.0 + line_expected[ arm_len - 1 ] + line_expected[cycle_len - 2]; // cycle node 1 (hated by 0) res += 1.0 + line_expected[ arm_len] + line_expected[cycle_len - 2]; for (int i = 2; i < cycle_len; i++) { // arm // \ . // 0 -> 1 -> ... i - 2 // i + 1 -> i + 2 -> ... c-1 / int a = arm_len + i - 1; int b = cycle_len - 2; res += 1.0 + line_expected[a] + line_expected[b] - line_expected[i-1]; } res /= (cycle_len + arm_len); } }
The general case will allow many more complex things:
We already have enough tools to solve this case or any similar case. The restriction that all vertices have one outgoing edge means that all connected components in the graph will be like this, there is a single cycle at the end and that cycle can make the root of a tree (with edges inverted). Once again, we can try to first find the probabilities of each vertex. To find the probabilities, we can, once again, consider only the vertex and all the vertex that may have an influence on its probability:
We already know how to get the expected value of the vertices colored green in that image. And with this knowledge, we can get the probabilities of all the green vertices. armed_ring_expected[3][6] will give the sum of all green probabilities, armed_ring_expected[3][5] will give the sum of all green probabilities except the last one. Therefore: armed_ring_expected[3][6] - armed_ring_expected[3][5] is the probability of the last green node. You can repeat this for every node in the graph: Find the distance between this node and a cycle (arm length) and the size of the cycle, then do armed_ring_expected[cycle_size][arm_length] -armed_ring_expected[cycle_size][arm_length-1] to find the probability. An exception is when arm_length = 0, in that case the vertex is purely in a cycle, but we already know the expected value of the cycle and if we divide it by the cycle length, we get the probability (Because the expected value is the sum of the individual probabilities and in this case the probabilities are all equal).
To find the arm and cycle lengths starting at each node we can use the Tortoise and Hare algorithm (Floyd‘s cycle detection).
先脑补一下注释版的
https://en.wikipedia.org/wiki/Cycle_detection?setlang=zh-cn
def brent(f, x0): # main phase: search successive powers of two power = lam = 1 tortoise = x0 hare = f(x0) # f(x0) is the element/node next to x0. while tortoise != hare: if power == lam: # time to start a new power of two? tortoise = hare power *= 2 lam = 0 hare = f(hare) lam += 1 # Find the position of the first repetition of length λ mu = 0 tortoise = hare = x0 for i in range(lam): # range(lam) produces a list with the values 0, 1, ... , lam-1 hare = f(hare) # The distance between the hare and tortoise is now λ. # Next, the hare and tortoise move at same speed till they agree while tortoise != hare: tortoise = f(tortoise) hare = f(hare) mu += 1 return lam, mu;
You can make use of Floyd‘s
cycle-finding algorithm, also know as tortoise and hare algorithm.
The idea is to have two references to the list and move them at different speeds. Move one forward by 1
node
and the other by 2
nodes.
next
)
will become null
.Java function implementing the algorithm:
boolean hasLoop(Node first) { if(first == null) // list does not exist..so no loop either. return false; Node slow, fast; // create two references. slow = fast = first; // make both refer to the start of the list. while(true) { slow = slow.next; // 1 hop. if(fast.next != null) fast = fast.next.next; // 2 hops. else return false; // next node null => no loop. if(slow == null || fast == null) // if either hits null..no loop. return false; if(slow == fast) // if the two ever meet...we must have a loop. return true; } }
vector<double> p(n, -1.0); for (int i = 0; i < n; i++) { // Floyd's cycle detection algorithm (tortoise and hare) int tortoise = a[i]; int hare = a[a[i]]; while (tortoise != hare) { tortoise = a[tortoise]; hare = a[a[hare]]; //进入环 } int mu = 0; tortoise = i; while (tortoise != hare) { tortoise = a[tortoise]; hare = a[hare]; mu += 1; } int lam = 1; hare = a[tortoise]; while (tortoise != hare) { hare = a[hare]; lam += 1; } // a cycle of length "lam" starts after "mu" steps. if (mu == 0) { p[i] = armed_ring_expected[lam][0] / lam; } else { p[i] = armed_ring_expected[lam][mu] - armed_ring_expected[lam][mu -1]; } } return accumulate(p.begin(), p.end(), 0.0);
版权声明:本文为博主原创文章,未经博主允许不得转载。
有向图判环+拆解图求参会期望 SRM 660 Div1 Medium: Privateparty
原文地址:http://blog.csdn.net/acm_10000h/article/details/47301991