AtCoder Beginner Contest 227 题解

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,... 的序列,问第 K K K 个数字是什么

对于 A = 1 A = 1 A=1 的情况其实很好解决,就是 ( K − 1 )   m o d   N + 1 (K - 1) \bmod N + 1 (K1)modN+1

实际上其他情况就是加了一个偏移量 A A A,所以答案就是 ( ( k − 1 )   m o d   n + a − 1 )   m o d   n + 1 ((k - 1)\bmod n + a - 1)\bmod n + 1 ((k1)modn+a1)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 + 1 k \leq n - a + 1 kna+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. 预先打表

题意:给定 N N N 组询问,每个询问一个 S S S,判断是否存在两个正整数 a , b a, b a,b,满足 4 a b + 3 a + 3 b = S 4ab + 3a + 3b = S 4ab+3a+3b=S

我们可以预处理出所有的所有可达的 S S S 值,记录在一个 s e t set set 中,之后每次查询只需要查 s e t set set 中有没有这个值即可

因为 1 ≤ S i ≤ 1000 1\leq S_i\leq 1000 1Si1000,所以 a , b a, b a,b 的范围最大是 1 ≤ a , b ≤ 1000 1\leq a, b\leq 1000 1a,b1000,这样枚举所有的可能的 ( a , b ) (a, b) (a,b) 算出 S S S 值即可

#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. 枚举

题意:给定 N N N,判断存在多少组正整数 ( A , B , C ) , A ≤ B ≤ C (A, B, C), A\leq B\leq C (A,B,C),ABC,满足 A B C ≤ N ABC\leq N ABCN

先考虑枚举哪一个数

  • 枚举 A A A,只需要枚举 O ( N 3 ) O(\sqrt[3]{N}) O(3N )
  • 枚举 B B B,需要枚举 O ( N ) O(\sqrt{N}) O(N )
  • 枚举 C C C,需要枚举 O ( N ) O(N) O(N)

没显然我们枚举 A A A

对于一个 A A A 来说,有多少个 B C ≤ ⌊ N A ⌋ BC\leq \lfloor \frac{N}{A} \rfloor BCAN 呢?

我们再 O ( ⌊ N A ⌋ ) O(\sqrt{\lfloor\frac{N}{A}\rfloor}) O(AN ) 枚举 B B B

下面只需要判断有多少个 C C C [ B , ⌊ N A B ⌋ ] [B, \lfloor \frac{N}{AB} \rfloor] [B,ABN] 中即可

答案为 ⌊ N A B ⌋ − B + 1 \lfloor \frac{N}{AB} \rfloor - B + 1 ABNB+1

时间复杂度: O ( N 2 3 ) O(N^{\frac{2}{3}}) O(N32)

D. 二分

题意: N N N 个部门,第 i i i 个部门有 A i A_i Ai 个雇员,完成一个项目需要 K K K 个不同部门的人,问最多能完成多少个项目?

直接做发现不太好做,贪心取大的话复杂度不对,所以考虑从答案入手

二分答案

这可问题可以看成:有 X X X 个项目,每个部门向尽可能多的项目里去分配人员(这无论是对项目还是对于该部门来说都是最优的),最后判断这个方案可不可行即可

需要注意二分上界不能取太大(比如 1 0 18 10^{18} 1018),会在计算的过程中爆 l o n g   l o n g long\ long long long

时间复杂度: O ( N log ⁡ ( max ⁡ A i ) ) 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 , Y K, E, Y K,E,Y 字符的字符串,计算在最多 K K K 次两两相邻位置交换后形成的所有可能的字符串集合的大小

首先先考虑这样的一个问题:给定两个同构串 S , T S, T S,T,他们之间的最小转移距离是多少?

那肯定是贪心地从左向右匹配,找最近的换,比如:

  • S = K E Y E , T = E Y K E S = KEYE, T = EYKE S=KEYE,T=EYKE
  • 首先使得 S S S 的第一位为 E E E,找到当前距离最近的 E E E 交换一下, S S S 变为 E K Y E EKYE EKYE
  • 然后是 Y Y Y S S S 变为 E Y K E EYKE EYKE

那么对于此题,一个很简单的想法是:枚举所有可能的串,计算两者之间的距离,如果这个距离 d ≤ K d\leq K dK ,那么对答案产生贡献,否则忽略

但这样做的复杂度大约是 O ( 3 30 ) O(3^{30}) O(330) 的,显然不可行,所以我们可以尝试用线性 d p dp dp 来做

d p [ i ] [ j ] [ k ] [ v ] dp[i][j][k][v] dp[i][j][k][v] 表示使得 S S S 的前 i i i 个位置通过 j j j 次移动,含有 k k k K K K v v v E E E i − k − v i - k - v ikv Y Y Y 的所有可能的字符串种类数

这个动态规划的核心点在于,我已经知道了前 i i i 个字符里分别所含的 K , E , Y K, E, Y K,E,Y 的个数,那么剩下的字符串的状态我也知道了,因为前面都是贪心移动的,比如原来的字符串为 S = Y K E E S = YKEE S=YKEE,我当前知道前 2 2 2 个含有 1 1 1 K K K 1 1 1 E E E ,那么剩下的一定是 Y E YE YE 而不可能是 E Y EY EY

基于这个想法,我们的 d p dp dp 就变得很简单了

或许正纠结怎么优化,不用考虑这个问题,just do it!这 ∣ S ∣ ≤ 30 |S|\leq 30 S30,大胆转移就行

转移为:

  • d p [ i + 1 ] [ x + n x t k ] [ j + 1 ] [ k ] ← d p [ 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]
  • d p [ i + 1 ] [ x + n x t e ] [ j ] [ k + 1 ] ← d p [ 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]
  • d p [ i + 1 ] [ x + n x t y ] [ j ] [ k ] ← d p [ 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]

n x t k nxt_k nxtk 表示下一个字符 K K K 距离当前位置的距离, n x t e , n x t y nxt_e, nxt_y nxte,nxty 同理,具体细节见代码

时间复杂度: O ( ∣ S ∣ 6 ) O(|S|^6) O(S6) 常数很小

也可以预处理出给定前 i i i 个位置的 K , E , Y K,E, Y K,E,Y 数量得到的剩下字符串,使得 d p dp dp 最内层复杂度由 O ( ∣ S ∣ ) O(|S|) O(S) 变为 O ( 1 ) O(1) O(1),从而使得总复杂度变为 O ( ∣ S ∣ 5 ) O(|S|^5) O(S5)

#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 × W H\times W H×W 的矩阵 A A A 1 ≤ A i , j ≤ 1 0 9 , 1 ≤ H , W ≤ 30 1\le A_{i,j}\le 10^9,1\le H,W\le 30 1Ai,j109,1H,W30

从左上角 ( 1 , 1 ) (1,1) (1,1) 出发,只可以向下、向右走,要求抵达右下角 ( H , W ) (H,W) (H,W)

计算途经前 K K K A i , j A_{i,j} Ai,j 之和为 s u m sum sum 1 ≤ K < H + W 1\le K< H+W 1K<H+W ,要求求出 s u m sum sum 的最小值

方案为 d p dp dp,很容易想到的 d p dp dp状态为 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示:抵达到 ( i , j ) (i,j) (i,j)时的

路径前 k k k大和的最小值。

但是我们发现,无法顺利地转移方程。因为当我们试图用 d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]更新 d p [ i + 1 ] [ j ] [ k ] dp[i+1][j][k] dp[i+1][j][k]时,

我们要在我们已经走过的路径中再添上 A i + 1 , j A_{i+1,j} Ai+1,j,从而导致路径的前 k k k大和发生了变换,

这要求我们记录所有路径途径的值。

换而言之,我们的 d p dp dp方案具有后效性!

为了解决 d p dp dp方案的后效性,我们可以采取如此策略

枚举分界值 x x x,我们认为最终方案选取的前 K K K大的 A i , j A_{i,j} Ai,j的最小值即为 x x x

那么,在我们转移状态的过程中只要碰到数值 ≥ x \ge x x,我们就统计入和,并且由 k k k更新到 k + 1 k+1 k+1

需要注意的是,当 A i , j = x A_{i,j}=x Ai,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;
}
  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
AtCoder Beginner Contest 134 是一场 AtCoder 的入门级比赛,以下是每道题的简要题解: A - Dodecagon 题目描述:已知一个正十二边形的边长,求它的面积。 解题思路:正十二边形的内角为 $150^\circ$,因此可以将正十二边形拆分为 12 个等腰三角形,通过三角形面积公式计算面积即可。 B - Golden Apple 题目描述:有 $N$ 个苹果和 $D$ 个盘子,每个盘子最多可以装下 $2D+1$ 个苹果,求最少需要多少个盘子才能装下所有的苹果。 解题思路:每个盘子最多可以装下 $2D+1$ 个苹果,因此可以将苹果平均分配到每个盘子中,可以得到最少需要 $\lceil \frac{N}{2D+1} \rceil$ 个盘子。 C - Exception Handling 题目描述:给定一个长度为 $N$ 的整数序列 $a$,求除了第 $i$ 个数以外的最大值。 解题思路:可以使用两个变量 $m_1$ 和 $m_2$ 分别记录最大值和次大值。遍历整个序列,当当前数不是第 $i$ 个数时,更新最大值和次大值。因此,最后的结果应该是 $m_1$ 或 $m_2$ 中较小的一个。 D - Preparing Boxes 题目描述:有 $N$ 个盒子和 $M$ 个物品,第 $i$ 个盒子可以放入 $a_i$ 个物品,每个物品只能放在一个盒子中。现在需要将所有的物品放入盒子中,每次操作可以将一个盒子内的物品全部取出并分配到其他盒子中,求最少需要多少次操作才能完成任务。 解题思路:首先可以计算出所有盒子中物品的总数 $S$,然后判断是否存在一个盒子的物品数量大于 $\lceil \frac{S}{2} \rceil$,如果存在,则无法完成任务。否则,可以用贪心的思想,每次从物品数量最多的盒子中取出一个物品,放入物品数量最少的盒子中。因为每次操作都会使得物品数量最多的盒子的物品数量减少,而物品数量最少的盒子的物品数量不变或增加,因此这种贪心策略可以保证最少需要的操作次数最小。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值