Round B APAC Test 2017 Problem D. Sherlock and Permutation Sorting

Problem D. Sherlock and Permutation Sorting

Sherlock and Watson have already been introduced to sorting in their computer programming course. Now, Watson has always been curious about parallel computing and wants to sort a permutation of the integers 1 through N by breaking it into chunks, sorting the chunks individually, and then concatenating them. 

For a permutation p1, p2, ..., pN, a chunk is a contiguous subarray of the permutation: i.e., a sequence of elements pi, pi + 1, ..., pj, for the elements at indexes i and j such that 1 ≤ i ≤ j ≤ N

Watson wants to partition his permutation into an ordered list of one or more chunks, without changing the order that the elements are in, in such a way that each element of the permutation is in exactly one chunk, and all elements in a chunk are smaller than all elements in any later chunk.
For example, for the permutation [2, 1, 3, 5, 4], these are the only four legal ways for Watson to break it into chunks: [[2, 1, 3], [5, 4]] or [[2, 1], [3, 5, 4]] or [[2, 1], [3], [5, 4]] or [[2, 1, 3, 5, 4]]. Watson is happiest when there are as many chunks as possible; we denote the maximum number of chunks for a permutation p as f(p). In this example, the maximum number of chunks is 3.

Watson wants to consider all permutations p of the numbers 1 through N, and find thesum of squares of f(p). Watson knows Sherlock might come in handy and comes to him for help, but Sherlock is as clueless as Watson and asks you for help. As the sum of squares can be large, please find it modulo M.

Input

The first line of the input gives the number of test cases, TT test cases follow. Each test case consists of one line with two integers N and M.

Output

For each test case, output one line containing Case #x: y, where x is the test case number (starting from 1) and y is the sum of squares of f(p) for all permutations p of sizeN, modulo M.

Limits

1 ≤ M ≤ 109.

Small dataset

1 ≤ T ≤ 100.
1 ≤ N ≤ 100.

Large dataset

1 ≤ T ≤ 20.
1 ≤ N ≤ 5000.

Sample


Input 
 

Output 
 
3
1 2
2 4
3 7

Case #1: 1
Case #2: 1
Case #3: 6

In Case 1, there is only one permutation.  f([1]) * f([1]) % 2  = 1. 

In Case 2, there are two permutations.
f([1, 2])  = 2.
f([2, 1])  = 1.
(22 + 12) % 4  = 1.

In Case 3, there are six permutations.
f([1, 2, 3])  = 3.
f([1, 3, 2])  = 2.
f([2, 1, 3])  = 2.
f([2, 3, 1])  = 1.
f([3, 1, 2])  = 1.
f([3, 2, 1])  = 1.
(32 + 22 + 22 + 12 + 12 + 12) % 7  = 6.


Soultion

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include <stack>
#include <list>
#include <map>
#include <set>
#include <iterator>
#include <unordered_set>
#include <unordered_map>
#include <algorithm>
using namespace std;

typedef long long ll;

const int MAXN = 5e3+1;
ll prim[MAXN], sumf[MAXN], sqrsum[MAXN], fact[MAXN];

void compute(int N, int M)
{
    memset(fact, 0, sizeof(prim));
    memset(prim, 0, sizeof(prim));
    memset(sumf, 0, sizeof(prim));
    memset(sqrsum, 0, sizeof(prim));

    fact[0] = 1, prim[0] = 0, sumf[0] = 0, sqrsum[0] = 0;

    for (int n = 1; n <= N; n++)
        fact[n] = (n * fact[n-1]) % M;

    for (int n = 1; n <= N; n++)
    {
        prim[n] = fact[n];
        for (int i = 1; i < n; i++)
        {
            prim[n] -= (fact[i] * prim[n-i]) % M;
            if (prim[n] < 0)
                prim[n] += M;
        }
    }
    for (int n = 1; n <= N; n++)
    {
        for (int i = 1; i <= n; i++)
        {
            sumf[n] += (((sumf[n-i] + fact[n-i]) % M) * prim[i]) % M;
            if (sumf[n] >= M)
                sumf[n] -= M;
            sqrsum[n] += (((sqrsum[n-i] + (2 * sumf[n-i]) % M + fact[n-i]) % M) * prim[i]) % M;
            if (sqrsum[n] >= M)
                sqrsum[n] -= M;
        }
#ifdef TEST
        printf("%d prim %lld sumf %lld sqrsum %lld\n", n, prim[n], sumf[n], sqrsum[n]);
#endif
    }
}

int main(int argc, char* argv[])
{
#ifndef TEST
    if (argc != 2)
    {
        cout << "Invalid input" << endl;
        return 1;
    }

    string input = argv[1];  
    string output = input.substr(0, input.length() - 2) + "out";  
    freopen(input.c_str(), "r", stdin);
    freopen(output.c_str(), "w", stdout);  
#endif

    int T;
    int N, M;

    cin >> T;
    for (int i = 1; i <= T; i++)
    {
        cin >> N >> M;

        compute(N, M);

        cout << "Case #" << i << ": " << sqrsum[N] << endl;
    }

    fclose(stdin);
    fclose(stdout);

    return 0;
}


Note

Since N is too large to enumerate all permutations, we need to come up with a clever way to compute the final answer. There are several hints in the problem that we can pick up on. Firstly there can be at most N distinct values of f(p). But there are N! different permutations. This means that a lot of permutations are going to have the same value of f(p). Thus it seems reasonable to try and find some way to compute the number of permutations which have the same value f(p).

Our second hint comes from the statement "all elements in a chunk are smaller than all elements in any later chunk". Trying some permutations out on pen and paper gives us the following claim —

If there is a valid way to divide a permutation into chunks, then there must exists a right most chunk. Moreover, if the size of the right most chunk is X, then it must consist of the largest X elements of the permutation.

The first part of the above statement is trivial. The second part can be proven by way of contradiction by trying to partition the permutation in a way where at least one element in the rightmost chunk is not one of the largest X elements. Let this smallest element in the right most chunk be i. If i is in the right most chunk, then one of the largest X elements j is to the left. Since j > i, this means that this is not a valid way to partition the permutation into chunks.

But we don't just want to partition the permutation into chunks but we want the maximum number of chunks. To take this into consideration, let'sdefine the notion of a primitive right most chunk. The right most chunk of size X of some partition of the permutation is said to be primitive, if it does not contain a proper suffix of length Y < X which also consists of the Y largest elements i.e. if any suffix of a right most chunk C is not a primitive chunk, then C is said to be a primitive chunk.

Why have we defined such a complex notation? This is motivated by the fact that if a right most chunk contains a suffix of size Y which contains the largest Y elements of the permutation, then we can split the current chunks into two chunks of size X - Y and Y such that this will also be a valid partition and the number of chunks in our partition will increase by 1.

Thus we have another lemma —

If a permutation p is divided into f(p) number of chunks, then the right most chunk in this partition must necessarily be a primitive chunk!

Now, let's use the tools at hand to make our third observation to crack this problem.

Let's assume that the size of the primitive chunk in a maximum partition of a permutation p of size N has size X. We know that this consists of the largest X elements of the permutation of size N. We should notice that all the chunks on the left are just the maximum way to partition a permutation p' where p' is the suffix of length N  -  X of the permutation p.

So what does this mean? We have a recursive way to partition a given permutation into the maximum number of chunks! Start from the right and find the right most index i such that p[i... N] contains the largest N - i + 1 elements of the permutation. This must necessarily be the primitive chunk of the permutation p. Thus we can recursively compute the chunks for p[1... i - 1] and append p[i... N] to get the maximum way to partition p! Great, but we can make an even more powerful observation. You don't necessarily have to append p[i... N] to the chunks of p[1... i - 1]. In fact any primitive chunk of size N - i + 1 can be appended to p[1... i - 1] and the value of f(p) for each of these permutations will be the same!

Let's have a look at these statements using some more formal notation.

Define prim(n) to be the number of primitive chunks of size n. Observe that value of prim(n) is independent of the actual values of set but only depends on the size of the set i.e. number of primitive chunks consisting of {1, 2, 3} is the same as number of primitive chunks consisting of {100, 101, 102}.

Define sum_f(n) to be the sum of f(p) of all permutations p of size n. Using our observations from above, we can create a recurrence relation for sum_f(n) in terms of prim()

How did we get this recurrence? Let the size of the primitive chunk in optimal partition be n - i. There are prim(n - i) ways to choose such a primitive chunk consisting of the n - i largest elements. If we choose any permutation p' of size i, it can be a prefix of permutations with primitive chunks of size n - i. For each such permutation p', we have the relation f(p) = f(p') + 1.

Thus,

There are i! number of permutations of size i and each of them can be append with one of prim(n - i) primitive chunks. Thus 

If we can compute prim(n) for all possible values of n, we can compute the sum of all values of f(p) for permutations of size n in  time.

Prim(n) can be computed by the following recurrence relation

Prim(0) = 0

This also takes  time. But the question is asking us to compute the sum of the squares of all f(p)! We will take care of this by using some math to build a third and final recurrence :)

Now it is time to make the final observation and make a final notation.

Let sqrsum(n) denote the sum of squares of f(p) over all permutations p of size n

We know that f(p) = f(p') + 1 where p' is the prefix of p after the primitive chunk of size (n - i) is removed. Squaring both sides, we get

f(p)2 = f(p')2 + 2 * f(p') + 1

If we fix the primitive chunk C of the permutation p and take the sum of over all valid p', we would get

For any primitive chunk of size n - i, the RHS of the above equation remains that sum.

Thus

If we take the sum over all possible primitive chunk sizes we will get sqrsum(n)

Thus

Thus we have our final recurrence relation.

This can also be computed in .

Thus our final answer is sqrsum(N).


Reference

https://code.google.com/codejam/contest/5254487/dashboard#s=p3

http://codeforces.com/blog/entry/46812

http://codeforces.com/blog/entry/46881

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EnjoyCodingAndGame

愿我的知识,成为您的财富!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值