NOIP-R7

不是,这一次模拟赛这么抽象?

A

也是有了啊,第一题想的跟正解不一样,这里两个都讲。

自己:

很明显的,这个题一定和倍数之类的有关,于是一开始就想着直接在区间 \([l, r]\) 里开始跑筛,但是发现还是有重复或者说是非法情况,考虑到一个数的倍数个数的平方种情况里可能会出现 \(\gcd\) 不为当前数的问题,所以想到莫比乌斯反演,运用莫比乌斯反演准确求出 \(\gcd\) 为每个数的情况个数,不过这里也有猜的成分,还是看看正解,我的比较蒙的。

正解:

定义 \(A_d\)\(\gcd = d\) 的倍数的方案数,\(B_d\)\(\gcd = d\) 恰好的方案数,然后得到式子:

\[A_d = (m/d)^n \]

\[B_d = A_d - \sum_{i = 2}^{m/d}B_{i\times d} \]

其实跟莫比乌斯的大致方向是一样的,只不过这个更好理解与实现。

记得从小到大。

这里给的代码是莫比乌斯:

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
const int MOD = 1e9+7;
const int N = 1e6+100;

ll cnt[N], prime[N], tot, minu[N], moi[N];
bool check[N];

ll qpow (ll a, ll b) {
    ll res = 1;
    while (b) {
        if (b & 1) res = (res * a) % MOD;
        a = (a * a) % MOD;
        b >>= 1;
    }
    return res;
}

void xxs () {
    check[0] = check[1] = true;
    for (int i = 0;i <= N;i++) moi[i] = 1;
    for (int i = 2;i <= N;i++) {
        if (!check[i]) prime[++tot] = i, moi[i] = -1;
        for (int j = 1;j <= tot && i * prime[j] <= N;j++) {
            check[i * prime[j]] = true;
            if (!(i % prime[j])) { moi[i * prime[j]] = 0; break; }
            moi[i * prime[j]] = - moi[i];
        }
    }
}

void solve () {
	ll n, m, l, r; cin >> n >> m >> l >> r;
    xxs();
    ll ans = 0;    
    for (ll i = l;i <= r;i++) {
        ll len = (m/i) % MOD;
        ll tmp = 0;
        for (ll k = 1;k <= len;k++) {
            ll now = (ll)(m / (i*k)) % MOD;
            tmp = (ll)(tmp + (ll)moi[k] * qpow(now, n) % MOD) % MOD;
        }
        ans = (ll)(ans + tmp) % MOD;
    }
    cout << (ans+MOD)%MOD << "\n";
}

int main () {
	ios::sync_with_stdio(false);
	cin.tie(nullptr), cout.tie(nullptr);
	int _ = 1;
	while (_--) solve();
	return 0;
}

B

严重怀疑出题人脑子进水了,数据那么小,直接暴力出奇迹。

C

呃呃,其实吧,在写题解的时候,我忘记自己怎么写的了,哈哈哈哈。

其实不难发现,根据贪心我们可以想到求出 \(a_i \to b_i\) 得最小操作步数先,定义 \(c_i = (b_i - a_i + 4) \mod 4\)

然后根据山峰图可以想到答案就是:

\[\sum_{i = 1} ^ {n-1} max(c_i - c_{i+1}, 0) \]

发现答案就是这玩意的前缀和。

同时发现这个 \(c_i\) 不过是个摆设,真正的操作步数应该是 \(k_i = c_i \mod 4\)。(至于为什么,我想这就是数学吧)

于是考虑对 \(c_i\) 进行操作,发现可以通过修改一段区间内的 \(c_i\) 来达到减小答案步数的目的,于是手推出这三种情况:

  • \(d_l = 2, d_r = -3 \to ans - 1\)
  • \(d_l = 3, d_r = -2 \to ans - 1\)
  • \(d_l = 3, d_r = -3 \to ans - 2\)

这里 \(d_i = c_i - c_{i+1}\),不难发现就是差分。

考虑从大到小处理,开始贪心即可。

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
const int N = 1e5+100;
const int INF = 1e9+100;

ll n, a[N], b[N], minu[N], c[N];
ll res;
ll sum[N], dif[N];
ll now[N];

void solve (){
    cin >> n;
    for (int i = 0;i < n;i++) cin >> a[i];
    for (int i = 0;i < n;i++) cin >> b[i];
    int ret = 0;
    for (int i = 0;i < n;i++) {
        c[i] = (4 + b[i] - a[i]) % 4;
        ll d = c[i];
        if (i > 0) {
            d += 4 - c[i-1];
            d = d  %4;
        }
        dif[i] = d, res += d, sum[i] = res; 
    }
    for (int j = 3;j >= 0;j--) {
        for (int i = n-1;i >= 0;i--) {
            now[i] = sum[i];
            if (i + 1 < n) 
                now[i] = min(now[i], now[i+1]);
        }

        ll minn = 0;
        for (int i = 0;i < n;i++) {
            sum[i] -= minn;
            if (dif[i] == j && now[i] >= minn + 4) 
                sum[i] -= 4, res -= j, minn += 4;
        }
    }
    cout << res << "\n";
}

int main () {
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int _ = 1;
    while (_--) solve();
    return 0;
}