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

题意: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);

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-11 01:34:16

有向图判环+拆解图求参会期望 SRM 660 Div1 Medium: Privateparty的相关文章

HDU 5154 Harry and Magical Computer 有向图判环

题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=5154 题解: 有向图判环. 1.用dfs,正在访问的节点标记为-1,已经访问过的节点标记为1,没有访问过的节点标记为0,如果访问到-1的节点说明说有环. 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<cmath> 5 using namespace std; 6 t

COJ 3012 LZJ的问题 (有向图判环)

传送门:http://oj.cnuschool.org.cn/oj/home/problem.htm?problemID=1042 试题描述: LZJ有一个问题想问问大家.他在写函数时有时候很头疼,如他写了这样几个函数: void f1(){   f2();   f3();}void f2(){   f3();}void f3(){   f1();}LZJ发现他无论怎么调换函数的位置,编译器总是不能通过编译,因为编译器规定调用的函数必须在当前函数之前写.还有一种情况是这样的:void f4(){

CodeForces 937D 936B Sleepy Game 有向图判环,拆点,DFS

题意: 一种游戏,2个人轮流控制棋子在一块有向图上移动,每次移动一条边,不能移动的人为输,无限循环则为平局,棋子初始位置为$S$ 现在有一个人可以同时控制两个玩家,问是否能使得第一个人必胜,并输出一个解,否则判断是否能平局 题解: 看到这个题首先我想到了强连通分量,但是事实证明求出强连通分量,缩点对解决问题没有什么帮助.... 能写一些看似正确的算法,但其实是假算法来的.. ........... 所以应该先分析策略,肯定是能赢就赢,不能赢就求平局,最后才算输 平局很好判断,有向图上,从$S$点

牛客练习赛11 B trie树+拓扑判环 E 分治求平面最近点对

牛客练习赛11 B  假的字符串题意:给定n个字符串,互不相等,你可以任意指定字符之间的大小关系(即重定义字典序),求有多少个串可能成为字典序最小的串,并输出它们. tags:好题 对于一个字符串, 1]如有其它字符串是它的前缀,那肯定不可能.这个直接用字典树处理就可以. 2]但如果以这个字符串为最小,怎么判定其它字符串不会矛盾呢? 其实矛盾的情况详细一点说是: 比如要以  abcd 为最小, 但又有另一个字符串 aba ,这就矛盾了. 对这种情况,在跑字典树的时候,我们对有相同父亲结点的多个儿

有向图判环

有这样一道编程面试题,给一个有向图的邻接矩阵,判别它是否有环. 题目麻烦在给的邻接矩阵是以 ‘字符输入流’ 的形式给出的,所以将其处理成数字形式的是首先要做的工作. 得到邻接矩阵之后,进行拓扑排序即可.假如能完成拓扑排序那就无环,如果不能,那就是有环. 样例输入: [[0, 1, 0], [0, 0, 1], [1, 0, 0]] [[0, 0, 0, 1, 0], [1, 0, 0, 0, 0], [0, 0, 0, 1, 1], [0, 0, 0, 0, 0], [0, 1, 0, 0, 0

CodeForces 659E New Reform (图的遍历判环)

Description Berland has n cities connected by m bidirectional roads. No road connects a city to itself, and each pair of cities is connected by no more than one road. It isnot guaranteed that you can get from any city to any other one, using only the

floyd判环算法(龟兔赛跑算法)

floyd判环算法(龟兔赛跑算法) 注意,这个算法是用来判断一条链+一条环的图,环的长度或者环与链的交界处的,所以此floyd非彼floyd(虽然都是一个人想出来的). (图不是我的) 如果只要求环的长度的话,只要让h和t相遇,然后再让h跑一圈,同时计算出步数就行了. 如果要算出链和环的交界点呢?首先,指针h和t同时从S出发,速度一个为2,一个为1(不要在意细节).当t走到链和环的交界点时,在右边的ht的距离等于st的距离.设st的距离为x,在左边的ht距离为y,那么环的长度就是x+y.现在让h

SDNU 1089.拓扑排序(拓扑判环小顶堆)

Description 给定一个有向图,若图无环,则将其进行拓扑排序并输出,否则输出IMPOSABLE. Input 第一行为两个整数n(1<=n<=1000).m(1<=m<=100000): 之后m行,每行两个整数a.b(0 < a, b <= n)表示一条从a到b的有向边. Output 若存在环,输出IMPOSABLE,否则输出一行用一个空格隔开的拓扑排序的结果,若存在多个结果,输出字典序最小的. Sample Input 5 4 1 2 2 3 3 4 4 5

POJ 2240 Arbitrage (spfa判环)

Arbitrage Arbitrage is the use of discrepancies in currency exchange rates to transform one unit of a currency into more than one unit of the same currency. For example, suppose that 1 US Dollar buys 0.5 British pound, 1 British pound buys 10.0 Frenc