码迷,mamicode.com
首页 > 其他好文 > 详细

有向图判环+拆解图求参会期望 SRM 660 Div1 Medium: Privateparty

时间:2015-08-05 20:27:00      阅读:382      评论:0      收藏:0      [点我收藏+]

标签:图论期望   有向图判环   

题意:N个参加聚会,和一个数组a,ai表示第i个人讨厌的人,如果一个到聚会门口的时候发现他讨厌的人已经在聚会里面,则他不会参加聚会,否则他会参加聚会。ai==i表示他没有讨厌的人。N个人来的先后顺序是任意的,也就是说n个来的先后顺序构成的1到n的排列是任意的。问参加聚会的人的期望是多少?

Privateparty


Problem Statement

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.

Definition

  • ClassPrivateparty
  • Methodgetexp
  • Parametersvector<int>
  • Returnsdouble
  • Method signaturedouble getexp(vector<int> a)
(be sure your method is public)

Limits

  • Time limit (s)2.000
  • Memory limit (MB)256

Notes

  • Your solution is correct if the absolute error or the relative error is at most 10^-9.

Constraints

  • a will contain exactly n elements.
  • n will be between 1 and 50, inclusive.
  • Each element of a will be between 0 and n - 1, inclusive.

Test cases

    • a{ 0, 1 }
    Returns2.0
    There are two friends. As a[0]=0 and a[1]=1, each friend likes the other. Regardless of the order of invitations, both will always accept.
    • a{ 0, 0 }
    Returns1.5
    In this test case friend 1 dislikes friend 0. If Hero invites 0 first and 1 next, 0 will accept and then 1 will reject. If Hero invites 1 first and 0 next, 1 will accept (as 0 didn‘t accept yet) and then 0 will accept as well (because he likes 1).
    • a{ 0, 1, 1 }
    Returns2.5

    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.

    • a{ 0, 1, 1, 2 }
    Returns3.166666666666667
    • a{ 3, 2, 1, 0 }
    Returns2.0

题解

解:分开来计算每个人来参加聚会的期望,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.

Lines

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 f(n) that will tell us the expected number of friends that turn up when the graph is just a linear sequence of n friends.

For each friend, there is an 1n probability that the friend is the first friend picked. Let‘s imagine what happens for each of those cases:

技术分享

So we can try this for each node i. When a friend is selected first, they will surely accept the invitation, so the count increases by 1. If there was any friend that hated them, that friend will never accept the invitation. The key is we can from now on ignore that both of these friends exist and just calculate the expected values for the remaining structures, usually two new graphs that are linear sequences are created, we can use f() to solve them. We divide the results of each of the n cases by n, because the probability for each of them is 1n. Remember, the expected value is equal to the sum of each possible number multiplied by its probability. The base case is when n=1, when there is only one friend, they always get invited. The iterative implementation is like this:

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

Rings

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 n?2 friends making a linear graph like those in the last section. The expected value for a cycle is 1+f(n?2). Where f is the solution for the above section.

Armed rings

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 f() and also solve the armed ring problem with a smaller arm now.

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.

Y shape

We could use the same approach, this time make it an O(n3) state by it having 3 arms. There is an easy way, however, we can make use of the linearity of expectation. The expected value of a sum is equal to the sum of the expected values of each summand. In this case, we can consider the total number of friends that accept the invitation as a sum: For each friend, if friend accepts invitation add 1, else 0. Then the expected value is equal to the sum of each of those expected values. The definition of the expected value tells us that the expected value in this case is the probability that each friend is accepted. Note the probabilities are not necessarily independent of each other, but that doesn‘t matter. The linearity of expectation still applies.

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: f(6). From this we can know that the sum of the probabilities for the 6 nodes is f(6). Now consider this other linear sequence:

技术分享

We can use the same logic and say that f(8) is the sum of all probabilities for blue nodes. Now if we add f(6)+f(8) we would get close to the sum of all probabilities in the whole graph, but there would be some repeated probabilities, the lower arm. We can just subtract it: f(4), so we can just use : f(8)+f(6)?f(4) to find the sum of probabilities in the graph, that is to say the expected value of the whole graph.

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);
         
    }
}

Other combined configurations

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;

How to detect a loop in a linked list?

http://stackoverflow.com/questions/2663115/how-to-detect-a-loop-in-a-linked-list/2663147#2663147

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.

  • If the linked list has a loop they will definitely meet.
  • Else either of the two references(or their 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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!