T1 拼数

题意概括

给定一个长度为 LL (1L1061 \le L \le 10^6) 的字符串 ss,它由小写字母和数字组成,且至少包含一个 191 \sim 9 的数字。要求从 ss 中选取任意多个数字字符(每个字符最多使用一次),按任意顺序拼接成一个正整数。求能拼成的最大正整数。

题目分析

这是一个经典的贪心问题。我们的目标是构造一个值最大的正整数。一个整数的值由两个核心因素决定:它的位数(长度)和高位上的数字大小。

  1. 位数最大化: 为了使数值尽可能大,我们应该使用 ss所有可用的数字字符。如果舍弃任何一个数字,构成的数位数必然 \le 使用所有数字构成的数,因此其值通常更小(除非舍弃的是 00,但保留 00 总是优于或等于不保留 00)。

  2. 高位优先: 在位数相同(即使用所有 KK 个可用数字)的前提下,要使数值最大,必须将越大的数字放在越靠前(越高)的数位上。

DD 为从 ss 中提取出的所有数字字符构成的多重集(multiset)。我们的目标是用 DD 中的所有元素 d1,d2,,dkd_1, d_2, \dots, d_k (其中 k=Dk = |D|) 构造一个 kk 位数 NN,使其值最大。

根据贪心策略,我们应该将 DD 中的元素按非递增(降序)排列,得到序列 d1,d2,,dkd'_1, d'_2, \dots, d'_k,满足 d1d2dkd'_1 \ge d'_2 \ge \dots \ge d'_k

构造的数 N=d1d2dkN = d'_1d'_2\dots d'_k (这里表示字符串拼接) 即为最大值。

合法性(正整数): 题目保证 ss 至少包含一个 191 \sim 9 的数字。因此,提取出的数字集合 DD 必然包含至少一个非 00 字符。当我们将 DD 降序排序后,最大的元素 d1d'_1 必然 ’1’\ge \text{'1'} (即 d1’0’d'_1 \ne \text{'0'})。因此,拼接得到的数 NN 绝不会有前导 00(除非 N=0N=0,但本题保证 DD 不全为 00),且 NN 必然是正整数。

算法流程:

  1. 遍历输入字符串 ss
  2. 将所有数字字符('0''9')提取到一个新的字符串 dd 中。
  3. 将字符串 dd 按降序排序。
  4. 输出排序后的字符串 dd

复杂度分析:N=sN = |s|KKss 中数字的个数 (KNK \le N)。

  • 时间复杂度:O(N)O(N) 遍历 ss 提取数字 + O(KlogK)O(K \log K) 排序数字。由于 KNK \le N,总时间复杂度为 O(NlogN)O(N \log N)
  • 空间复杂度:O(K)O(K) 存储提取的数字。最坏情况 K=NK=N,空间复杂度为 O(N)O(N)

代码

#include<bits/stdc++.h> 

using namespace std;using ll=long long;

void sol() {
    string s; 
    cin >> s;
    
    string d; 
    
    // 遍历s,提取所有数字字符
    for (char c : s) {
        if (c >= '0' && c <= '9') { // 判断是否为数字
            d.push_back(c);
        }
    }
    
    // 贪心策略:要使数字最大,必须将大的数字放在高位
    // 故对所有提取的数字进行降序排序
    sort(d.rbegin(), d.rend());
    
    // 输出拼接成的最大整数
    cout << d << '\n';
}

int main() {

    ios::sync_with_stdio(0); 
    cin.tie(0);
    cout.tie(0);
    
    sol();
    
    return 0;
}

T2 座位

题意概括

给定 n×mn \times m 个互不相同的分数 a1,,anma_1, \dots, a_{nm}。将它们按降序排名 s1>>snms_1 > \dots > s_{nm}。座位按列蛇形分配:第 1 列从 s1s_1sns_n(行 1n1 \to n),第 2 列从 sn+1s_{n+1}s2ns_{2n}(行 n1n \to 1),第 3 列从 s2n+1s_{2n+1}s3ns_{3n}(行 1n1 \to n),以此类推。求分数 a1a_1 所在的座位 (c,r)(c, r)(列, 行)。 数据范围:1n,m101 \le n, m \le 10, 1ai1001 \le a_i \le 100

题目分析

核心任务是确定 a1a_1(小 R 的分数)在所有 n×mn \times m 个分数中的降序排名 kk。我们可以读入 a1a_1,然后读入其余 n×m1n \times m - 1 个分数,统计其中大于 a1a_1 的数量 cntcnt,则 a1a_111-based 排名 k=cnt+1k = cnt + 1。得到 kk 后,我们分析座位分配规律。

座位分配以 nn 个人(一整列)为一个周期。我们将排名 kk 转换为 00-based 索引 k=k1k' = k - 100-based 列索引 cc' 可以通过 c=knc' = \lfloor \frac{k'}{n} \rfloor 计算得到。在 cc' 列中的 00-based 行偏移 roffr_{off} (从该列的起始方向算起) 为 roff=k(modn)r_{off} = k' \pmod n。蛇形分配的规律取决于列索引 cc' 的奇偶性:1. 如果 cc' 为偶数(c=0,2,4,c' = 0, 2, 4, \dots),分配方向为自顶向下(行 0n10 \to n-1)。此时 00-based 行索引 r=roffr' = r_{off}。2. 如果 cc' 为奇数(c=1,3,5,c' = 1, 3, 5, \dots),分配方向为自底向上(行 n10n-1 \to 0)。

此时 00-based 行索引 r=(n1)roffr' = (n - 1) - r_{off}。最后,将 00-based 的 (c,r)(c', r') 转换为 11-based 的 (c,r)(c, r),即 c=c+1c = c' + 1r=r+1r = r' + 1。由于 n,m10n, m \le 10,总人数 nm100nm \le 100。计算排名 kk 的时间复杂度为 O(nm)O(nm),计算 (c,r)(c, r) 的时间复杂度为 O(1)O(1)。总时间复杂度 O(nm)O(nm),空间复杂度 O(1)O(1)

#include <bits/stdc++.h> 

using namespace std;
using ll = long long;

int main() {
    ios::sync_with_stdio(0); 
    cin.tie(0);
    cout.tie(0);

    int n, m;
    cin >> n >> m;

    int my;
    cin >> my; // 小 R 的分数

    int k = 1; // 1-based 排名
    int tot = n * m;

    for (int i = 1; i < tot; ++i) {
        int x;
        cin >> x;
        if (x > my) {
            k++; // 统计有多少人分数比小 R 高
        }
    }

    k--; // 转换为 0-based 排名 (k')

    int c = k / n; // 0-based 列 (c')
    int r = k % n; // 0-based 行偏移 

    if (c % 2 == 1) { 
        // 如果是奇数列 (c' = 1, 3, ...),方向自底向上
        r = n - 1 - r; 
    }
    
    // 输出 1-based 结果
    cout << c + 1 << " " << r + 1 << '\n';

    return 0;
}

T3 异或和

题意概括

给定一个长度为 nn 的非负整数序列 aa 和一个非负整数 kk。你需要选择序列中尽可能多的不相交的区间 [l,r][l, r],使得每个区间的异或和 alal+1ara_l \oplus a_{l+1} \oplus \dots \oplus a_r 均等于 kk。求这个最大数量。 数据范围:1n5×1051 \leq n \leq 5 \times 10^5, 0k<2200 \leq k < 2^{20}, 0ai<2200 \leq a_i < 2^{20}

题目分析

本题要求最大数量的不相交区间,是典型的动态规划 (DP) 问题。 我们定义 si=t=1iats_i = \bigoplus_{t=1}^i a_t 为序列 aa 的前缀异或和,并令 s0=0s_0 = 0。根据异或的性质,区间 [l,r][l, r] 的异或和为 srsl1s_r \oplus s_{l-1}。 我们要找的区间 [l,r][l, r] 满足 srsl1=ks_r \oplus s_{l-1} = k,这等价于 sl1=srks_{l-1} = s_r \oplus k

定义 dp[i]dp[i] 表示考虑序列的前 ii 个元素 a1,,aia_1, \dots, a_i,能选出的满足条件的不相交区间的最大数量。 计算 dp[i]dp[i] 时,我们有两种决策:

  1. 不选择ii 结尾的区间。这种情况下,最优解与 a1,,ai1a_1, \dots, a_{i-1} 的最优解相同,即 dp[i]=dp[i1]dp[i] = dp[i-1]
  2. 选择一个以 ii 结尾的区间 [l,i][l, i],且其异或和为 kk。为了使区间总数最大,我们应在 a1,,al1a_1, \dots, a_{l-1} 中也取最优解。设 j=l1j = l-1,则我们需要找到 j<ij < isj=siks_j = s_i \oplus k,使得 dp[j]+1dp[j] + 1 最大。

综合两种情况,DP 转移方程为:

$$dp[i] = \max(dp[i-1], \max_{j < i, \text{s.t. } s_j = s_i \oplus k} \{dp[j] + 1\}) $$

直接计算 maxj<i\max_{j < i} 会导致 O(n2)O(n^2) 的复杂度,需要优化。 我们发现,在 ii 递增的过程中,对于 siks_i \oplus k 这个值,我们只需要找到一个 j<ij < i 使得 sj=siks_j = s_i \oplus kdp[j]dp[j] 最大。 我们可以使用一个辅助数组(或哈希表) mpmp,其中 mp[x]mp[x] 存储 max{dp[j]}\max \{dp[j]\},其中 jj 满足 sj=xs_j = xjj 在当前 ii 之前。 在遍历 ii11nn 的过程中,我们维护 sis_i

  1. s=sa[i]s = s \oplus a[i]ss 初始为 00)。
  2. 计算 dp[i]=dp[i1]dp[i] = dp[i-1] (对应决策 1)。
  3. 计算目标前缀和 v=skv = s \oplus k
  4. mpmp 中查找 vv。如果 mp[v]mp[v] 存在(例如,我们用 1-1 初始化 mpmp,则检查 mp[v]1mp[v] \ne -1),说明存在 j<ij < i 使得 sj=vs_j = v。我们用决策 2 更新 dp[i]dp[i]dp[i]=max(dp[i],mp[v]+1)dp[i] = \max(dp[i], mp[v] + 1)
  5. 更新 mpmpmp[s]=max(mp[s],dp[i])mp[s] = \max(mp[s], dp[i])。将 dp[i]dp[i] 存入 mp[si]mp[s_i],供后续的 i>ii' > i 使用。

由于 aia_ikk 均小于 2202^{20},所有的 sis_ivv 也都小于 2202^{20}。因此 mpmp 可以是一个大小为 2202^{20} 的数组。 时间复杂度:O(n+M)O(n + M),其中 M=220M = 2^{20} (用于 mpmp 的初始化和访问)。 空间复杂度:O(n+M)O(n + M),用于 dpdp 数组和 mpmp 数组。

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

int n, k;
int a[500005]; // 存储序列a
int dp[500005]; // dp[i] 表示前i个元素的最大区间数
int mp[1 << 20]; // 映射:mp[x] = max(dp[j]) s.t. s[j] = x

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    cin >> n >> k;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    // 初始化 mp 数组,-1 表示该前缀和尚未出现
    memset(mp, -1, sizeof(mp));

    // s[0] = 0, dp[0] = 0
    mp[0] = 0; 

    int s = 0; // 当前的前缀异或和 s[i]
    for (int i = 1; i <= n; i++) {
        s ^= a[i]; // 计算 s[i]

        // 决策1:不选以 i 结尾的区间
        dp[i] = dp[i - 1];

        // 决策2:选择以 i 结尾的区间 [l, i]
        int v = s ^ k; // 寻找目标 s[l-1],即 s[j]

        if (mp[v] != -1) {
            // 如果存在这样的 j = l-1
            dp[i] = max(dp[i], mp[v] + 1);
        }

        // 更新 mp[s[i]] 为 dp[i] 的最大值
        // 供 i 之后的 j' (j' > i) 使用
        mp[s] = max(mp[s], dp[i]);
    }

    cout << dp[n] << '\n';

    return 0;
}

T4 多边形

题意概括

给定 nn 根木棍,长度分别为 a1,a2,,ana_1, a_2, \dots, a_n。需要选出一个下标子集 S{1,,n}S \subseteq \{1, \dots, n\},设 L={aiiS}L = \{a_i \mid i \in S\}m=Sm = |S|。该子集 SS 能构成多边形的条件是:m3m \ge 3lLl>2×maxlLl\sum_{l \in L} l > 2 \times \max_{l \in L} l。 求有多少个这样的下标子集 SS 满足条件,答案对 998,244,353 取模。

数据范围:3n5,0003 \leq n \leq 5,0001ai5,0001 \leq a_i \leq 5,000

题目分析

我们考虑使用总方案数减去不合法的方案数。总方案数为 2n2^n。不合法的方案分为两类:

  1. 选出的木棍数 m<3m < 3。这类方案数

    $$\binom{n}{0} + \binom{n}{1} + \binom{n}{2} = 1 + n + \frac{n(n-1)}{2} $$
  2. 选出的木棍数 m3m \ge 3,但不满足 li>2×maxli\sum l_i > 2 \times \max l_i,即 li2×maxli\sum l_i \le 2 \times \max l_i。 设 SS 是选出的木棍集合,lmax=maxlSl_{\max} = \max_{l \in S}。令 S=S{lmax}S' = S \setminus \{l_{\max}\}(如果最大值有多个,任取其一)。不合法条件 li2lmax\sum l_i \le 2 l_{\max} 等价于 (lSl)+lmax2lmax(\sum_{l \in S'} l) + l_{\max} \le 2 l_{\max},即 lSllmax\sum_{l \in S'} l \le l_{\max}

为了不重不漏地统计第二类不合法方案,我们首先将 nn 根木棍按长度 aia_i 从小到大排序。 我们迭代 ii11nn,并假设 aia_i 是当前不合法集合 SS下标最大 的那个最大值元素。 我们需要在 aia_i 之前(即下标 j<ij < i)的元素 {a1,,ai1}\{a_1, \dots, a_{i-1}\} 中选取一个子集 SS',使得 lSlai\sum_{l \in S'} l \le a_i,并且 m=S+13m = |S'| + 1 \ge 3,即 S2|S'| \ge 2

这可以看作一个01背包计数问题。我们维护一个 dpdp 数组,dp[k]dp[k] 表示从 {a1,,ai1}\{a_1, \dots, a_{i-1}\} 中选取子集,使其元素和恰好为 kk 的方案数。 当我们从 ii 迭代到 i+1i+1 时,dpdp 数组需要被更新,以包含 aia_i 作为可选元素。 设 A=max(ai)A = \max(a_i)。我们初始化 dpdp 数组(大小 A+1A+1),dp[0]=1dp[0] = 1,其余为 00。 我们按 ii11nn 循环:

  1. 此时 dp[k]dp[k] 存储的是使用 {a1,,ai1}\{a_1, \dots, a_{i-1}\} 构成和为 kk 的方案数。

  2. 我们计算 T=k=0aidp[k]T = \sum_{k=0}^{a_i} dp[k]TT 表示从 {a1,,ai1}\{a_1, \dots, a_{i-1}\} 中选取和 ai\le a_i 的子集 SS' 的总方案数。

  3. TT 包含了 S=0|S'|=0 (空集,1种) 和 S=1|S'|=1 (共 i1i-1 种) 的情况。

  4. 因此,满足 Sai\sum S' \le a_iS2|S'| \ge 2 的方案数为 T1(i1)T - 1 - (i-1)。我们将此计入不合法方案总数 invinv

  5. aia_i 加入背包,更新 dpdp 数组,为下一次迭代 i+1i+1 做准备:

    $$dp[k] = (dp[k] + dp[k - a_i]) \pmod{MOD} \quad (\text{for } k \text{ from } A \text{ down to } a_i) $$

    总不合法方案数 invinv 即为 m<3m < 3 的方案数加上所有 ii 迭代中累加的 TiT - i。 最终答案为 (2ninv)(modMOD)(2^n - inv) \pmod{MOD}

时间复杂度:排序 O(NlogN)O(N \log N),DP 过程 NN 次迭代,每次迭代 O(A)O(A) 查询和 O(A)O(A) 更新,总复杂度 O(NlogN+Nmax(ai))O(N \log N + N \cdot \max(a_i))。 空间复杂度:O(max(ai))O(\max(a_i))

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

const ll mod = 998244353;

// 快速幂 p(a, b) = a^b % mod
ll p(ll a, ll b) {
    ll res = 1;
    a %= mod;
    while (b > 0) {
        if (b & 1) res = (res * a) % mod;
        a = (a * a) % mod;
        b >>= 1;
    }
    return res;
}

int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    int n;
    cin >> n;

    vector<int> a(n);
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
    }

    sort(a.begin(), a.end());

    // 总方案数 2^n
    ll ans = p(2, n);

    // 不合法方案数 m < 3
    ll inv = (1 + n + (ll)n * (n - 1) / 2 % mod) % mod;

    int m = a[n - 1]; // 最大ai
    vector<ll> dp(m + 1, 0);
    dp[0] = 1;

    for (int i = 0; i < n; ++i) {
        int mx = a[i];
        
        // 计算 S' 子集和 <= mx 的方案数
        ll tot = 0;
        for (int k = 0; k <= mx; ++k) {
            tot = (tot + dp[k]) % mod;
        }

        // c1 是 |S'|=1 的方案数
        ll c1 = i; 
        // sub 是 |S'| >= 2 的方案数
        ll sub = (tot - 1 - c1 + mod + mod) % mod;

        // 计入不合法方案 (m >= 3 的情况)
        inv = (inv + sub) % mod;

        // 将 a[i] 加入背包
        for (int k = m; k >= a[i]; --k) {
            dp[k] = (dp[k] + dp[k - a[i]]) % mod;
        }
    }

    // (总方案 - 不合法方案) % mod
    cout << (ans - inv + mod) % mod << '\n';

    return 0;
}

0 条评论

目前还没有评论...