Lời Giải HSG Tin Tỉnh Quảng Ninh (B) - 2017

November 4, 2025 August 16, 2024
Author Author Hung Nguyen Tuong

Bài 1. Thống kê chữ cái

Cho một xâu SS chỉ gồm các chữ cái tiếng Anh (az,AZ)(a\ldots z,A \ldots Z).

Yêu cầu

Bạn hãy thống kê các chữ cái xuất hiện trong xâu SS theo thứ tự từ điển và số lần xuất hiện của nó. Việc thống kê các chữ cái không phân biệt chữ hoa, chữ thường.

Dữ liệu

Tệp VANBAN.INP chứa một dòng ghi xâu SS.

Kết quả

Ghi vào tệp VANBAN.OUT với nội dung:

  • Dòng đầu tiên ghi số lượng chữ cái khác nhau có trong xâu SS (không phân biệt chữ hoa, chữ thường).
  • Mỗi dòng tiếp theo ghi một chữ cái in hoa, tiếp theo là một dấu cách và số lần xuất hiện của nó trong xâu SS.

Ví dụ

VANBAN.INPVANBAN.OUT
aFbAAffFbbDda4
A 4
B 3
D 2
F 4

Ràng buộc

  • 30% số test ứng với 30% số điểm của bài có độ dài xâu SS không lớn hơn 10210^2.
  • 30% số test ứng với 30% số điểm của bài có độ dài xâu SS không lớn hơn 10310^3.
  • 40% số test ứng với 40% số điểm của bài có độ dài xâu SS không lớn hơn 10510^5.

Lời giải

#include <bits/stdc++.h>
using namespace std;
#define nl '\n'

int main()
{
    string s;
    cin >> s;
    map<char, int> f;
    for (char c : s)
        f[('a' <= c && c <= 'z') ? c - 32 : c]++;

    cout << f.size() << nl;
    for (auto &p : f)
        cout << p.first << " " << p.second << nl;

    return 0;
}

Bài 2. Đoạn thẳng dài nhất

Trên trục số, người ta tô đậm ba đoạn [L1,R1][L_1, R_1], [L2,R2][L_2, R_2][L3,R3][L_3, R_3] với các điểm đầu và điểm cuối đều có tọa độ nguyên.

Yêu cầu

Tìm đoạn có độ dài lớn nhất được tô đậm trên trục số từ ba đoạn đã cho.

Dữ liệu

Tệp DOAN.INP gồm 3 dòng, dòng thứ ii chứa 2 số nguyên Li,Ri (109LiRi109)L_i, R_i ~ (-10^9 \leq L_i \leq R_i \leq 10^9), với i=1,2,3i = 1,2,3, cách nhau bởi dấu cách.

Kết quả

Ghi vào tệp DOAN.OUT một số nguyên là độ dài của đoạn được tô đậm dài nhất trên trục số.

Ví dụ

DOAN.INPDOAN.OUT
1 4
-2 2
5 10
6
-2 0
0 3
5 9
5
0 1
2 4
-12 -5
7

Ràng buộc

  • 30% số test ứng với 30% số điểm trong trường hợp không có hai đoạn nào có điểm chung.
  • 30% số test ứng với 30% số điểm trong trường hợp 0LiRi1030 \leq L_i \leq R_i \leq 10^3.
  • 40% số test ứng với 40% số điểm trong trường hợp 109LiRi109-10^9 \leq L_i \leq R_i \leq 10^9.

Lời giải

Trước tiên, đọc ba đoạn và sắp xếp theo điểm bắt đầu.

Duyệt qua các đoạn. Nếu một đoạn mới giao hoặc chạm với đoạn hiện tại, ta mở rộng đoạn hiện tại. Nếu không, ta chuyển sang đoạn mới.

Trong quá trình này, độ dài lớn nhất của các đoạn liên tục được cập nhật.

Cuối cùng, đưa ra kết quả là đoạn dài nhất được tô đậm.

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

int main()
{
    vector<pair<ll, ll>> s(3);
    for (int i = 0; i < 3; i++)
        cin >> s[i].first >> s[i].second;
    sort(s.begin(), s.end());
    pair<ll, ll> cur = s[0];
    ll ans = cur.second - cur.first;

    for (int i = 1; i < 3; i++)
    {
        if (s[i].first <= cur.second)
            cur.second = max(cur.second, s[i].second);
        else
            cur = s[i];
        ans = max(ans, cur.second - cur.first);
    }
    cout << ans;
    return 0;
}

Bài 3. Tổng các chữ số

Yêu cầu

Bạn hãy đếm số lượng trong đoạn [L,R][L,R] có tổng các chữ số bằng hh cho trước.

Ví dụ L=15,R=100,h=10L=15,R=100,h=10 thì có 99 số có tổng các chữ số bằng 1010 đó là 19,28,37,46,55,64,73,82,9119,28,37,46,55,64,73,82,91.

Dữ liệu

Vào từ tệp CHUSO.INP gồm một dòng chứa 3 số nguyên L,R,hL,R,h viết cách nhau bởi dấu cách (1LR109,1h81)(1 \le L \le R \le 10^9, 1 \le h\le 81).

Kết quả

Ghi ra tệp CHUSO.OUT gồm số tự nhiên duy nhất là số lượng các số có tổng các chữ số bằng hh nằm trong đoạn [L,R][L,R].

Ví dụ

CHUSO.INPCHUSO.OUT
15 100 109

Ràng buộc

  • 30%30\% số test ứng với 30%30\% số điểm của bài có LR104L \le R \le 10^4.
  • 30%30\% số test ứng với 30%30\% số điểm của bài có LR106L \le R \le 10^6.
  • 30%30\% số test khác ứng với 30%30\% số điểm của bài có LR109L \le R \le 10^9.

Lời giải

RR có thể rất lớn, nên việc tính tổng chữ số từng số từ LRL \to R là không khả thi.

Chúng ta sẽ áp dụng quy hoạch động trên chữ số (Digit DP).

Tóm tắt đề bài, ta cần tìm số lượng số xx sao cho:

  • LxRL \le x \le R.
  • Tổng các chữ số của xxhh.

Giả sử f(k,s)f(k,s) là số lượng số x[0,k]x \in [0,k] mà tổng các chữ số là ss, ký hiệu d(x)=sd(x)=s.

Vậy số lượng số xx trong đề bài cần tìm là:

f(R,h)f(L1,h) f(R,h)-f(L-1,h)

Vậy làm để có được f(k,s)f(k,s)?

Với k9,s9k \le 9,s \le 9 thì việc tính f(k,s)f(k,s) quá đơn giản.

Trường hợp k>9k>9, khi kk có nhiều chữ số, ta có thể tách kk thành 2 phần như sau:

k=10×k+r k = 10 \times k' + r
  • Chữ số cuối cùng: r=kmod10r = k \bmod 10.
  • Phần còn lại: k=k/10k' = \lfloor k/10 \rfloor.

Ví dụ: k=12345k=1234,r=5k=12345 \rightarrow k' = 1234, r=5.

Và mỗi số x (0xk)x ~ (0 \le x \le k) cũng biểu diễn được như vậy:

x=10×x+i x = 10 \times x' + i
  • Với ii là chữ số cuối (0i9)(0 \le i \le 9).
  • xx' là phần còn lại, có số chữ số \le số chữ số của kk'.

Ta thấy rằng, vì xkx \le k nên sẽ luôn đảm bảo xkx' \le k'. Nhưng khi ta ghép thêm ii vào xx' để so sánh với kk, điều ngược lại chưa chắc đúng. Bởi 2 trường hợp có thể xảy ra:

  • iri \le r: Nếu x<kx'<k' thì chắc chắn xkx \le k, nếu x=kx'=k', hàng đơn vị của xx vẫn \le của kk nên xkx \le k.
  • i>ri > r: Nếu x<kx'<k' thì vẫn đảm bảo xkx \le k, nhưng nếu x=kx'=k', hàng đơn vị của x>x > của kk nên x>kx > k.

Biết rằng:

d(x)=d(x)+i d(x)=d(x')+i

Để d(x)=sd(x) = s thì d(x)d(x') phải là sis-i.

Vậy số lượng số x[0,k]x \in [0,k] thoả mãn d(x)=sd(x)=s cũng là số lượng số xx' thoả mãn với mỗi i[0,9]i \in [0,9]:

  • Thêm ii vào cuối xx' được xkx \le k. Theo quan sát ở trên, ta có x[0,k(i>r)]x' \in [0,k'-(i > r)].
  • d(x)=sid(x')=s-i.

Ta suy ra được hàm đệ quy như sau:

T(k,s)=i=09T(k(i>r),si)=i=09T(k/10(i>(kmod10)),si) \begin{align*}T(k,s) &= \sum_{i=0}^9 T(k'-(i>r),s-i) \\ &= \sum_{i=0}^9 T(\lfloor k/10 \rfloor-(i>(k \bmod 10)),s-i)\end{align*}

Và ta cũng có các trường hợp cơ sở sau:

  • T(k,s)=0T(k,s)=0 nếu (k<0 or s<0)(k<0 ~\text{or}~ s < 0) hoặc (k=0 vaˋ s>0)(k=0 ~\text{và}~ s>0).
  • T(k,s)=1T(k,s)=1 nếu k=s=0k=s=0.
#include <bits/stdc++.h>
using namespace std;

map<pair<int, int>, int> memo;

int T(int k, int s)
{
    if (k < 0 || s < 0)
        return 0;
    if (!k)
        return !s;
    if (memo.find({k, s}) != memo.end())
        return memo[{k, s}];
    int ans = 0;
    for (int i = 0; i <= 9; i++)
        ans += T(k / 10 - (i > (k % 10)), s - i);
    return memo[{k, s}] = ans;
}

int main()
{
    int L, R, h;
    cin >> L >> R >> h;
    cout << T(R, h) - T(L - 1, h);
    return 0;
}

Vì mỗi lần gọi đệ quy ta rút gọn kk đi một chữ số. Giả sử ban đầu kkDD chữ số log10k\approx \log_{10}k, nên chỉ rút gọn kk đi log10k\log_{10}k lần. ss được giảm từ hh xuống 00, qua mỗi lần đệ quy trừ đi i[0,9]i \in [0,9], nên số các giá trị tối đa ss có thể có là hh. Vậy ta có s×log10ks \times \log_{10}k trạng thái, mỗi trạng thái duyệt qua 1010 giá trị iiO(10)O(10), coi là O(1)O(1) vì là hằng số.

Nhờ lưu kết quả trong memo, nên độ phức tạp của thuật toán là O(hlogR)O(h\log R).