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
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
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
using namespace std;

set 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
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
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 
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> grid(h, vector(w));
    rep(i, h) rep(j, w) cin >> grid[i][j];
    ll ans = inf;
    for(auto y : grid) for(auto x : y) {
        vector>> dp(K + 1, vector>(h, vector(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;
}

你可能感兴趣的:(AtCoder,c++,编程语言,acm竞赛)