CF::Gym 100338/ 2007-2008 Winter Petrozavodsk Camp (ASC 30)

Posted on By 二价氢

试题PDF

B Signed Derangements

定义一个大小为n的Signed Derangements为n一个全排列随机加上正负号

求大小为n的Signed Derangements中,$$p_i \neq i$$的排列数

 

利用容斥原理即可,显然,$$f_1 = 1$$

 

接着,$$\forall m > 1 , f_m = 2\^m m! - \Sigma_{1\le j<m} C_m\^jf_(m-j)$$

于是可以计算出结果。注意需要使用高精度。

import java.io.*;
import java.math.*;
import java.util.*;
import java.text.*;
import java.lang.*;
public class Main{
    final static int maxn = 200 + 5;
    public static BigInteger bigPow(BigInteger a, int b) {
        BigInteger ret = new BigInteger("1");
        BigInteger x = a;
        while (b > 0) {
            if ((b & 1) == 1) {
                ret = ret.multiply(x);
            }
            x = x.multiply(x);
            b = b >> 1;
        }
        return ret;
    }
    public static void main(String[] args) {
        try{
            InputStream fin = new FileInputStream(new File("derangements.in"));
            Scanner cin = new Scanner(new BufferedInputStream(fin));
        BigInteger c[][] = new BigInteger[maxn][maxn];
        BigInteger frac[] = new BigInteger[maxn];
        BigInteger f[] = new BigInteger[maxn];
        c[0][0] = new BigInteger("1");
        frac[0] = new BigInteger("1");
        for (int i = 1; i < maxn; i++)
            c[0][i] = new BigInteger("0");
        for (int i = 1; i < maxn; i++) {
            frac[i] = frac[i - 1].multiply(new BigInteger("" + i));
            c[i][0] = new BigInteger("1");
            for (int j = 1; j < maxn; j++)
                c[i][j] = c[i - 1][j - 1].add(c[i - 1][j]);
        }
        f[1] = new BigInteger("1");
        for (int i = 2; i < maxn; i++) {
            f[i] = bigPow(new BigInteger("2"), i).multiply(frac[i]);
            for (int j = 1; j < i; j++) {
                f[i] = f[i].subtract(c[i][j].multiply(f[i - j]));
            }
            f[i] = f[i].subtract(new BigInteger("1"));
        }
        int n = cin.nextInt();
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(new FileOutputStream("derangements.out")));
        pw.println(f[n].toString());
        fin.close();
        pw.close();
        /*
        for (int i = 1; i < maxn; i++) {
            System.out.printf("\"%s\" ,\n", f[i].toString());
        }*/
        }catch(Exception e)
        {}
    }
}

D Currency Exchange

求$$[L,R]$$区间内,能用00112233445566778899组成的数字种数

显然是数位DP,首先,我们用三进制数$$(a_9a_8a_7a_6a_5a_4a_3a_2a_1a_0)_3$$表示每种数字出现了几次,则转移方程为

$$F_{len + 1,k,w + (1 << k)_3} = \Sigma F_{len,k’,w}$$

其中len表示当前数字位数,k表示最高位,w表示每个数字出现了几次。

之后就是传统数位DP

#include <bits/stdc++.h>
using namespace std;
const int maxn = 59049 + 5, maxbit = 21;
const int pow3[]={1,3,9,27,81,243,729,2187,6561,19683,59049};
long long f[maxbit][10][maxn];
int eachbit[maxbit];
int eachbit2[maxbit];
int ato3(int b[])
{
    int ret = 0;
    for (int i = 0; i < 10; i++)
        ret = ret + b[i] * pow3[i];
    return ret;
}
void ttoa(int b[], int num)
{
    for (int i = 0; i < 10; i++)
        b[i] = (num / pow3[i]) % 3;
}
long long getans(long long q)
{
    long long ret = 0;
    int totbit = 1;
    memset(eachbit2, 0, sizeof(eachbit2));
    while (q)
    {
        eachbit2[totbit++] = q % 10;
        q /= 10;
    }
    int eachbit3[maxbit];
    bool flg;
    for (int i = 0; i <= 9; i++) eachbit3[i] = 2;
    totbit--;
    for (int i = 1; i < totbit; i++)
        for (int j = 1; j < 10; j++)
            for (int l = 0; l < 59049; l++)
                ret += f[i][j][l];
//  cerr << ret << endl;
    for (int i = 1; i < eachbit2[totbit]; i++)
        for (int l = 0; l < 59049; l++)
            ret += f[totbit][i][l];
//  cerr << ret << endl;
//  eachbit3[eachbit2[totbit]]--;
    for (int i = totbit - 1; i > 0; i--)
    {
        eachbit3[eachbit2[i + 1]]--;
        for (int j = 0; j < eachbit2[i]; j++)
        {
            for (int l = 0; l < 59049; l++)
            {
                flg = true;
                ttoa(eachbit, l);
                for (int m = 0; m < 10; m++)
                    if (eachbit[m] > eachbit3[m])
                    {
                        flg = false;
                        break;
                    }
                if (flg)
                {
                    /*
                    if (f[i][j][l])
                    {
                        cerr << i << " " << j << " " << l;
                        cerr << " " << f[i][j][l] << endl;
                    }*/
                    ret += f[i][j][l];
                }
            }
        }
    }
    return ret;
}
int main()
{
#ifdef ONLINE_JUDGE
    freopen("exchange.in", "r", stdin);
    freopen("exchange.out", "w", stdout);
#endif
    long long l , r;
    f[0][0][0] = 1;
    for (int i = 0; i < 20; i++)
        for (int j = 0; j < 59049; j++)
        {
            ttoa(eachbit, j);
            long long lsum = 0;
            for (int k = 0; k <= 9; k++)
                lsum += f[i][k][j];
            /*
            if (lsum)
            {
                for (int k = 0; k <= 9; k++) cerr << eachbit[k];
                cerr << endl;
                cerr << i << " " << j << " " << lsum << endl;
                cin >> l;
            }*/
            for (int k = 0; k <= 9; k++)
                if (eachbit[k] < 2)
                {
                    eachbit[k]++;
                    f[i + 1][k][ato3(eachbit)] += lsum;
                    eachbit[k]--;
                }
        }
    cin >> l >> r;
    cout << getans(r + 1) - getans(l) << endl;
    return 0;
}

F $$\sqrt{Nim}$$

Nim游戏,每次可以取$$[1,\lfloor k\rfloor]$$个石子,问先手是否必胜。

可以枚举$$\lfloor k\rfloor$$的值,注意到数据只有$$10\^{12}$$,则$$\lfloor k\rfloor$$不会超过$$10\^6$$,必败态也不会超过$$10\^6$$,预处理之后回答即可。

#include <bits/stdc++.h>
using namespace std;
set<long long> s;
int main()
{
#ifdef ONLINE_JUDGE
    freopen("nim.in", "r", stdin);
    freopen("nim.out", "w", stdout);
#endif
    long long curr = 2;
    s.insert(1e16);
    s.insert(2);
    for (long long sq = 1; sq <= 1000000 + 5; sq++)
    {
        long long curr2 = curr + sq + 1;
        while (curr2 < (sq + 1) * (sq + 1))
        {
            s.insert(curr2);
            curr = curr2;
            curr2 = curr + sq + 1;
        }
    }
    long long n;
    cin >> n;
    if ((*s.lower_bound(n)) == n)
        printf("LOSE\n");
    else
        printf("WIN\n");
    return 0;
}