ABC 169 F题 + 171 F题 + 小结

一.前言&小结

自闭了, T 2 T2 T2 好像之前做过,于是直接开始莽,但印象不是特别深刻,所以中途换了很多次思路,很久后发现错了一个很 s b sb sb 东西(把代码改来改去,写的什么都乱了…)。 T 2 T2 T2 卡了 40 m i n + 40min^+ 40min+ 后总算搞出来了,切了 T 3 T3 T3, T 4 T4 T4 后一看 T 6 T6 T6 做过(而且还写了题解的),于是也直接开始莽,然后也因为印象不是特别深刻,思路错了,然后推出来一个 O ( n ⋅ ∣ s ∣ ) O (n \cdot |s|) O(ns) 的递推转移,一直卡在 T 6 T6 T6,结果 T 5 T5 T5 都没怎么看,直接自闭。

所以:

  1. 远古之前写的题解要经常翻翻,大多都是我当时不会的题,说不定我现在也不会
  2. 写代码之前思路一定要清晰,不然思路&代码改来改去,最后思路&代码都乱了。
  3. 不要死磕,时间越长,改动的次数越多,会对自己的代码越来越陌生,还不如重构一遍

二.题解

1. One Quadrillion and One Dalmatians

一句话题意: n = p 1 ⋅ 2 6 1 + p 2 ⋅ 2 6 2 + . . . + p k ⋅ 2 6 k ( ∀ p i ≠ 0 ) n = p_1 \cdot 26^{1}+p_2 \cdot 26^{2} +... +p_k \cdot 26^{k}(\forall p_i \neq 0) n=p1261+p2262+...+pk26k(pi=0)


最开始我没这样翻译,而是翻译成了 10 10 10 进制和 27 27 27 进制的转化,怎么调都调不过去。

这样翻译了之后,做法应该相当明显,从大到小开始判断,如果 p i = 0 p_i = 0 pi=0,则向高位借,如果高位变为 0 0 0,则继续向高高位借,直到高位不为零或者到了最高位。

#include <cmath>
#include <cstdio>
#include <iostream>
#define LL long long
#define ULL unsigned long long
using namespace std;

const int Maxn = 15;

ULL n;
ULL w[Maxn + 5], k[Maxn + 5];

int main () {
	cin >> n;
	w[1] = 1;
	for (int i = 2; i <= Maxn; i++) {
		w[i] = w[i - 1] * 26;
	}
	int Up = 0;
	for (int i = Maxn; i >= 1; i--) {
		if (n < w[i]) continue;
		if (Up == 0) Up = i;
		k[i] = n / w[i];
		n %= w[i];
	}
	for (int i = Up; i >= 1; i--) {
		int p = i;
		while (k[p] == 0 && k[p + 1] > 0) {
			k[p] += 26;
			k[p + 1]--;
			p++;
		}
	}
	for (int i = Up; i >= 1; i--) {
		if (k[i] != 0) printf ("%c", k[i] + 'a' - 1);
	}
	return 0;
}

2.Knapsack for All Subsets

我们有一个集合 T T T c a r d ( T ) = t card (T) = t card(T)=t, 全集为 I I I c a r d ( I ) = i card(I) = i card(I)=i,则 T T T 的超集个数为 2 i − t 2^{i - t} 2it(乘法原理易知)。

所以如果我们有一个集合 T T T ∑ x ∈ T x = s \sum_{x \in T}x = s xTx=s,则它对答案的贡献为 2 n − c a r d ( T ) 2^{n-card(T)} 2ncard(T)

看到 s ≤ 3000 s \leq 3000 s3000,想到用 d p dp dp 记录和为 i i i 的贡献和。

d p [ i ] [ j ] dp[i][j] dp[i][j] 表示在前 i i i 个数中选择,和为 j j j 的集合的贡献和。

d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − a [ i ] ] 2 dp[i][j] = dp[i - 1][j] + \frac{dp[i - 1][j - a[i]]}{2} dp[i][j]=dp[i1][j]+2dp[i1][ja[i]](多了一个数 a [ i ] a[i] a[i],所以贡献需要除以 2 2 2)。

第一维由于只有相邻的在转移,所以可以滚动。

#include <cmath>
#include <cstdio>
#include <iostream>
#include <algorithm>
#define int long long
#define LL long long
#define ULL unsigned long long
using namespace std;

const int Maxn = 3000;
const int Mod = 998244353;

int n, s, a[Maxn + 5];
int dp[Maxn + 5];

int quick_pow (int a, int b) {
	int res = 1;
	while (b) {
		if (b & 1) res = (res * a) % Mod;
		a = (a * a) % Mod; b >>= 1;
	}
	return res;
}

signed main () {
	cin >> n >> s;
	for (int i = 1; i <= n; i++)
		cin >> a[i];
	
	dp[0] = quick_pow (2, n);
	for (int i = 1; i <= n; i++) {
		for (int j = s; j >= a[i]; j--)
			dp[j] = (dp[j] + dp[j - a[i]] * (Mod / 2 + 1)) % Mod;
	}
	cout << dp[s];
	return 0;
}

3. Strivore

好心人的提示

在字符串 s s s 中插入 n n n 个小写字母,就相当于在 n + s . l e n g t h n+s.length n+s.length 个格子里面填入小写字母,要求其存在为 s s s 的子序列(不一定要连续)。

先确定 s s s 第一个字母所在的位置(大致思路),假设在位置 i i i 处,( 0 < i ≤ n + 1 0 < i \leq n+1 0<in+1), i i i 前面的空格每一个都有 26 26 26 种情况,总共 ( 2 6 i − 1 ) (26^{i-1}) (26i1)种情况, i i i 后面的空格,先选 s . l e n g t h − 1 s.length-1 s.length1 各格子放入 s 的其他字母,有 ( C k − i l e n − 1 ) (C_{k-i}^{len-1}) (Ckilen1)种方法,然后每个 s 串字母后面不选相同的,用于去重,有 ( 2 5 k − i − l e n + 1 ) (25^{k-i-len+1} ) (25kilen+1) 种情况。


不要问我 T a Ta Ta 在讲什么,我也没太看懂,但只要知道思路就行了

在这里插入图片描述

为了证明其正确性,我们仅需要证明这样的方法 不重不漏


不重

十分简单,第二个数列中的第 i i i 个空一定不是 s [ 1 ] s[1] s[1],所以一定不会重复


不漏

灵感来自 yxc大巨佬我不是在打广告啊

我们可以利用集合的思想来解决

在这里插入图片描述
一目了然,每一个区域对应着枚举的 i i i所对应的方案数,所以没有漏掉任何情况


60 p t s 60pts 60pts

逆元加上快速幂乱搞

#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define LL long long
#define ULL unsigned long long
using namespace std;

template <typename T> int read (T &x) {x = 0; T f = 1;char tem = getchar ();while (tem < '0' || tem > '9') {if (tem == '-') f = -1;tem = getchar ();}while (tem >= '0' && tem <= '9') {x = (x << 1) + (x << 3) + tem - '0';tem = getchar ();}x *= f; return 1;}
template <typename T> void write (T x) {if (x < 0) {x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
template <typename T> T Max (T x, T y) { return x > y ? x : y; }
template <typename T> T Min (T x, T y) { return x < y ? x : y; }
template <typename T> T Abs (T x) { return x > 0 ? x : -x; }

const int Maxn = 1e6;
const LL Mod = 1e9 + 7;

int n;
LL fac[Maxn * 2 + 5], w1[Maxn * 2 + 5], w2[Maxn * 2 + 5], inv_fac[Maxn * 2 + 5];
string s;

void exgcd (LL a, LL b, LL &x, LL &y) {
    if (b == 0) {
        x = 1; y = 0;
        return;
    }
    exgcd (b, a % b, y, x);
    y -= a / b * x;
}

LL inv (LL a) {
    LL x, y;
    exgcd (a, Mod, x, y);
    x = ((x % Mod) + Mod) % Mod;
    return x;
}

LL C (LL a, LL b) {
    return fac[b] * inv_fac[a] % Mod * inv_fac[b - a] % Mod;
}

int main () {
    cin >> n >> s;
    fac[0] = 1; w1[0] = 1; w2[0] = 1;
    for (int i = 1; i <= n + s.length () + 1; i++) {
        fac[i] = fac[i - 1] * i;
        fac[i] %= Mod;
        w1[i] = w1[i - 1] * 26;
        w1[i] %= Mod;
        w2[i] = w2[i - 1] * 25;
        w2[i] %= Mod;
    }
    
    inv_fac[n + s.length ()] = inv (fac[n + s.length()]);
    for(int i = n + s.length () - 1; i >= 0; i--) 
        inv_fac[i] = inv_fac[i + 1] * (i + 1) % Mod;
    
    LL ans = 0;
    for (int i = 1; i <= n + 1; i++) {
        ans += w1[i - 1] * C (s.length () - 1, n + s.length () - i) % Mod * w2[n - i + 1] % Mod;
        ans %= Mod;
    }
    cout << ans;
	return 0;
}

发现 n ∈ [ 1 , 1 e 6 ] n \in [1, 1e6] n[1,1e6],要卡 O ( n ∗ l o g 2 n ) O(n * log_2n) O(nlog2n),所以只有打表打出幂。

但是还有一个问题,阶乘的逆元怎么求呢?


引入一个 N B NB NB 的东西,阶乘逆元

a ! x ≡ 1 ( m o d p ) ( a = i ) a!x \equiv 1 \pmod {p} (a = i) a!x1(modp)(a=i)
a ! i ∗ y ≡ 1 ( m o d p ) \frac {a!}{i} * y \equiv 1 \pmod p ia!y1(modp)
y = i x y = ix y=ix

即证
a ! i ∗ i x ≡ 1 ( m o d p ) \frac {a!} {i} *ix \equiv 1 \pmod p ia!ix1(modp)

( a − 1 ) ! ∗ i x ≡ 1 ( m o d p ) (a-1)! *ix \equiv 1 \pmod p (a1)!ix1(modp)

a ! x ≡ 1 ( m o d p ) a!x \equiv1 \pmod p a!x1(modp)

所以 ( a − 1 ) ! (a - 1)! (a1)! 的逆元为 i x ix ix


#include <cmath>
#include <stack>
#include <queue>
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <algorithm>
#define LL long long
#define ULL unsigned long long
using namespace std;

template <typename T> int read (T &x) {x = 0; T f = 1;char tem = getchar ();while (tem < '0' || tem > '9') {if (tem == '-') f = -1;tem = getchar ();}while (tem >= '0' && tem <= '9') {x = (x << 1) + (x << 3) + tem - '0';tem = getchar ();}x *= f; return 1;}
template <typename T> void write (T x) {if (x < 0) {x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}
template <typename T> T Max (T x, T y) { return x > y ? x : y; }
template <typename T> T Min (T x, T y) { return x < y ? x : y; }
template <typename T> T Abs (T x) { return x > 0 ? x : -x; }

const int Maxn = 1e6;
const LL Mod = 1e9 + 7;

int n;
LL fac[Maxn * 2 + 5], w1[Maxn * 2 + 5], w2[Maxn * 2 + 5], inv_fac[Maxn * 2 + 5];
string s;

void exgcd (LL a, LL b, LL &x, LL &y) {
    if (b == 0) {
        x = 1; y = 0;
        return;
    }
    exgcd (b, a % b, y, x);
    y -= a / b * x;
}

LL inv (LL a) {
    LL x, y;
    exgcd (a, Mod, x, y);
    x = ((x % Mod) + Mod) % Mod;
    return x;
}

LL C (LL a, LL b) {
    return fac[b] * inv_fac[a] % Mod * inv_fac[b - a] % Mod;
}

int main () {
    cin >> n >> s;
    fac[0] = 1; w1[0] = 1; w2[0] = 1;
    for (int i = 1; i <= n + s.length () + 1; i++) {
        fac[i] = fac[i - 1] * i;
        fac[i] %= Mod;
        w1[i] = w1[i - 1] * 26;
        w1[i] %= Mod;
        w2[i] = w2[i - 1] * 25;
        w2[i] %= Mod;
    }
    
    inv_fac[n + s.length ()] = inv (fac[n + s.length()]);
    for(int i = n + s.length () - 1; i >= 0; i--) 
        inv_fac[i] = inv_fac[i + 1] * (i + 1) % Mod;
    
    LL ans = 0;
    for (int i = 1; i <= n + 1; i++) {
        ans += w1[i - 1] * C (s.length () - 1, n + s.length () - i) % Mod * w2[n - i + 1] % Mod;
        ans %= Mod;
    }
    cout << ans;
	return 0;
}
posted @ 2021-07-09 14:33  C2022lihan  阅读(9)  评论(0编辑  收藏  举报