VP Educational Codeforces Round 74 (Rated for Div. 2)


A. Prime Subtraction

题意:\(x\)一直减质数,能不能到\(y\)

\(x - y > 1\)就一定可以,因为如果\(x - y\)是偶数,则一直减2,否则减3,然后一直减2。

点击查看代码
void solve() {
    i64 x, y;
    std::cin >> x >> y;
    if (x - y != 1) {
    	std::cout << "YES\n";
    } else {
    	std::cout << "NO\n";
    }
}

B. Kill 'Em All

题意:坐标轴上有\(n\)个怪物,你可以发射炮弹。如果在\(y\)处发射炮弹,那么在\(y\)点的怪物都会死亡,大于\(y\)的怪物会向右移动\(r\),小于\(y\)的会向左移动\(r\)。怪物坐标小于等于\(0\)也会死亡,求最少炮弹数。

把怪物往右边推肯定不优。那么从大到小打,模拟就行。

点击查看代码
void solve() {
    int n, r;
    std::cin >> n >> r;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }

    std::ranges::sort(a);
    a.erase(std::unique(a.begin(), a.end()), a.end());
    std::ranges::reverse(a);
    int ans = 0;
    for (int i = 0; i < a.size(); ++ i) {
    	if (a[i] - (i64)ans * r > 0) {
    		++ ans;
    	}
    }
    std::cout << ans << "\n";
}

C. Standard Free2play

题意:从\(h\)跳到\(0\),有\(n\)个台阶可以使用。如果当前在\(x\),那么可以让\(x, x+1\)这两个台阶切换状态,也就是可以使用的变成不可使用,不可使用变成可以使用。然后你会一直掉落到下一个台阶的位置,但你掉落的高度不能超过零。你可以进行操作,每次强制改变一个台阶的状态,求最少操作次数。

如果当前在\(x\),对于下一个台阶\(y\),可以一直走到\(y+1\)这个位置,此时再走就会使得\(y\)消失,那么\(y\)和它后面的台阶距离不能超过\(1\),否则就需要操作一次。

点击查看代码
void solve() {
    int n, h;
    std::cin >> h >> n;
    std::vector<int> a(n);
    for (int i = 0; i < n; ++ i) {
    	std::cin >> a[i];
    }
    a.push_back(0);

    int ans = 0;
    for (int i = 1; i < n; ++ i) {
    	if (a[i] > a[i + 1] + 1) {
    		++ ans;
    	} else {
    		++ i;
    	}
    }
    std::cout << ans << "\n";
}

D. AB-string

一个只包含\(AB\)的字符串,如果一个字符串满足每个字符都在一个长度大于\(1\)的子回文串里,这个字符串就是好的。求这个字符串好的子串的个数。

好的不容易求,可以求坏的,那么答案就是总数减坏的子串。
考虑什么子串是坏的,首先这个子串有一个单独的字符,它没有相邻的和它相等的字符。那么就是一段\(AB\)交错的子串,可以发现这个子串长度不能超过\(2\),因为\(ABA, ABAB..\)这些都是回文。那么只可能是\(ABBB...B, BAAA..A, AAAA...AB, BBB..BA\)这些。

点击查看代码
void solve() {
    int n;
    std::cin >> n;
    std::string s;
    std::cin >> s;
    i64 ans = (i64)n * (n - 1) / 2;
    for (int i = 0; i < n; ++ i) {
    	int j = i;
    	while (j + 1 < n && s[j + 1] == s[i]) {
    		++ j;
    	}

    	ans -= (i > 0) * (j - i);
    	ans -= (j + 1 < n) * (j - i + 1);
    	i = j;
    }
    std::cout << ans << "\n";
}

E. Keyboard Purchase

题意:一个只包含前\(m\)个小写字母的字符串,你需要选择一个前\(m\)个小写字母组成的排列,然后记的\(i\)个的位置为\(p_i\),那么代价就是\(\sum_{i=1}^{n-1} |p_{s_i} - p_{s_{i+1}}|\)。求最小代价。

\(m\)很小,考虑状压。首先记\(cnt[i][j]\)表示\(i,j\)相邻的对数,\(sun[i]\)表示\(i\)和其它所有字符相邻对的总和。对于一个状态\(s\),如果第\(i\)个字母出现了这一位就是\(1\),那么可以枚举接下来放哪个字符在后面,记加上这个字符有\(x\)个字符,记它和已有的字符的\(cnt\)的和为\(sum1\),和没出现的字符的\(cnt\)和为\(sum2\),那么和前面的代价是\(sum1 \times (x - y)\),和后面的代价是\(sum2 \times (y - x)\)。发现我们不知道\(y\)的值,那么我们可以提前算出来,也就是贡献为\(sum1 \times x - sum2 \times x\),这样不需要知道前面的位置和后面的位置,因为它们拆开并不影响计算。

点击查看代码
void solve() {
    int n, m;
    std::cin >> n >> m;
    std::string s;
    std::cin >> s;
    std::array<std::array<int, 26>, 26> cnt{};
    for (int i = 0; i + 1 < n; ++ i) {
    	cnt[s[i] - 'a'][s[i + 1] - 'a'] += 1;
    	cnt[s[i + 1] - 'a'][s[i] - 'a'] += 1;
    }

    std::vector<int> sum(m);
    for (int i = 0; i < m; ++ i) {
    	cnt[i][i] = 0;
    	for (int j = 0; j < m; ++ j) {
    		sum[i] += cnt[i][j];
    	}
    }

    const i64 inf = 1e18;
    std::vector<i64> f(1 << m, inf);
    f[0] = 0;
    for (int i = 1; i < 1 << m; ++ i) {
    	int p = __builtin_popcount(i);
    	for (int j = 0; j < m; ++ j) {
    		if (i >> j & 1) {
    			i64 sum1 = 0, sum2 = 0;
    			for (int k = 0; k < m; ++ k) {
    				if (i >> k & 1) {
    					sum1 += cnt[j][k];
    				}
    			}
    			sum2 = sum[j] - sum1;
    			f[i] = std::min(f[i], f[i - (1 << j)] + sum1 * p - sum2 * p);
    		}
    	}
    }

    std::cout << f[(1 << m) - 1] << "\n";
}
posted @ 2025-05-03 17:19  maburb  阅读(13)  评论(0)    收藏  举报