ABC297练习笔记

D - Count Subtractions

给定两个整数 \(A,B\),重复下列操作直到 \(A=B\)

  • \(A>B\),则令 \(A=A-B\)
  • 否则,令 \(B=B-A\)

求出一共进行多少次操作。 \(1\leq A,B\leq 10^{18}\)

sol.

可以发现和求解 \(\text{gcd}\) 特别像,当 \(A>B\) 时一共需要连续让 \(A\) 减小 \(\lfloor\dfrac{A}{B}\rfloor\) 次变为 \(A\mod B\)。复杂度 \(\mathcal O(\log A)\)

#include<bits/stdc++.h>
using namespace std;
long long a, b, ans;
int main(){
    cin >> a >> b;
    while(a && b){
        if(a > b){
            if(a%b == 0)ans += a/b-1;
            else ans += a/b;
            a %= b;
        }else{
            if(b%a == 0)ans += b/a-1;
            else ans += b/a;
            b %= a;
        }
    }
    cout << ans << endl;
    return 0;
}

E - Kth Takoyaki Set

\(n\) 种商品,第 \(i\) 种商品的价格为 \(a_i\),每种商品都有无限个。求出这些商品能够组合出来的第 \(k\) 小的价格。

\(1\leq n\leq 10,1\leq k\leq 1\times 10^5, 1\leq a_i\leq 10^9\)

sol.

假设已经求出了前 \(k-1\) 小的价格,分别是 \(p_1,...p_{k-1}\),并且设 \(p_0=0\)。那么 \(p_k\) 肯定等于某个 \(p_i(i\in [0,k-1])\) 加上某个 \(a_j\),并且这个组合没有作为前 \(k-1\) 小的价格。用 \(\text{set}\) 维护可能的价格的集合,集合大小为 \(\mathcal O(kn)\),复杂度就是 \(\mathcal O(kn\log kn)\)

#include<bits/stdc++.h>
using namespace std;
const int maxn = 15;
int n, k, a[maxn];
set<long long> st;
int main(){
    cin >> n >> k;
    for(int i = 1; i <= n; ++i)cin >> a[i], st.insert(a[i]);
    long long mn = 0;
    for(int i = 1; i <= k; ++i){
        mn = *st.begin(), st.erase(st.begin());
        for(int j = 1; j <= n; ++j)st.insert(mn + a[j]);
    }
    printf("%lld\n", mn);
    return 0;
}

F - Minimum Bounding Box 2

\(n\)\(m\) 的矩阵,随机选择 \(k\) 个格子染黑。一种状态的价值是包含所有黑格子的最小矩阵的面积。求出价值的期望。

\(n,m\leq 1000,k\leq nm\)

sol.

设包含所有黑格子的矩阵是 \(l1\times l2\) 的,面积 \(S = l1\times l2\),那么 \(S\choose k\) 就表示矩阵里有 \(k\) 个黑格子的方案数。但是不能保证这是最小的这种矩阵。

考虑容斥,无非就是左边少一列、右边少一列、上面少一行、下面少一行四种情况组合一下就可以了。复杂度 \(\mathcal O(2^4nm)\)

#include<bits/stdc++.h>
#define fp(i, a, b) for(int i = (a), ed = (b); i <= ed; ++i)
#define fb(i, a, b) for(int i = (a), ed = (b); i >= ed; --i)
using namespace std;
const int maxn = 1010, mod = 998244353;
int n, m, k, fac[maxn*maxn], ifac[maxn*maxn], ans;
inline int fpow(int a, int b, int ans = 1){
    for(; b; b >>= 1, a = 1ll*a*a%mod)if(b&1)ans = 1ll*ans*a%mod;
    return ans;
}
inline int C(int n, int m){
    if(n < m)return 0;
    return 1ll*fac[n]*ifac[m]%mod*ifac[n-m]%mod;
}
inline void init(int n){
    fac[0] = 1; fp(i, 1, n)fac[i] = 1ll*fac[i-1]*i%mod;
    ifac[n] = fpow(fac[n], mod-2); fb(i, n, 1)ifac[i-1] = 1ll*ifac[i]*i%mod;
}
inline int slv(int n, int m){
    int ans = 0;
    fp(l1, 1, n)fp(l2, 1, m){
        int res = 0;
        fp(s1, 0, 3)fp(s2, 0, 3){
            int d1 = __builtin_popcount(s1), d2 = __builtin_popcount(s2);
            if(d1 > l1 || d2 > l2)continue;
            int S = (l1-d1)*(l2-d2);
            if((d1+d2)%2 == 0)res = (res + C(S, k))%mod;
            else res = (res - C(S, k)+mod)%mod;
        }
        res = 1ll*(n-l1+1)*(m-l2+1)%mod * l1%mod * l2%mod * res%mod;
        ans = (ans + res)%mod;
    }
    return 1ll*ans*fpow(C(n*m, k), mod-2)%mod;
}
int main(){
    cin >> n >> m >> k;
    init(n*m), printf("%d\n", slv(n, m));
    return 0;
}

G - Constrained Nim 2

\(n\) 堆石头,第 \(i\) 堆初始有 \(a_i\) 个石头。\(A\)\(B\) 轮流从某一堆里面取石头,每次取的个数必须在 \([L,R]\) 内,当某个人不能操作时这个人输。问谁必胜?

\(n\leq 2\times 10^5,1\leq L\leq R\leq 10^9,1\leq a_i\leq 10^9\)

sol.

考虑只有一堆的情况,假设个数为 \(m\)

  • \(m < L+R\) 时,有 \(m-R<L\)

    • \(m\in [0,L)\),则先手必败,\(SG(m)=0\)
    • \(m\in [L,2L)\),则取一次之后 \(m < L\),因此 \(SG(m)=1\)
    • \(m\in [2L,3L)\),取一次之后 \(m\in[0,2L)\),因此 \(SG(m)=2\)
    • 归纳可得 \(SG(m) = \lfloor\dfrac{m}{L}\rfloor\)
  • \(m\in [L+R,2(L+R))\)

    • \(m\in[L+R,L+R+L)\),则取一次之后 \(m\in[L,L+R)\),因此 \(SG(m)=0\)
    • \(m\in [L+R+L,L+R+2L)\),则取一次之后 \(m\in[2L,L+R+L)\),因此 \(SG(m)=1\)
    • 归纳可得 \(SG(m)=\lfloor\dfrac{m-L-R}{L}\rfloor\)
  • 进而可以归纳得出 \(SG(m)=\lfloor\dfrac{m\mod (L+R)}{L}\rfloor\)

求出所有堆的 \(SG\) 的异或和即可,复杂度 \(\mathcal O(n)\)

#include<bits/stdc++.h>
using namespace std;
const int maxn = 200010;
int n, L, R, a[maxn];
int main(){
    ios::sync_with_stdio(false);
    cin >> n >> L >> R;
    int SG = 0;
    for(int i = 1; i <= n; ++i)cin >> a[i], SG ^= a[i]%(L+R)/L;
    if(SG)cout << "First" << endl;
    else cout << "Second" << endl;
    return 0;
}

Ex - Diff Adjacent

求出所有由正整数组成的、和为 \(n\) 的、任意相邻两项不相同的数列的长度之和。\(1\leq n\leq 2\times 10^5\)

sol.

第一次学到了怎么用二元生成函数……

首先和一元的 \(\text{GF}\) 类似,用 \(x\) 的指数表示所有所求数列的总和。求长度之和不能将长度放在系数里面,因为算的是和而不是乘积。这里的技巧就是用 \(y\) 的指数来表示当前的数列长度,最后让 \(\text{GF}\)\(y\) 求导,再另 \(y=1\) 即可。

具体来说,首先使用容斥,放入 \(i\)\(\text{GF}\) 就是 \(\sum_{j=1}(-1)^{j-1}x^{ij}y^j\)。因此可以设 \(g(x,y)=\sum_{i=1}\sum_{j=1}(-1)^{j-1}x^{ij}y^j,f(x,y)=\dfrac{1}{1-g(x,y)}\),则答案就是 \([x^n]f'_y(x,y)|_{y=1}\)

首先有 \(g(x,y)=\sum_{i=1}\dfrac{x^iy}{1+x^iy}\)。尝试化简 \(f'_y(x,y)\),有 \(f'_y(x,y)=\dfrac{g'_y(x,y)}{(1-g(x,y))^2} = \dfrac{\sum_{i=1}\dfrac{x^i}{(1+x^iy)^2}}{(1-\sum_{i=1}\dfrac{x^iy}{1+x^iy})^2}\)

那么 \(f'_y(x,y)|_{y=1}=\dfrac{\sum_{i=1}\dfrac{x^i}{(1+x^i)^2}}{(1-\sum_{i=1}\dfrac{x^i}{1+x^i})^2}\)。其中 \(\dfrac{x^i}{(1+x^i)^2}=\sum_{j=1}(-1)^{j-1}jx^{ij},\dfrac{x^i}{1+x^i}=\sum_{j=1}(-1)^{j-1}x^{ij}\)。写一个多项式求逆即可,复杂度 \(\mathcal O(n\log n)\)

#include<bits/stdc++.h>
#define fp(i, a, b) for(int i = (a), ed = (b); i <= ed; ++i)
#define fb(i, a, b) for(int i = (a), ed = (b); i >= ed; --i)
using namespace std;
const int maxn = 600010, mod = 998244353, g = 3, invg = (mod+1)/3;
int n, a[maxn], b[maxn], c[maxn], d[maxn];
int lim, hst, rev[maxn<<2];
inline int fpow(int a, int b, int ans = 1){
    for(; b; b >>= 1, a = 1ll*a*a%mod)if(b&1)ans = 1ll*ans*a%mod;
    return ans;
}
inline void init(int n){
	lim = 1, hst = 0;
	while(lim < n)lim <<= 1, ++hst;
	fp(i, 1, lim-1)rev[i] = (rev[i>>1]>>1)|((i&1)<<hst-1);
}
inline void ntt(int *a, const int &typ){
	fp(i, 1, lim-1)if(i > rev[i])swap(a[i], a[rev[i]]);
	for(int md = 1; md < lim; md <<= 1){
		int len = md<<1, Gn = fpow(typ ? invg : g, (mod-1)/len);
		for(int l = 0; l < lim; l += len){
			for(int nw = 0, Pow = 1; nw < md; ++nw, Pow = 1ll*Pow*Gn%mod){
				int x = a[l+nw], y = 1ll*Pow*a[l+nw+md]%mod;
				a[l+nw] = (x+y)%mod, a[l+nw+md] = (x-y+mod)%mod;
			}
		}
	}
	if(!typ)return;
	int inv = fpow(lim, mod-2);
	fp(i, 0, lim-1)a[i] = 1ll*a[i]*inv%mod;
}
int inv_ary[maxn<<2];
void get_inv(int *a, int *f, int n){
	if(n == 1)return f[0] = fpow(a[0], mod-2), void();
	get_inv(a, f, n+1>>1), init(n*2-1);
	fp(i, 0, n-1)inv_ary[i] = a[i];
	ntt(f, 0), ntt(inv_ary, 0);
	fp(i, 0, lim-1)f[i] = 1ll*f[i]*(2-1ll*f[i]*inv_ary[i]%mod+mod)%mod;
	ntt(f, 1);
	fp(i, n, lim-1)f[i] = 0;
	fp(i, 0, lim-1)inv_ary[i] = 0;
}
int main(){
    cin >> n, b[0] = 1;
    for(int i = 1; i <= n; ++i)fp(j, 1, n/i){
        if(j%2 == 0)a[i*j] = (a[i*j] - j + mod)%mod;
        else a[i*j] = (a[i*j]+j)%mod;
    }
    for(int i = 1; i <= n; ++i)fp(j, 1, n/i){
        if(j%2 == 0)b[i*j] = (b[i*j]+1)%mod;
        else b[i*j] = (b[i*j]-1+mod)%mod;
    }
    get_inv(b, c, n+1), init(2*n+1);
    ntt(a, 0), ntt(c, 0);
    fp(i, 0, lim-1)d[i] = 1ll*a[i]*c[i]%mod*c[i]%mod;
    ntt(d, 1), printf("%d\n", d[n]);
    return 0;
}
posted @ 2023-04-20 16:49  修电缆的建筑工  阅读(84)  评论(0编辑  收藏  举报