BZOJ1101:「POI2007」Zap 题解

主要思路

这道题是一道很好的题,包含了数论里面的很多技巧。

首先,这是一道多组询问的题,询问数量非常的大,遂放弃使用欧拉函数来求。考虑可以使用莫比乌斯函数的性质来搞,开始从这方面分析。

复习一下莫比乌斯函数的定义:

\[ \mu(x) = \begin{cases} 0, 在x中存在指数大于一的质因子 \\ 1, 在x中质因子指数都为1且质因子个数为偶数 \\ -1, 在x中质因子质数都为1且质因子个数为奇数 \end{cases} \]

我们观察一下答案式子:\(\sum_{i,j} [gcd(x,y)=d]\)。我们先把\(gcd\)部分进行变换:

\[gcd(\frac{x}{d}, \frac{y}{d}) = 1\]

显然,这样的对数为\( \lfloor \frac{x}{d} \rfloor*\lfloor \frac{y}{d} \rfloor \)。我们考虑容斥:因为如果暴力累加的话,会多出很多不该存在的部分,比如说又是2的倍数又是3的倍数的部分。

所以最后答案式为:

\[\sum_{i=1}^{min\{a,b\}} \mu(i)*\lfloor \frac{x}{i} \rfloor*\lfloor \frac{y}{i} \rfloor \]

这时有个技巧,我们可以证明在整数段\([x, min\{ \lfloor \frac{a}{\lfloor \frac{a}{x} \rfloor} \rfloor, \lfloor \frac{b}{\lfloor \frac{b}{x} \rfloor} \rfloor \} ]\)上,\(\lfloor \frac{x}{i} \rfloor*\lfloor \frac{y}{i} \rfloor\)的结果是一样的,直接前缀和与之相乘即可。最后这样的段最坏情况为\(O(\sqrt{a}+\sqrt{b})\),所以时间复杂度比较低。

代码

// BZ1101.cpp
#include <bits/stdc++.h>
#define ll int
using namespace std;
const int MAX_N = 5e4 + 200;
ll mobi[MAX_N], sum[MAX_N], n;
bool vis[MAX_N];
int main()
{
    for (int i = 1; i < MAX_N; i++)
        mobi[i] = 1;
    for (int i = 2; i < MAX_N; i++)
    {
        if (vis[i])
            continue;
        mobi[i] = -1;
        for (int j = 2; i * j < MAX_N; j++)
        {
            vis[i * j] = true;
            if (j % i == 0)
                mobi[i * j] = 0;
            else
                mobi[i * j] *= -1;
        }
    }
    for (int i = 1; i < MAX_N; i++)
        sum[i] = sum[i - 1] + mobi[i];
    scanf("%d", &n);
    while (n--)
    {
        int a, b, k, gx;
        scanf("%d%d%d", &a, &b, &k);
        a /= k, b /= k;
        int ans = 0;
        for (int i = 1; i <= min(a, b); i = gx + 1)
        {
            gx = min(a / (a / i), b / (b / i));
            ans += (sum[gx] - sum[i - 1]) * (a / i) * (b / i);
        }
        printf("%d\n", ans);
    }
    return 0;
}

Codeforces 451E:Devu and Flowers 题解

主要思路

这道题需要多重集的知识,如果没有学习请看这篇博客的多重集部分。

很明显,题意要求我们求出多重集的组合数,且为“增强版组合数”。所以,我们根据公式,使用二进制分解的方法来求出即可。这是一道比较裸的多重集组合数问题。

代码

// CF451E.cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int mod = 1e9 + 7, MAX_N = 25;
ll arr[MAX_N], n, inv[MAX_N], m;
ll quick_power(ll bas, ll tim)
{
    ll ans = 1;
    while (tim)
    {
        if (tim & 1)
            ans = ans * bas % mod;
        bas = bas * bas % mod;
        tim >>= 1;
    }
    return ans;
}
ll C(ll a, ll b)
{
    if (a < 0 || b < 0 || a < b)
        return 0;
    a %= mod;
    if (a == 0 || b == 0)
        return 1;
    ll ans = 1;
    for (int i = 0; i < b; i++)
        ans = ans * (a - i) % mod;
    for (int i = 1; i <= b; i++)
        ans = ans * inv[i] % mod;
    return ans;
}
int main()
{
    scanf("%lld%lld", &n, &m);
    for (int i = 1; i <= n; i++)
        scanf("%lld", &arr[i]);
    inv[0] = 1;
    for (int i = 1; i < MAX_N; i++)
        inv[i] = quick_power(i, mod - 2);
    ll ans = 0;
    for (int stat = 0; stat < (1 << n); stat++)
        if (stat == 0)
            ans = (ans + C(n + m - 1, n - 1)) % mod;
        else
        {
            ll t = n + m, p = 0;
            for (int i = 0; i < n; i++)
                if ((stat >> i) & 1)
                    p++, t -= arr[i + 1];
            t -= (p + 1);
            if (p & 1)
                ans = (ans - C(t, n - 1)) % mod;
            else
                ans = (ans + C(t, n - 1)) % mod;
        }
    ans = (ans + mod) % mod;
    printf("%lld", ans);
    return 0;
}

source:Page 172,《算法竞赛进阶指南》李煜东著

POJ2480:Longge’s Problem 题解

主要思路和结论

给定一个\(n\),求\(\sum_{i=1}^{n} \gcd(i,n)\)。

题意非常的小清新,然而推式子的时候很伤心。我一开始使用打表的方法,一直没成功发现其中的规律。所以推式子的能力还是相当重要的。

我们来分析一下。对于\(i\in [1,n], gcd(i,n)=1\),它们对答案的贡献是\(\phi(n)\)。之后来看另一部分,也就是\(j\in [1,n], gcd(j,n)=k, k \neq 1\),我们可以除下,变成\(k*gcd(\frac{j}{k},\frac{n}{k})=k, gcd(\frac{j}{k},\frac{n}{k})=1\),其中根据欧拉定理,有\(\phi(k)\)个这样的\(gcd\),所以推出得\(k*\phi(k)\)。答案最后只需统计\(k\)的因数乘上\(\phi(因数)\)即可。

最后可以考虑使用 map 记录已计算过的欧拉值,可获得常数优化。

代码

// POJ2480.cpp
#include <cstdio>
#include <iostream>
#include <vector>
#include <map>
#define ll long long
using namespace std;
map<ll, ll> prefix;
vector<ll> getFracted(ll x)
{
    vector<ll> res;
    for (ll i = 1; i * i <= x; i++)
        if (x % i == 0)
        {
            res.push_back(i);
            if (i != x / i)
                res.push_back(x / i);
        }
    return res;
}
ll phi(ll x)
{
    ll ans = x;
    for (ll i = 2; i * i <= x; i++)
        if (x % i == 0)
        {
            ans -= ans / i;
            while (x % i == 0)
                x /= i;
        }
    if (x > 1)
        ans -= ans / x;
    return ans;
}
int main()
{
    ll n;
    while (scanf("%lld", &n) != EOF)
    {
        ll ans = 0;
        vector<ll> container = getFracted(n);
        ll siz = container.size();
        for (int i = 0; i < siz; i++)
        {
            if (!prefix.count(n / container[i]))
                prefix[n / container[i]] = phi(n / container[i]);
            ll e = prefix[n / container[i]];
            ans += e * container[i];
        }
        printf("%lld\n", ans);
    }
    return 0;
}

src:https://www.cnblogs.com/neopenx/p/4095691.html

U63113:入侵 – 又名 「XG 的数学题」

题目背景

众所周知,XG_Zepto 是一位来自 FZOI 的神仙。他觉得眼下的比赛太简单了,就用扫雷的时间出了一道数学题给 kal0rona。kal0rona 太菜了,看不懂题也不会写,之后求救于你了。

题目思路

很好的一道计数 DP,出自神仙组长 XG_Zepto 之手。首先,设\(f[i][j]\)为长度为\(i\),换弹次数为\(j\)时满足条件的排列数量。之后,考虑枚举第\(i\)个数字出现的位置\(k\),对于位置\(k\)后面的数字是不会有特殊贡献的(贡献为\((i-k)!\)),而对于前面的数,可以进行枚举前一段进行转移,一共有\({k-1}\choose{i-1}\)种选数字的方案和\(f[i-1][j-1\)种排列,乘起来即可,也就是:

\[ F[i][j] = \sum_{k = 1}^{i}f[k-1][j-1]* {{k-1} \choose {i-1}} *(i-1)! \]

可以化简,并用前缀和优化:

\[ F[i][j] = (i-1)!\sum_{k=1}^{i}\frac{f[k-1][j-1]}{(k-1)!} \]

代码

// U63113.cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX_N = 5050, mod = 998244353;
ll f[MAX_N], prefix[MAX_N], level[MAX_N], inv[MAX_N], n, m;
ll quick_pow(ll bas, ll tim)
{
    ll ans = 1;
    while (tim)
    {
        if (tim & 1)
            ans = ans * bas % mod;
        bas = bas * bas % mod;
        tim >>= 1;
    }
    return ans;
}
int main()
{
    scanf("%lld%lld", &n, &m);
    level[0] = 1, inv[0] = 1;
    for (int i = 1; i <= n; i++)
        level[i] = level[i - 1] * i % mod;
    inv[n] = quick_pow(level[n], mod - 2);
    for (int i = n; i >= 1; i--)
        inv[i - 1] = inv[i] * i % mod;
    for (int i = 0; i <= n; i++)
        prefix[i] = 1;
    for (int j = 1; j <= m; j++)
    {
        for (int i = j; i <= n; i++)
            f[i] = prefix[i - 1] * level[i - 1] % mod;
        for (int i = j; i <= n; i++)
            prefix[i] = f[i] * inv[i] % mod;
        for (int i = j + 1; i <= n; i++)
            prefix[i] = (prefix[i] + prefix[i - 1]) % mod;
    }
    printf("%lld", f[n]);
    return 0;
}

POJ1037:A decorative fence 题解

主要思路

这道题是计数类 DP 中一道非常好的题。

我先来简述一下“试填法”。很多做过数位 DP 的神仙们都可能比较熟悉这样的技巧,一次次相减尝试并统计答案。这就是试填法,特别是在一些递推形式的数位 DP 中会经常简单这样的方法。

那我们开始来分析一下这道题吧。首先,设\(F[i][j][0/1]\)为木板数为\(i\)时,最后放置的木板排名为\(j\)且处于低位(0)或高位(1)。可以写出递推式:

\[ F[i][j][0] = \sum_{h=j}^{i-1} F[i-1][h][1] \\ F[i][j][1] = \sum_{h = 1}^{j-1} F[i-1][h][0] \]

之后,我们可以试图找出第一块木板的排名,设\(last\)为上一块木板的排名,\(k\)为上一块木板是否为高位(0/1)。通过枚举高度并试填:

  • 当枚举到的方案数小于目前的方案余量,余量减去并继续枚举。
  • 当方案数大于方案余量,记下状态并跳出。

对于之后的\([2,n]\)块木板,道理是一样的。需要注意的是,我们的枚举顺序从\(n\)到\(1\),保证完整性。

代码

// POJ1037.cpp
#include <cstdio>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
ll n, m, T, f[25][25][2];
bool vis[25];
void initialize()
{
    f[1][1][0] = f[1][1][1] = 1;
    for (int i = 1; i <= 20; i++)
        for (int j = 1; j <= i; j++)
        {
            for (int k = j; k <= i - 1; k++)
                f[i][j][0] += f[i - 1][k][1];
            for (int k = 1; k <= j - 1; k++)
                f[i][j][1] += f[i - 1][k][0];
        }
}
int main()
{
    scanf("%d", &T);
    initialize();
    while (T--)
    {
        memset(vis, 0, sizeof(vis));
        scanf("%lld%lld", &n, &m);
        // to determine the first condition;
        ll last, k;
        for (int j = 1; j <= n; j++)
        {
            if (f[n][j][1] >= m)
            {
                last = j, k = 1;
                break;
            }
            else
                m -= f[n][j][1];
            if (f[n][j][0] >= m)
            {
                last = j, k = 0;
                break;
            }
            else
                m -= f[n][j][0];
        }
        vis[last] = true;
        printf("%lld", last);
        for (int i = 2; i <= n; i++)
        {
            k ^= 1;
            int j = 0;
            for (int len = 1; len <= n; len++)
            {
                if (vis[len])
                    continue;
                j++;
                if ((k == 1 && len > last) || (k == 0 && len < last))
                    if (f[n - i + 1][j][k] >= m)
                    {
                        last = len;
                        break;
                    }
                    else
                        m -= f[n - i + 1][j][k];
            }
            printf(" %lld", last);
            vis[last] = true;
        }
        puts("");
    }
    return 0;
}

CH3401:石头游戏题解

毒瘤思路

对,这道题是毒瘤题。首先,我们可以判定出,这些序列一定会在\(60s\)内全部从头开始,因为\(1,2,3,4,5,6\)的最大公倍数为\(60\)。之后,我们可以考虑把这些操作放入操作矩阵之中。操作矩阵的长宽都为\(n*m\)。具体的操作对应如下:

  • 在本题中,我们把一个二维的点映射为一个数,公式为\(num(i,j)=(i-1)m+j\)。
  • 对于把格子值设置为数字的操作:
    1. 首先把操作矩阵\(A[0][num(i,j)]\)设置为操作数。
    2. 之后,把矩阵\(A[num(i,j)][num(i,j)]\)设置为\(1\),这是为了使其在矩阵乘法中得到保留。
  • 而对于移动操作:
    • 如果是\(E\),那么把\(A[num(i,j)][num(i,j+1)]\)设置为一,以此类推

然后算出\(p=\lfloor \frac{m}{60} \rfloor, q = m \mod 60\),进行矩阵快速幂和连续乘法即可。

好毒瘤,建议不要写。

代码

// CH3401.cpp
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int MAX_N = 70;
ll N, M, t, act;
char opt[MAX_N][MAX_N];
string opts[MAX_N];
struct matrix
{
    ll mat[MAX_N][MAX_N], n, m;
    matrix() { memset(mat, 0, sizeof(mat)); }
    matrix(int n, int m)
    {
        this->n = n, this->m = m;
        for (int i = 1; i <= max(n, m); i++)
            mat[i][i] = 1;
    }
    matrix operator*(const matrix &tar) const
    {
        matrix ans = matrix();
        ans.n = n, ans.m = tar.m;
        for (int i = 0; i <= n; i++)
            for (int j = 0; j <= tar.m; j++)
                for (int k = 0; k <= m; k++)
                    ans.mat[i][j] += mat[i][k] * tar.mat[k][j];
        return ans;
    }
    matrix operator^(const int &tim) const
    {
        int ti = tim;
        matrix ans, bas = *this;
        for (int i = 0; i <= max(n, m); i++)
            ans.mat[i][i] = 1;
        ans.n = n, ans.m = m;
        while (ti)
        {
            if (ti & 1)
                ans = ans * bas;
            bas = bas * bas;
            ti >>= 1;
        }
        return ans;
    }
} P, Q, mats[70];
ll getPos(ll x, ll y) { return (x - 1) * M + y; }
int main()
{
    scanf("%lld%lld%lld%lld", &N, &M, &t, &act);
    for (int i = 1; i <= N; i++)
        for (int j = 1; j <= M; j++)
            cin >> opt[i][j], opt[i][j] -= '0';
    for (int i = 0; i < act; i++)
        cin >> opts[i];
    int p = t / 60, q = t % 60;
    matrix F;
    F.n = 0, F.m = getPos(N, M), F.mat[0][0] = 1;
    for (int tim = 1; tim <= 60; tim++)
    {
        matrix current;
        current.n = getPos(N, M), current.m = getPos(N, M);
        current.mat[0][0] = 1;
        for (int i = 1; i <= N; i++)
            for (int j = 1; j <= M; j++)
            {
                int pos = (tim - 1) % (opts[opt[i][j]].size());
                char op = opts[opt[i][j]][pos];
                switch (op)
                {
                case 'N':
                    if (i > 1)
                        current.mat[getPos(i, j)][getPos(i - 1, j)] = 1;
                    break;
                case 'S':
                    if (i < N)
                        current.mat[getPos(i, j)][getPos(i + 1, j)] = 1;
                    break;
                case 'E':
                    if (j < M)
                        current.mat[getPos(i, j)][getPos(i, j + 1)] = 1;
                    break;
                case 'W':
                    if (j > 1)
                        current.mat[getPos(i, j)][getPos(i, j - 1)] = 1;
                    break;
                case 'D':
                    break;
                default:
                    current.mat[0][getPos(i, j)] = op - '0';
                    current.mat[getPos(i, j)][getPos(i, j)] = 1;
                    break;
                }
            }
        mats[tim] = current;
    }
    P = mats[1];
    for (int i = 2; i <= 60; i++)
        P = P * mats[i];
    P = P ^ p;
    Q = q == 0 ? matrix(getPos(N, M), getPos(N, M)) : mats[1];
    for (int i = 2; i <= q; i++)
        Q = Q * mats[i];
    F = F * P * Q;
    ll ans = 0;
    for (int i = 1; i <= getPos(N, M); i++)
        ans = max(ans, F.mat[0][i]);
    printf("%lld", ans);
    return 0;
}