Edu39

加固文明幻景發表於2024-03-21

D. Timetable

Problem - D - Codeforces

轉化成揹包問題

dp預處理

很容易看出來是揹包,但是怎麼把區段內的時間轉化成物品很吃力。

首先物品的體積肯定不能直接用題目給的時間點,而是用逃課的數量

然後對物品的價值進行預處理

  • 預處理出每天的日期
  • 每次處理一天留下 \(k\)\(1\) 的物品之前就先跑一遍找出貢獻最小的方案
  • 然後用這個方案來跑揹包就行。

資料很小,不會超時

constexpr int N(500 + 10);

std::vector<int> sched[N];
int dp[N][N];

void solve() {
    int n, m, k;
    std::cin >> n >> m >> k;
    std::string s;
    for (int i = 1; i <= n; i++) {
        std::cin >> s;
        for (int j = 0; j < m; j++) {
            if (s[j] == '1') {
                sched[i].push_back(j);//預處理出每一天的1的日期
            }
        } 
    }

    memset(dp, 0x3f, sizeof dp);
    dp[0][0] = 0;

    for (int i = 1; i <= n; ++i) {
        int siz(sz(sched[i]));
        for (int cnt = 0; cnt <= siz; ++cnt) {
            int res = m;//先求出當前選擇cnt個1的能得到的最小貢獻
            if (cnt) {
                for (int l = 0, r = cnt - 1; r < siz; ++l, ++r) {//暴力列舉一邊所有區間
                    res = std::min(res, sched[i][r] - sched[i][l] + 1);
                }
            }
            else {//如果是選擇0個,那顯然貢獻就是0
                res = 0;  
            }
            int cntSkip(siz - cnt);
            for (int l = 0; l + cntSkip <= k; ++l) {//已經轉化成了揹包問題
                dp[i][l + siz - cnt] = std::min(dp[i][l + cntSkip], dp[i - 1][l] + res);
            }
        }
    }

    std::cout << *std::min_element(dp[n], dp[n] + k + 1) << '\n';
}

E. Largest Beautiful Number

Problem - E - Codeforces

貪心構造和bitmask的妙用

void solve()
{
#define tests
    std::string s;
    std::cin >> s;
    int n(sz(s));
    std::string res;
    for (int i = 0; i < n - 2; i++) {
        res += '9';
    }
    for (int lcp = n - 1; lcp >= 0; lcp--) {//從右到左檢查,一旦遇到合適的位就輸出
        if (s[lcp] != '0') {
            for (char c = s[lcp] - 1; c >= (lcp == 0 ? '1' : '0'); c--) {//列舉該位的所有可能數碼
                std::string cur = s.substr(0, lcp) + c;
                int mask = 0;//用位來當數碼,標記這一位對應的數碼是否出現偶數次
                for (int i = 0; i < sz(cur); i++) {
                    mask ^= (1 << (cur[i] - '0'));
                }
                std::string me;
                for (int i = 9; i >= 0; i--) {
                    if ((mask >> i) & 1) {
                        me += (i + '0');//為落單的湊,從大到小湊,儘量接近給的數
                    }
                }
                while (sz(cur) + sz(me) < sz(s)) {//剩下的全部湊9
                    cur += '9';
                }
                cur += me;
                if (sz(cur) == sz(s)) {
                    res = cur;
                    break;
                }
            }
            if (sz(res) == sz(s)) {
                break;
            }
        }
    }
    std::cout << res << '\n';
}

F. Fibonacci String Subsequences

Problem - F - Codeforces

\(\texttt{kmp}\) 原理

矩陣

考慮每個 \(F(x)\) 中與 \(s\) 相同的子序列的貢獻。設這個子序列為 \(F(x)_{p_1}, F(x)_{p_2}, F(x)_{p_3}, \ldots, F(x)_{p_n}\)

我們想要它成為一個子序列的子串,那麼 \(F(x)_{[p_1, p_n]}\) 中除了 \(p_1 \sim p_n\) 就不能有別的字元被選。而 \([1, p_1 - 1] \cup [p_n + 1, |F(x)|]\) 中的每個字元都可以選或不選,相當於每個字元產生 \(2\) 的貢獻。

如果我們設 \(f_{i, j}\) 為 kmp 過程中考慮到 \(F(x)\) 的第 \(i\) 位,匹配到 \(s\) 的第 \(j\) 位的答案,那麼相當於

\(f_{i + 1, 0} \gets 2 f_{i, 0}, f_{i + 1, n} \gets 2 f_{i, n}, \forall j \in [1, n - 1], f_{i + 1, j} \gets f_{i, j}, \forall j \in [1, n] \land F(x)_i = s_j, f_{i + 1, j} \gets f_{i, j - 1}\)

答案即為 \(f_{|F(x)|, n}\)

可以用矩陣刻畫這個轉移過程。設 \(F_i\)\(F(i)\) 的轉移矩陣,那麼 \(F_i = F_{i - 1} F_{i - 2}\)

時間複雜度 \(O(n^3x)\)​​​​。

struct mat {
    Z a[N][N];
    mat() {
        memset(a, 0, sizeof a);
    }
}dp[N];

mat operator * (const mat &a, const mat &b) {
	mat res;
	for (int k = 0; k <= n; ++k) {
		for (int i = 0; i <= n; ++i) {
			for (int j = 0; j <= n; ++j) {
				res.a[i][j] += a.a[i][k] * b.a[k][j];
			}
		}
	}
	return res;
}

void solve() {
    std::cin >> n >> m >> s;
    dp[0].a[0][0] = dp[0].a[n][n] = dp[1].a[0][0] = dp[1].a[n][n] = 2;
    for (int i = 1; i < n; i++) {
        dp[0].a[i][i] = dp[1].a[i][i] = 1;
    }
    for (int i = 1; i <= n; i++) {
        if (s[i - 1] == '0') {
            dp[0].a[i - 1][i] = 1;
        }
        else {
            dp[1].a[i - 1][i] = 1;
        }
    }
    for (int i = 2; i <= m; i++) {
        dp[i] = dp[i - 1] * dp[i - 2];
    }
    mat ans;
	ans.a[0][0] = 1;
	ans = ans * dp[m];
    std::cout << ans.a[0][n] << '\n';
}

G. Almost Increasing Array

Problem - G - Codeforces

CF946G - Almost Increasing Array - 洛谷專欄 (luogu.com.cn)

過於困難,先放著