A. 移位
题意:一个形如 A,A+1,...,N,1,2,...,N,1,2,...,N,...A, A + 1, ..., N, 1, 2, ..., N, 1, 2, ..., N, ...A,A+1,...,N,1,2,...,N,1,2,...,N,... 的序列,问第 KKK 个数字是什么
对于 A=1A = 1A=1 的情况其实很好解决,就是 (K−1) mod N+1(K - 1) \bmod N + 1(K−1)modN+1
实际上其他情况就是加了一个偏移量 AAA,所以答案就是 ((k−1) mod n+a−1) mod n+1((k - 1)\bmod n + a - 1)\bmod n + 1((k−1)modn+a−1)modn+1
#include<bits/stdc++.h>
using namespace std;
int n, k, a;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> k >> a;
cout << ((k - 1) % n + a - 1) % n + 1 << endl;
return 0;
}
或者特判下 k≤n−a+1k \leq n - a + 1k≤n−a+1 的情况
#include<bits/stdc++.h>
using namespace std;
int n, k, a;
int main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> k >> a;
if (k <= n - a + 1) cout << k + a - 1 << '\n';
else {
cout << (k - (n - a + 1) - 1) % n + 1 << '\n';
}
return 0;
}
B. 预先打表
题意:给定 NNN 组询问,每个询问一个 SSS,判断是否存在两个正整数 a,ba, ba,b,满足 4ab+3a+3b=S4ab + 3a + 3b = S4ab+3a+3b=S
我们可以预处理出所有的所有可达的 SSS 值,记录在一个 setsetset 中,之后每次查询只需要查 setsetset 中有没有这个值即可
因为 1≤Si≤10001\leq S_i\leq 10001≤Si≤1000,所以 a,ba, ba,b 的范围最大是 1≤a,b≤10001\leq a, b\leq 10001≤a,b≤1000,这样枚举所有的可能的 (a,b)(a, b)(a,b) 算出 SSS 值即可
#include<bits/stdc++.h>
using namespace std;
set<int> st;
int n, x, ans;
int main(void) {
for (int a = 1; a <= 1000; a++) {
for (int b = 1; b <= 1000; b++) {
st.insert(4 * a * b + 3 * a + 3 * b);
}
}
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n;
for (int i = 0; i < n; i++) {
cin >> x;
ans += !st.count(x);
}
cout << ans << endl;
return 0;
}
C. 枚举
题意:给定 NNN,判断存在多少组正整数 (A,B,C),A≤B≤C(A, B, C), A\leq B\leq C(A,B,C),A≤B≤C,满足 ABC≤NABC\leq NABC≤N
先考虑枚举哪一个数
- 枚举 AAA,只需要枚举 O(N3)O(\sqrt[3]{N})O(3N)
- 枚举 BBB,需要枚举 O(N)O(\sqrt{N})O(N)
- 枚举 CCC,需要枚举 O(N)O(N)O(N)
没显然我们枚举 AAA
对于一个 AAA 来说,有多少个 BC≤⌊NA⌋BC\leq \lfloor \frac{N}{A} \rfloorBC≤⌊AN⌋ 呢?
我们再 O(⌊NA⌋)O(\sqrt{\lfloor\frac{N}{A}\rfloor})O(⌊AN⌋) 枚举 BBB
下面只需要判断有多少个 CCC 在 [B,⌊NAB⌋][B, \lfloor \frac{N}{AB} \rfloor][B,⌊ABN⌋] 中即可
答案为 ⌊NAB⌋−B+1\lfloor \frac{N}{AB} \rfloor - B + 1⌊ABN⌋−B+1
时间复杂度:O(N23)O(N^{\frac{2}{3}})O(N32)
D. 二分
题意:NNN 个部门,第 iii 个部门有 AiA_iAi 个雇员,完成一个项目需要 KKK 个不同部门的人,问最多能完成多少个项目?
直接做发现不太好做,贪心取大的话复杂度不对,所以考虑从答案入手
二分答案
这可问题可以看成:有 XXX 个项目,每个部门向尽可能多的项目里去分配人员(这无论是对项目还是对于该部门来说都是最优的),最后判断这个方案可不可行即可
需要注意二分上界不能取太大(比如 101810^{18}1018),会在计算的过程中爆 long longlong\ longlong long
时间复杂度:O(Nlog(maxAi))O(N\log(\max A_i))O(Nlog(maxAi))
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int ll
const int N = 2e5 + 5;
int n, k, a[N];
bool check(int now) {
int sum = 0;
for (int i = 1; i <= n; i++) {
sum += min(now, a[i]);
}
return sum >= k * now;
}
signed main(void) {
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
int l = 0, r = 2e17 / k, ans = -1;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) {
ans = mid;
l = mid + 1;
}
else r = mid - 1;
}
cout << ans << endl;
return 0;
}
E. 动态规划
题意:给定一个只含有 K,E,YK, E, YK,E,Y 字符的字符串,计算在最多 KKK 次两两相邻位置交换后形成的所有可能的字符串集合的大小
首先先考虑这样的一个问题:给定两个同构串 S,TS, TS,T,他们之间的最小转移距离是多少?
那肯定是贪心地从左向右匹配,找最近的换,比如:
- S=KEYE,T=EYKES = KEYE, T = EYKES=KEYE,T=EYKE
- 首先使得 SSS 的第一位为 EEE,找到当前距离最近的 EEE 交换一下,SSS 变为 EKYEEKYEEKYE
- 然后是 YYY,SSS 变为 EYKEEYKEEYKE
那么对于此题,一个很简单的想法是:枚举所有可能的串,计算两者之间的距离,如果这个距离 d≤Kd\leq Kd≤K ,那么对答案产生贡献,否则忽略
但这样做的复杂度大约是 O(330)O(3^{30})O(330) 的,显然不可行,所以我们可以尝试用线性 dpdpdp 来做
dp[i][j][k][v]dp[i][j][k][v]dp[i][j][k][v] 表示使得 SSS 的前 iii 个位置通过 jjj 次移动,含有 kkk 个 KKK,vvv 个 EEE,i−k−vi - k - vi−k−v 个 YYY 的所有可能的字符串种类数
这个动态规划的核心点在于,我已经知道了前 iii 个字符里分别所含的 K,E,YK, E, YK,E,Y 的个数,那么剩下的字符串的状态我也知道了,因为前面都是贪心移动的,比如原来的字符串为 S=YKEES = YKEES=YKEE,我当前知道前 222 个含有 111 个 KKK,111 个 EEE ,那么剩下的一定是 YEYEYE 而不可能是 EYEYEY
基于这个想法,我们的 dpdpdp 就变得很简单了
或许正纠结怎么优化,不用考虑这个问题,just do it!这 ∣S∣≤30|S|\leq 30∣S∣≤30,大胆转移就行
转移为:
- dp[i+1][x+nxtk][j+1][k]←dp[i][x][j][k]dp[i + 1][x + nxt_k][j + 1][k]\leftarrow dp[i][x][j][k]dp[i+1][x+nxtk][j+1][k]←dp[i][x][j][k]
- dp[i+1][x+nxte][j][k+1]←dp[i][x][j][k]dp[i + 1][x + nxt_e][j][k + 1]\leftarrow dp[i][x][j][k]dp[i+1][x+nxte][j][k+1]←dp[i][x][j][k]
- dp[i+1][x+nxty][j][k]←dp[i][x][j][k]dp[i + 1][x + nxt_y][j][k]\leftarrow dp[i][x][j][k]dp[i+1][x+nxty][j][k]←dp[i][x][j][k]
nxtknxt_knxtk 表示下一个字符 KKK 距离当前位置的距离,nxte,nxtynxt_e, nxt_ynxte,nxty 同理,具体细节见代码
时间复杂度:O(∣S∣6)O(|S|^6)O(∣S∣6) 常数很小
也可以预处理出给定前 iii 个位置的 K,E,YK,E, YK,E,Y 数量得到的剩下字符串,使得 dpdpdp 最内层复杂度由 O(∣S∣)O(|S|)O(∣S∣) 变为 O(1)O(1)O(1),从而使得总复杂度变为 O(∣S∣5)O(|S|^5)O(∣S∣5)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int ll
const int N = 33;
char s[N];
int dp[N][N * N][N][N], k, n, pos, cnt, m;
signed main(void) {
scanf("%s%d", s + 1, &m);
n = strlen(s + 1);
dp[0][0][0][0] = 1;
int ck = 0, ce = 0, cy = 0;
for (int i = 1; i <= n; i++) {
ck += s[i] == 'K';
ce += s[i] == 'E';
cy += s[i] == 'Y';
}
for (int i = 0; i < n; i++) {
for (int x = 0; x <= n * n; x++) {
for (int j = 0; j <= ck; j++) {
for (int k = 0; k <= ce; k++) {
if (i - j - k > cy || j + k > i) continue;
int cntk = j, cnte = k, cnty = i - j - k;
string p; p.clear();
for (int v = 1; v <= n; v++) {
if (s[v] == 'K') {
if (cntk) cntk--;
else p += 'K';
}
else if (s[v] == 'E') {
if (cnte) cnte--;
else p += 'E';
}
else {
if (cnty) cnty--;
else p += 'Y';
}
}
int nxtk = -1, nxte = -1, nxty = -1;
for (int v = 0; v < (int) p.size(); v++) {
if (p[v] == 'K') {
nxtk = v; break;
}
}
for (int v = 0; v < (int) p.size(); v++) {
if (p[v] == 'E') {
nxte = v; break;
}
}
for (int v = 0; v < (int) p.size(); v++) {
if (p[v] == 'Y') {
nxty = v; break;
}
}
if (nxtk != -1) dp[i + 1][x + nxtk][j + 1][k] += dp[i][x][j][k];
if (nxte != -1) dp[i + 1][x + nxte][j][k + 1] += dp[i][x][j][k];
if (nxty != -1) dp[i + 1][x + nxty][j][k] += dp[i][x][j][k];
}
}
}
}
int ans = 0;
for (int i = 0; i <= min(m, n * n); i++) {
for (int j = 0; j <= n; j++) {
for (int k = 0; k <= n; k++) {
ans += dp[n][i][j][k];
}
}
}
cout << ans << endl;
return 0;
}
F. 动态规划
题意:给一个 H×WH\times WH×W 的矩阵 AAA,1≤Ai,j≤109,1≤H,W≤301\le A_{i,j}\le 10^9,1\le H,W\le 301≤Ai,j≤109,1≤H,W≤30,
从左上角 (1,1)(1,1)(1,1) 出发,只可以向下、向右走,要求抵达右下角 (H,W)(H,W)(H,W)
计算途经前 KKK 大 Ai,jA_{i,j}Ai,j 之和为 sumsumsum。1≤K<H+W1\le K< H+W1≤K<H+W ,要求求出 sumsumsum 的最小值
方案为dpdpdp,很容易想到的dpdpdp状态为dp[i][j][k]dp[i][j][k]dp[i][j][k]表示:抵达到(i,j)(i,j)(i,j)时的
路径前kkk大和的最小值。
但是我们发现,无法顺利地转移方程。因为当我们试图用dp[i][j][k]dp[i][j][k]dp[i][j][k]更新dp[i+1][j][k]dp[i+1][j][k]dp[i+1][j][k]时,
我们要在我们已经走过的路径中再添上Ai+1,jA_{i+1,j}Ai+1,j,从而导致路径的前kkk大和发生了变换,
这要求我们记录所有路径途径的值。
换而言之,我们的dpdpdp方案具有后效性!
为了解决dpdpdp方案的后效性,我们可以采取如此策略
枚举分界值xxx,我们认为最终方案选取的前KKK大的Ai,jA_{i,j}Ai,j的最小值即为xxx
那么,在我们转移状态的过程中只要碰到数值≥x\ge x≥x,我们就统计入和,并且由kkk更新到k+1k+1k+1
需要注意的是,当Ai,j=xA_{i,j}=xAi,j=x时,有统计与不统计两种情况
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define rep(i,n) for(int i = 0; i < n; i++)
void chmin(ll&a,ll b){if(a>b)a=b;}
const ll inf = 1001001001001001001;
int main(){
int h, w, K; cin >> h >> w >> K;
vector<vector<int>> grid(h, vector<int>(w));
rep(i, h) rep(j, w) cin >> grid[i][j];
ll ans = inf;
for(auto y : grid) for(auto x : y) {
vector<vector<vector<ll>>> dp(K + 1, vector<vector<ll>>(h, vector<ll>(w, inf)));
if(grid[0][0] >= x) dp[1][0][0] = grid[0][0];
if(grid[0][0] <= x) dp[0][0][0] = 0;
rep(i, K + 1) rep(j, h) rep(k, w) {
if(j != h - 1){
if(i != K && grid[j + 1][k] >= x) chmin(dp[i + 1][j + 1][k], dp[i][j][k] + grid[j + 1][k]);
if(grid[j + 1][k] <= x) chmin(dp[i][j + 1][k], dp[i][j][k]);
}
if(k != w - 1){
if(i != K && grid[j][k + 1] >= x) chmin(dp[i + 1][j][k + 1], dp[i][j][k] + grid[j][k + 1]);
if(grid[j][k + 1] <= x) chmin(dp[i][j][k + 1], dp[i][j][k]);
}
}
chmin(ans, dp[K][h - 1][w - 1]);
}
cout << ans << endl;
return 0;
}
本文精选了几道典型的算法竞赛题目并提供了详细的解答思路,包括序列查找、预处理、枚举、二分查找、动态规划等核心算法技巧。
560

被折叠的 条评论
为什么被折叠?



