window.cnblogsConfig = {//可以放多张照片,应该是在每一个博文上面的图片,如果是多张的话,那么就随机换的。 homeTopImg: [ "https://cdn.luogu.com.cn/upload/image_hosting/clcd8ydf.png", "https://cdn.luogu.com.cn/upload/image_hosting/clcd8ydf.png" ], }

AT_abc 复盘合集(2023)

AT_abc234 复盘

A

题目翻译炸了,导致调了好久,结果时翻译少了个括号。

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int n;

int f(int x){
	return x * x + 2 * x + 3;
}

signed main(){
	cin >> n;
	cout << f(f(f(n) + n) + f(f(n)));
	return 0;
}

B

直接 \(n^2\) 枚举,求最大值

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int n, ans;
int x[105], y[105];

signed main(){
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> x[i] >> y[i];
	for (int i = 1; i <= n; i++){
		for (int j = 1; j <= n; j++) ans = max(ans, (x[i] - x[j]) * (x[i] - x[j]) + (y[i] - y[j]) * (y[i] - y[j]));
	}
	printf("%.7lf", sqrt(ans));
	return 0;
}

C

首先想一种构造进制的方法去构造这个数,发现和二进制有点像,直接按二进制的来然后为 1 的部分乘 2

注意判断前导 0

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int n, f;

signed main(){
	cin >> n;
	for (int i = 60; i >= 0; i--){
		int k = 1 & (n >> i);
		if ((k == 0 && f == 1) || k == 1) cout << k * 2;
		if (k == 1) f = 1;
	}
	return 0;
}

D

一眼优先队列,有一个性质,长度为 \(k\) 的优先队列中队头一定是第 \(k\) 大或小的数。所以可以直接塞 \(k\) 个数进去,此时队头就是第 \(k\) 大的,之后塞进去第 \(k+1\) 个,再弹出,就把第 \(k+1\) 大的洗出去,第 \(k\) 个的也就更新了。

一开始想复杂了,维护一个数组手动去判断是否要弹出原来的

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int n, k;
int a[500005];
priority_queue<int, vector<int>, greater<int>> q;

signed main(){
	cin >> n >> k;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= k; i++) q.push(a[i]);
	cout << q.top() << endl;
	for (int i = k + 1; i <= n; i++){
		q.push(a[i]);
		q.pop();
		cout << q.top() << endl;
	}
	return 0;
}

E

巨水,有点像打制表格,但是根据等差数列的性质,确定首项和公差之后,整个数列就出来了。所以可以直接枚举首项和公差,然后判断是否每一位数都合法

最后如果当前位数不行,那么就跳到下一位的最小值。

之前国庆 J 组模拟赛好像做过类似的题

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int n;
int a[12] = {0,1,12,123,1234,12345,123456,1234567,12345678,123456789,9876543210};
string s;

signed main(){
	cin >> s;
	for (int i = 0; i < s.size(); i++) n = n * 10 + s[i] - '0';
	for (int i = s[0] - '0'; i <= 9; i++){
		for (int d = -9; d <= 9; d++){
			int tmp = i, ans = 0;
			for (int k = 0; k < s.size(); k++){
				if (tmp >= 0 && tmp <= 9) ans = ans * 10 + tmp;
				else break;
				tmp += d;
			}
			if (ans >= n) return cout << ans, 0;
		}
	}
	cout << a[s.size() + 1];
	return 0;
}

AT_abc242复盘

A

水题,特判一下不可能和一定可能的情况。

AC code:

#include <bits/stdc++.h>
using namespace std;

double a, b, c, x, ans; 

int main(){
	cin >> a >> b >> c >> x;
	if (x > b) ans = 0;
	else if (x <= a) ans = 1;
	else ans = c / (b - a);
	printf("%.8lf", ans);
	return 0;
}

B

直接 sort,但当时我忘了字符串能用 sort。

AC code:

#include <bits/stdc++.h>
using namespace std;

string s; 
map<char, int> mp;

int main(){
	cin >> s;
	for (int i = 0; i < s.size(); i++) mp[s[i]]++;
	for (char i = 'a'; i <= 'z'; i++){
		for (int j = 1; j <= mp[i]; j++) cout << i;
	}
	return 0;
}

C

一开始想错了,写了 30 min。直接 dp,\(dp_{i,j}\) 表示第 \(i\) 个数选 \(j\) 时的方案数。很明显可以从 \(dp_{i-1,j},dp_{i-1,j-1},dp_{i-1,j+1}\) 转移过来,答案就是 \(\max{dp_n}\)

记得转移时特判边界。

AC code:

#include <bits/stdc++.h>
using namespace std;

const int mod = 998244353;
int n, ans;
int dp[1000005][10];

int main(){
	cin >> n;
	for (int i = 1; i <= 9; i++) dp[1][i] = 1;
	for (int i = 2; i <= n; i++){
		for (int j = 1; j <= 9; j++){
			dp[i][j] = (dp[i][j] + dp[i - 1][j - 1]) % mod;
			if (j < 9) dp[i][j] = (dp[i][j] + dp[i - 1][j + 1]) % mod;
			dp[i][j] = (dp[i][j] + dp[i - 1][j]) % mod;
		}
	}
	for (int i = 1; i <= 9; i++) ans = (ans + dp[n][i]) % mod;
	cout << ans;
	return 0;
}

D

找规律

  • 不难发现首字母有循环,3 个一循环
  • 每一个字母 \(x\) 都是由第 \(x/2\) 向下取整转移过来的

所以可以直接从下往上遍历,如果到达第一个了,直接模 \(3\) 判断是哪个字符,否则记录下在 \(t\) 时间下是第几个字符。

之后从上往下走,因为由每一层是第几个字符的数据,所以可以根据题目的规律来转移,不断向下推进。

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int q, t, k; 
string s;
map<pair<char,int> , char> mp;

signed main(){
	cin >> s;
	s = ' ' + s;
	mp[{'A', 1}] = 'B', mp[{'A', 2}] = 'C', mp[{'B', 1}] = 'C', mp[{'B', 2}] = 'A', mp[{'C', 1}] = 'A', mp[{'C', 2}] = 'B';
	cin >> q;
	while (q--){
		cin >> t >> k;
		stack<int> st;
		while (t--){
			if (k == 1) t %= 3;
			st.push(k);
			k = (k + 1) / 2;
		}
		char ans = s[k];
		while (!st.empty()){
			int f = st.top(); st.pop();
			if(f != k * 2) ans = mp[{ans, 1}];
			else ans = mp[{ans, 2}];
			k = f;
		}
		cout << ans << endl;
	}
	return 0;
}

E

我的题解

参考 C题 的思路,还是类似递推的做法。

对于两个字符串 \(s\)\(t\),只要 \(t\) 前面有一个字符小于 \(s\) 的对应字符,那么后面的所有字符无论多大肯定是小于 \(s\) 的,而后面的字符无论是什么都不影响,可以直接乘上 \(26\)

根据这个性质,定义 \(dp_i\) 表示到第 \(i\) 个字符时回文串的方案数,我们可以得到转移方程 \(dp_i=dp_{i-1} \times26+a_i\),其中 \(a_i\) 表示 \(s_i\) 减去 A 的值(即可以取多少个小于 \(s_i\) 的字符)。但是有个 bug,这个代码只能处理每一个字符都小于 \(s_i\) 的情况,而无法判断全部等于 \(s\) 时的情况,需要一个 \(check\) 去判断前半段字符都与 \(s\) 相等时是否合法。最后答案就是 \(dp_n+check\)

最后加上 \(check\) 后一定要再模 \(998244353\)

在每一次询问时一定不要清空 \(dp\) 数组,不然会 TLE!!!警钟敲烂。

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int mod = 998244353;
int t, n;
int dp[1000005];
string s;

bool check(){//判断原串是否能是回文
	string t = " ";
	for (int i = 1; i <= (n + 1) / 2; i++) t += s[i];
	for (int i = n - (n + 1) / 2; i >= 1; i--) t += s[i];
	return t <= s;
}

signed main(){
	cin >> t;
	while (t--){
		cin >> n >> s;
		s = ' ' + s;
		for (int i = 1; i <= (n + 1) / 2; i++) dp[i] = (dp[i - 1] * 26 + s[i] - 'A') % mod;//递推
		cout << (dp[(n + 1) / 2] + check()) % mod << endl;
	}
	return 0;
}

AT_abc265 复盘

A

水题,但我 WA 了三次,直接暴力。

AC code:

#include <bits/stdc++.h>
using namespace std;

int n, x, y, ans = 0x3f3f3f3f;

int main(){
	cin >> x >> y >> n;
	for (int i = 0; i <= n; i++){
		for (int j = 0; j <= n; j++){
			if (i + j * 3 == n) ans = min(ans, x * i + y * j);
		}
	}
	cout << ans;
	return 0;
}

B

模拟,从第二个洞穴一直到第 \(n\) 个,过程中看时间是否足够。

AC code:

#include<bits/stdc++.h>
using namespace std;

int t, n, m, x, y;
int a[100005];
map<int, int> mp;

int main(){
	cin >> n >> m >> t;
	for (int i = 1; i < n; i++) cin >> a[i];
	for (int i = 1; i <= m; i++){
		cin >> x >> y;
		mp[x] = y;
	}
	for(int i = 2; i <= n; i++){
		t -= a[i - 1];
		if (t <= 0) return cout << "No", 0;
		t += mp[i];
	}
	cout << "Yes";
	return 0;
}

C

之前写的,shi 一样的代码,思路新奇,模拟每一次操作,看是否超出棋盘,这里用了个 while(1) 在加个 \(cnt\) 来判断在经历过超过 \(2.5 \times 10^5\) 后能不能出去。

注意边界问题。

AC code:

#include<bits/stdc++.h>
using namespace std;

int h, w, cnt;
string s[505];

int main(){
	cin >> h >> w;;
	for (int i = 1; i <= h; i++) cin >> s[i];
	int x = 1, y = 0;
	while (1){
		cnt++;
		if (s[x][y] == 'U' && x != 1) x--;
		else if (s[x][y] == 'D' && x != h) x++;
		else if (s[x][y] == 'L' && y != 0) y--;
		else if (s[x][y] == 'R' && y != w - 1) y++;
		else break;
		if (cnt > 250000) return cout << "-1", 0;
	}
	cout << x << " " << y + 1;
	return 0;
}

D

很显然要用前缀和,但还是会炸。考虑之前两个指针的情况,运用双指针可以优化掉一个 \(n\)。此时参考之前的情况,运用“四指针”,不断向后挪,只需要 \(O(n)\)

AC code:

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n, p, q, r;
int a[200005], d[200005];

signed main(){
	cin >> n >> p >> q >> r;
	for (int i = 1; i <= n; i++){
		cin >> a[i];
		d[i] = d[i - 1] + a[i];
	}
    int x = 0, y = 0, z = 0, w = 0;
	while (x <= n && y <= n && z <= n && w <= n){
		if (d[y] - d[x] > p) x++;
		if (d[z] - d[y] > q || d[y] - d[x] < p) y++;
		if (d[w] - d[z] > r || d[z] - d[y] < q) z++;
		if (d[w] - d[z] < r) w++;
		if (d[y] - d[x] == p && d[z] - d[y] == q && d[w] - d[z] == r) return cout << "Yes", 0;
	}
	cout << "No";
	return 0;
}

E

第一眼看到就是搜索,但数据范围不允许。模拟一下,在 bfs 的过程中,其实只是更新步数,有很多个点 转移 到这一个点。可以说是 dp。但时间和空间都会炸。而每次操作走的步数都是确定的,可以直接去枚举每种情况走了多少步,只需要 \(O(n^3)\) 完全不会炸。basecase 为 \(dp_{0, 0, 0}=1\),ans 为每一维之和为 \(n\) 的 dp 值之和。

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int mod = 998244353;
int n, m, a, b, c, d, e, f, ans;
int dp[305][305][305];
map<pair<int, int>, int> mp;

signed main(){
    cin >> n >> m;
    cin >> a >> b >> c >> d >> e >> f;
    for (int i = 1, x, y; i <= m; i++){
        cin >> x >> y;
        mp[{x, y}] = 1;
    }
    dp[0][0][0] = 1;
    for (int i = 0; i <= n; i++){
        for (int j = 0; j <= n; j++){
            for (int k = 0; k <= n; k++){
                if (i + j + k > n || i + j + k == 0) continue;
                int nx = i * a + j * c + k * e;
                int ny = i * b + j * d + k * f;
                if (mp[{nx, ny}] == 1) continue;
                if (i) dp[i][j][k] += dp[i - 1][j][k];
                if (j) dp[i][j][k] += dp[i][j - 1][k];
                if (k) dp[i][j][k] += dp[i][j][k - 1];
                dp[i][j][k] %= mod;
            }
        }
    }
    for (int i = 0; i <= n; i++){
        for (int j = 0; j <= n; j++){
            for (int k = 0; k <= n; k++){
                if (i + j + k == n) ans = (ans + dp[i][j][k]) % mod;
            }
        }
    }
    cout << ans << endl;
    return 0;
}

AT_abc266复盘

A

直接输出,water 题

AC code:

#include <bits/stdc++.h>
using namespace std;

string s;

int main(){
	cin >> s;
	cout << s[s.size() / 2];
	return 0;
}

B

直接遍历 \(0\)\(998244352\),判断是否能整除。

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int mod = 998244353;
int n;

signed main(){
	cin >> n;
	for (int i = 0; i < mod; i++){
		if ((n - i) % mod == 0) return cout << i, 0;
	}
	return 0;
}

C

凸四边形的一个性质:对于每一条对角线,剩下的两个顶点一定在对角线两侧。

如何判断两个点是否在一条直线的两侧?只需要把这个点映射到这条直线上,得到两个映射点,看这两个点是否在映射点的两侧。

遍历每一条对角线,直接根据这个判断就好。

判断时注意判断是否大于 \(0\) 时要设置精度 \(eps=1 \times 10 ^ {-8}\)

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const double eps = 1e-8;
double x[5], y[5];

int check(double x, double y, double xx, double yy, double k, double b){
	double s1 = k * x + b;
	double s2 = k * xx + b;
	if (y - s1 > eps && s2 - yy > eps) return 1;
	else if (s1 - y > eps && yy - s2 > eps) return 1;
	else return 0;
}

signed main(){
	for (int i = 0; i < 4; i++) cin >> x[i] >> y[i];
	for (int i = 0; i < 4; i++){
		int j = (i + 2) % 4;
		double k = (y[i] - y[j]) / (x[i] - x[j]);
		double b = y[i] - k * x[i];
		int px1 = x[(i + 1) % 4], px2 = x[(i + 3) % 4];
		int py1 = y[(i + 1) % 4], py2 = y[(i + 3) % 4];
		if (!check(px1, py1, px2, py2, k, b)) return cout << "No", 0;
	}
	cout << "Yes";
	return 0;
}

D

一眼 dp,\(t\) 不知道,位置不知道,直接设 \(dp_{i,j}\) 为第 \(i\) 时间在 \(j\) 号坑拿到的最大值。

basecase:没有 basecase。

转移方程:考虑从前面走过来和从后面走过来,加上当前这一时间 snuke 的长度。寻找当前 snuke 的长度可以直接二分(时间满足单调性)。

ans:\(dp_{maxt}\) 的最大值。

注意从前面和后面转移过来时的边界。

#include <bits/stdc++.h>
#define int long long
using namespace std;

int n, maxt, ans;
int t[100005], a[100005], x[100005], dp[100005][10];

signed main(){
	cin >> n;
	for (int i = 1; i <= n; i++){
		cin >> t[i] >> x[i] >> a[i];
		maxt = max(maxt, t[i]);
	}
	for (int i = 1; i <= maxt; i++){
		for (int j = 0; j <= min(i, 4ll); j++){
			dp[i][j] = dp[i - 1][j];
			if (j > 0) dp[i][j] = max(dp[i - 1][j - 1], dp[i][j]);
			if (j < 4) dp[i][j] = max(dp[i - 1][j + 1], dp[i][j]);
			int pos = lower_bound(t + 1, t + n + 1, i) - t;
			if (t[pos] == i && x[pos] == j) dp[i][j] += a[pos];
		}
	}
	for (int i = 0; i <= 4; i++) ans = max(ans, dp[maxt][i]);
	cout << ans;
	return 0;
}

E

期望:简单理解就是满足要求的概率的带权平均数。

所以这题直接每一次操作判断投的这个筛子的点数是否大于上一次的期望,如果大于就投,否则就不投,取上一次的期望。

最后记得把计算得到的总和除以 \(6\)

最大值肯定是投到最后一次的期望。

#include <bits/stdc++.h>
using namespace std;

int n;
double dp[105];

int main(){
	cin >> n;
	for (int i = 1; i <= n; i++){
		for (int j = 1; j <= 6; j++){
			if (j > dp[i - 1]) dp[i] += j;
			else dp[i] += dp[i - 1];
		}
		dp[i] /= 6;
	}
	printf("%.10lf", dp[n]);
	return 0;
}

G

my 题解

很明显发现是一道排列组合的题。

开始推式子,我们发现 rg 的排列会影响到 rg 的个数,不妨先把 r 提出来,先去算剩下有多少种排列,再把 r 加进去计算总排列数。而 rg 中包含了一个 r 和一个 g,所以之后排列的 rg 的个数为 \(R-k\)\(G - k\)

至于式子,考虑简化版的,只有 rgb 的限制,那么 r 就有 \(C_{R+G+B}^R \times C_{G+B}^{G}\) 种情况(先填 r 后填 g 最后 b 的位置也就确定了,\(R+G+B\) 表示字符串总长度)。那么原题中把 r 提出来后同理有 \(C_{G-k+B+k}^{G-k} \times C_{B+k}^k\) 种情况。

再考虑加了 r 之后的情况,因为前面的操作使得我们不能在之后的操作中形成 rg,所以之后 r 能选择的位置就只剩下了总数减去 r 的个数。

最后得到式子 \(C_{G-k+B+k}^{G-k} \times C_{B+k}^k \times C_{R-k+B+k}^{R-k}=C_{G+B}^{G-k} \times C_{B+k}^k \times C_{R+B}^{R-k}\)

很明显这题不能暴力求组合数,必须通过卢卡斯定理。

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

const int mod = 998244353;
int r, g, b, k;

int ksm(int a, int b, int p){
	int res = 1;
	while (b){
		if (b & 1) res = res * a % p;
		a = a * a % p;
		b >>= 1;
	}
	return res;
}

int C(int a, int b, int p){
	if (b > a) return 0;
	int res = 1;
	for (int i = a, j = 1; j <= b; i--, j++){
		res = res * i % p;
		res = res * ksm(j, p - 2, p) % p;
	}
	return res;
}

int lucas(int a, int b, int p){
	if (a < p && b < p) return C(a, b, p);
	return C(a % p, b % p, p) * lucas(a / p, b / p, p) % p; 
}

signed main(){
	cin >> r >> g >> b >> k;
	cout << lucas(g + b, g - k, mod) * lucas(b + k, k, mod) % mod * lucas(r + b, r - k, mod) % mod << endl;//lucas套公式
	return 0;
}

AT_abc267复盘

A

AC code:

#include<bits/stdc++.h>
using namespace std;

map<string,int> mp;
string s;

int main(){
	mp["Monday"] = 1;
	mp["Tuesday"] = 2;
	mp["Wednesday"] = 3;
	mp["Thursday"] = 4;
	mp["Friday"] = 5;
	cin >> s;
	cout<< 6 - mp[s];
	return 0;
}

B

直接 if 大发过掉。

AC code:

#include<bits/stdc++.h>
using namespace std;

char s[15];
int a[15];

int main(){
	cin >> s + 1;
	int len = strlen(s + 1);
	for (int i = 1; i <= len; i++){
		if (s[i] == '0') a[i] = 0;
		else a[i] = 1;
	}
	if (a[1] == 0){
		if(a[1]==0&&a[5]==0&&(a[2]==1||a[3]==1||a[4]==1||a[6]==1||a[7]==1||a[8]==1||a[9]==1||a[10]==1)) cout<<"Yes";
		else if(a[2]==0&&a[8]==0&&(a[1]==1||a[3]==1||a[4]==1||a[6]==1||a[7]==1||a[5]==1||a[9]==1||a[10]==1)) cout<<"Yes";
		else if(a[3]==0&&a[9]==0&&(a[1]==1||a[2]==1||a[4]==1||a[6]==1||a[7]==1||a[8]==1||a[5]==1||a[10]==1)) cout<<"Yes";
		else cout<<"No";
	}
	else cout<<"No";
	return 0;
}

C

这个数据范围很明显不能用暴力,考虑前缀和,然后发现区间向后挪一格等价于将 \(sum\) 减去第一项,加上 \(m \times\) 后一项,然后慢慢挪就好了。

#include <bits/stdc++.h>
#define int long long
using namespace std;

int n, m, ans = -1e18, sum;
int a[200005], s[200005];

signed main(){
    cin >> n >> m;
    for (int i = 1; i <= n; i++){
        cin >> a[i];
        s[i] = s[i - 1] + a[i];
    }
    for (int i = 1; i <= m; i++) sum += a[i] * i;
    for (int i = m; i <= n; i++){
        ans = max(ans, sum);
        sum -= (s[i] - s[i - m]);
        sum += a[i + 1] * m;
    }
    cout << ans;
    return 0;
}

D

一眼 dp,定义状态 \(dp_{i,j}\) 表示去到第 \(i\) 个前面取 \(j\) 个的最大值。basecase 为 \(dp_{i,0}=0\),ans 为 \(dp_{n,m}\)。转移方程也不难,类似背包,只考虑这一项取不取,取就在前一项的基础上加上 \(a_i\times m\),不取就跟前一项一样。

#include <bits/stdc++.h>
#define int long long
using namespace std;

int n, m, ans, sum;
int a[3005], s[3005], dp[3005][3005];

signed main(){
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    memset(dp, -0x3f, sizeof(dp));
    for (int i = 0; i <= n; i++) dp[i][0] = 0;
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= m; j++) dp[i][j] = max(dp[i - 1][j - 1] + a[i] * j, dp[i - 1][j]);
    }
    cout << dp[n][m];
    return 0;
}

E

根据题面,最大值的最小值,很容易想到二分答案,去二分单次操作的代价。

二分答案中的 check 可以直接通过 bfs 搜一遍看是否每个点都符合要求。但在每次 bfs 都统计一遍子节点的点权和很明显就会 TLE,所以这里采用打制表格预处理,在 bfs 前处理出来点权和。

对于 bfs,类似于拓扑排序的思路,把点权和小于 \(mid\) 的节点加入队列中,然后按 bfs 的思路向下一层搜。设当前节点为 \(u\),子节点为 \(v\),则 \(v\) 的点权和要减去 \(a_u\)(因为 \(u\) 已经访问过了)。再判断 \(v\) 是否符合需求,如果符合,那么加入队列。最后判断是否全部节点都被遍历过了,只有全部都能遍历,才满足要求。

在遍历 \(v\) 时,不需要去看其他已经在队列中的节点权值。如果 \(v\) 节点满足要求,那么直接加入,否则在遍历另一个在队列中与 \(v\) 相关的节点 \(u_1\) 时也会减去相应权值,不需要在 \(u\) 一步减去。

不开 long long 见祖宗。

二分时 \(l\) 记得设为 \(0\),警钟敲烂。

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int n, m, sum;
int a[200005], s[200005], vis[200005], t[200005];
vector<int> g[200005];

bool check(int mid){//直接bfs
    queue<int> q;
    memset(vis, 0, sizeof(vis));
    for (int i = 1; i <= n; i++){
        t[i] = s[i];
        if (s[i] <= mid){//符合要求加入队列
            q.push(i);
            vis[i] = 1;
        }
    }
    while (!q.empty()){
        int u = q.front(); q.pop();
        for (int i = 0; i < g[u].size(); i++){
            int v = g[u][i];
            t[v] -= a[u];//只看u节点的权值
            if (t[v] <= mid && vis[v] == 0){//符合要求加入队列
                vis[v] = 1;
                q.push(v);
            }
        }
    }
    for (int i = 1; i <= n; i++){//判断是否全部遍历过
        if (vis[i] == 0) return 0;
    }
    return 1;
}

signed main(){
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1, u, v; i <= m; i++){
        cin >> u >> v;
        g[u].push_back(v);
        g[v].push_back(u);
    }
    for (int i = 1; i <= n; i++){//预处理子节点权值和
        for (int j = 0; j < g[i].size(); j++) s[i] += a[g[i][j]];
    }
    int l = 0, r = 1e18;//l为0!!!!!
    while (l < r){//二分答案
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    cout << r;
    return 0;
}

AT_abc268 复盘

A

用一个桶一存直接秒掉

AC code:

#include <bits/stdc++.h>
using namespace std;

int a, b, c, d, e, cnt;
int mp[105];

int main(){
	cin >> a >> b >> c >> d >> e;
	mp[a]++, mp[b]++, mp[c]++, mp[d]++, mp[e]++;
	for (int i = 0; i <= 100; i++) cnt += (mp[i] >= 1);
	cout << cnt;
	return 0;
}

B

遍历一遍,如果有不相同的直接 return

开始时特判一下长度

AC code:

#include<bits/stdc++.h>
using namespace std;

string s,t;

int main(){
	cin >> s >> t;
	if (s.size() > t.size()) return cout << "No", 0;
	for (int i = 0; i < s.size(); i++){
		if (s[i] != t[i]) return cout << "No", 0;
	}
	cout << "Yes";
	return 0;
}

C

可以直接把每一个点当作零点去记录一次,最后看最多的有多少个

AC code:


#include<bits/stdc++.h>
using namespace std;

int n, p, ans;
int cnt[200005];

int main(){
	cin >> n;
	for (int i = 0; i < n; i++){
		cin >> p;
		if (i == p){
			cnt[n - 1]++;
			cnt[0]++;
			cnt[1]++;
		}
		else if(i < p){
			cnt[p - i - 1]++;
			cnt[p - i]++;
			cnt[p - i + 1]++;
		}
		else{
			cnt[n + p - i - 1]++;
			cnt[n + p - i]++;
			cnt[n + p - i + 1]++;
		}
	}
	for (int i = 0; i < n; i++) ans = max(ans, cnt[i]);
	cout << ans;
	return 0;
}

D

题目一眼看出是 dfs 暴搜的题,只需要先全排列一下每一个字符串的顺序,再根据字符串已有的长度去再 dfs 一遍中间插入 _ 的数量。

对于不能重复,易得通过一个 map 去存储已经出现的 string,最后在 dfs 找到答案的时候看一下 map 中有没有出现即可。

AC code:

#include<bits/stdc++.h>
using namespace std;

int n, m;
int a[10], vis[10];
string s[10], t[100005];
vector<string> v;
map<string, int> mp;

void dfs1(int x, int sum){
	if (sum < 0) return ;
	if (x == n){
		if(16 - sum < 3) return;
		string ans = v[0];
		for (int i = 1; i < n; i++){
			for (int j = 1; j <= a[i]; j++) ans += "_";
			ans += v[i];
		}
		if (mp[ans] == 0) cout << ans, exit(0);
		return ;
	}
	for (int i = 1; i <= sum; i++){
		a[x] = i;
		dfs1(x + 1, sum - i);
	}
}

void dfs(int x, int sum){
	if (x == n + 1){
		memset(a, 0, sizeof(a));
		dfs1(1, 16 - sum);
		return ;
	}
	for (int i = 1; i <= n; i++){
		if (vis[i] == 0){
			v.push_back(s[i]), vis[i] = 1;
			dfs(x + 1, sum + s[i].size());
			vis[i] = 0, v.pop_back();
		}
	}
}

int main(){
	cin >> n >> m;
	for (int i = 1; i <= n; i++) cin >> s[i];
	for (int i = 1; i <= m; i++){
		cin >> t[i];
		mp[t[i]] = 1;
	}
	dfs(1, 0);
	cout << -1;
	return 0;
}

F

首先想到贪心,去记录每一个数字部分有多少,有多少个 X,之后发现可以直接 sort 一遍,在 \(cmp\) 中去判断是 \(a+b\) 所构成的字符串权值更大,还是 \(b+a\) 更大。通过这个 sort 出来之后,直接拼接,然后去调用计算权值的函数输出权值就刑。

这里因为前面两个字符串调换位置只会影响这两个字符串的权值,而不影响后面的,所以可以用贪心。

AC code:

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n;
string s[200005], ans;

int f(string a){
	int sum = 0, cnt = 0;
	for (int i = 0; i < a.size(); i++){
		if (a[i] == 'X') cnt++;
		else sum += cnt * (a[i] - '0');
	}
	return sum;
}

bool cmp(string a, string b){
	return f(a + b) > f(b + a);
}

signed main(){
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> s[i];
	sort(s + 1, s + n + 1, cmp);
	for (int i = 1; i <= n; i++) ans += s[i];
	cout << f(ans);
	return 0;
}

AT_abc270 复盘

A

很烦,if 大法秒了。

不过易发现,他们都是 2 的整数次方,可以采用位运算,直接按位或就好。

AC code:

#include<bits/stdc++.h>
using namespace std;

int a, b;

int main(){
	cin >> a >> b;
	cout << (a | b);
	return 0;
} 

B

还是 if,分类讨论,能直接去,要拿锤子去,过不去。

AC code:

#include<bits/stdc++.h>
using namespace std;

int x, y, z;

int main(){
	cin >> x >> y >> z;
	if ((x > 0 && y < 0) || (x < 0 && y > 0) || (x > 0 && x < y) || (x < 0 && x > y)) cout << abs(x);
	else if ((y > 0 && z > y) || (y < 0 && z < y)) cout << -1;
	else cout << abs(x - z) + abs(z);
	return 0;
}

C

直接建树,从 \(x\) 开始一直 dfs 直到 \(y\),然后输出路径。

AC code:

#include<bits/stdc++.h>
using namespace std;

int n, x, y;
vector<int> g[200005], v;

void dfs(int x,int fa){
	v.push_back(x);
	if (x == y){
		for (int i = 0; i < v.size(); i++) cout << v[i] << " ";
		return ;
	}
	for (int i = 0; i < g[x].size(); i++){
		if (g[x][i] != fa) dfs(g[x][i], x);
	}
	v.pop_back();
	return ;
}

int main(){
	cin >> n >> x >> y;
	for(int i = 1, u, v; i < n; i++){
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);	
	}
	dfs(x, -1);
	return 0;
}

D

考虑贪心,每次贪心取最大的,但是发现可能最后有剩余导致 WA。

贪心不对那就去找 dp,直接设 \(dp_i\) 为取 \(i\) 个能取到得最大值。易得一个性质,如果甲拿了 \(dp_i\) 那么乙一定拿了 \(i-dp_i\)。所以可得转移方程为 \(dp_i=max{i-dp_{i-a_j}}\)

AC code:

#include <bits/stdc++.h>
using namespace std;

int n, k;
int a[10005], dp[10005];

int main(){
    cin >> n >> k;
    for (int i = 1; i <= k; i++) cin >> a[i];
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= k; j++){
            if (i < a[j]) continue;
            dp[i] = max(dp[i], i - dp[i - a[j]]);
        }
    }
    cout << dp[n];
    return 0;
}

E

考虑到每一次取都是只拿一个,那可以一次性直接看取了多少个。枚举不行,那可以试试二分,发现满足单调性——拿的轮数越多,剩的越少,直接去二分最大轮数,最后从头到尾遍历一遍输出。

但有个问题,我们发现他不一定是拿整数轮,所以还要再来遍历一遍,最后输出。

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int n, k;
int a[100005];

bool check(int mid){
    int sum = 0;
    for (int i = 1; i <= n; i++) sum += min(a[i], mid);
    return sum <= k;
}

signed main(){
    cin >> n >> k;
    for (int i = 1; i <= n; i++) cin >> a[i];
    int l = 1, r = k, ans = 0;
    while (l <= r){
        int mid = l + r >> 1;
        if (check(mid)) l = mid + 1, ans = mid;
        else r = mid - 1;
    }
    for (int i = 1; i <= n; i++) k -= min(a[i], ans), a[i] -= min(a[i], ans);
    for (int i = 1; i <= n; i++){
        if (k == 0) break;
        if (a[i]) a[i]--, k--;
    }
    for (int i = 1; i <= n; i++) cout << a[i] << " ";
    return 0;
}

AT_abc271 复盘

A

直接暴力转 16 进制。

#include<bits/stdc++.h>
using namespace std;
int n,len;
int a[10005];
int main(){
	cin>>n;
	while(n){
		a[++len]=n%16;
		n/=16;	
	}
	for(int i=len;i<2;i++) cout<<"0"; 
	for(int i=len;i>=1;i--){
		if(a[i]==10)cout<<"A";
		else if(a[i]==11) cout<<"B";
		else if(a[i]==12) cout<<"C";
		else if(a[i]==13) cout<<"D";
		else if(a[i]==14) cout<<"E";
		else if(a[i]==15) cout<<"F";
		else cout<<a[i];
	}
}

B

直接 map 套 vector 秒了。

AC code:

#include <bits/stdc++.h>
using namespace std;

int n, q;
vector<int> v[200005];

int main(){
    cin >> n >> q;
    for (int i = 1, l; i <= n; i++){
        cin >> l;
        for (int j = 1, a; j <= l; j++){
            cin >> a;
            v[i].push_back(a);
        }
    }
    for (int i = 1, s, t; i <= q; i++){
        cin >> s >> t;
        cout << v[s][t - 1] << endl;
    }
    return 0;
}

C

易观察出一个性质,不可能取到大于 \(n\) 的书。只需要记录 \(n\) 以内需要买多少本,然后判断已经有的加上后面卖的是否有 \(i\),有的话记录最大值。

注意是两本书换一本书。

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int n;
int a[300005], b[300005];

signed main(){
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++){
        if (a[i] <= n) b[a[i]] = 1;
    }
    for (int i = 1; i <= n + 1; i++) b[i] += b[i - 1];
    for (int i = 1; i <= n + 1; i++){
        if (i - b[i] > (n - b[i]) / 2) return cout << i - 1, 0;
    }
    return 0;
}

D

abc240c 一模一样,可以通过 dp 或者 bitset 来解决。dp 就是记录取到第 \(i\) 个和能否为 \(j\)。basecase 为 \(dp_{0,0}=1\),ans 为 \(dp_{n,m}\) 是否为 1。

在输出取左还是取右的时候,可以直接从 \(n\) 反推回去,看往左取可不可能,往右可不可能。这里因为每一个可能的数都是从头转移过来的,所以如果当前节点取左可能,那么就必有一种合法情况。

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int n, x;
int a[105], b[105], dp[105][10005];
string ans;

void dfs(int x, int s){
    if (x == 0) return ;
    if (a[x] <= s && dp[x - 1][s - a[x]] == 1){
        dfs(x - 1, s - a[x]);
        ans += 'H';
    }
    else if (b[x] <= s && dp[x - 1][s - b[x]] == 1){
        dfs(x - 1, s - b[x]);
        ans += 'T';
    }
}

signed main(){
	scanf("%lld%lld", &n, &x);
	for (int i = 1; i <= n; i++) scanf("%lld%lld", &a[i], &b[i]);
	dp[0][0] = 1;
	for (int i = 1; i <= n; i++){
		for (int j = 1; j <= x; j++){
			if (j >= a[i]) dp[i][j] = dp[i - 1][j - a[i]] | dp[i][j];
			if (j >= b[i]) dp[i][j] = dp[i - 1][j - b[i]] | dp[i][j];
		}
	}
	if (dp[n][x]){
        cout << "Yes\n";
        dfs(n, x);
        cout << ans;
    }
	else printf("No");
	return 0;
}

E

看起来是一道图论,但是发现题目说子序列,可以想到用 dp 去求。

状态表示:子序列我们可以选或不选,对应到上面的边就是加不加这条边的权值。这样一来我们就不需要去记录边而是记录到这一点权值的最小值。综上,\(dp_i\) 表示到第 \(i\) 个点好路的权值最小值。

状态计算:根据状态表示的思想,子序列上 dp 我们不需要考虑前面选了哪几条边,我们只关心这个点经过这次操作后权值的最小,所以得到方程 \(dp_{b_i}=\min(dp_{b_i},dp_{a_i}+c_i)\)

basecase:显然一号点最小权值为 \(0\)\(dp_1=0\)

ans:最终只要判断 \(dp_n\) 是否能到达(是否为极大值),能则输出 \(dp_n\),否则输出 \(-1\)

不开 long long 见祖宗?

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int n, m, k;
int a[200005], b[200005], c[200005], e[200005], dp[200005];

signed main(){
	cin >> n >> m >> k;
	for (int i = 1; i <= m; i++) cin >> a[i] >> b[i] >> c[i];
	for (int i = 1; i <= k; i++) cin >> e[i];
	memset(dp, 0x3f, sizeof(dp));
	dp[1] = 0;
	for (int i = 1; i <= k; i++){
		dp[b[e[i]]] = min(dp[b[e[i]]], dp[a[e[i]]] + c[e[i]]);
	}
	if (dp[n] == 0x3f3f3f3f3f3f3f3f) cout << "-1";
	else cout << dp[n];
	return 0;
}

F

一眼直接暴力,但肯定会 TLE。

bdfs 一下,发现有一种思想叫 Meet In Middle。就是从头 dfs 到中间,再从尾 dfs 到中间汇聚答案。这种类似于分治,适用于对于答案具体路径没有需求的题目。

这题就很容易了,从 \((1,1)\) 搜到对角线,再从 \((n,n)\) 搜回对角线统计答案。

这里有一个性质,\(x\) xor \(x = 0\),根据这个,在往回搜的时候直接加上搜过来时的当前异或值得个数就是当前路径得答案。可以使用 map 套 pair 记录搜过来时当前节点当前异或值出现次数。

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int n, ans;
int a[25][25];
map<pair<pair<int, int>, int>, int> mp;

void dfs1(int x, int y, int res){
    res ^= a[x][y];
    if (x + y == n + 1){
        mp[{{x, y}, res}]++;
        return ;
    }
    dfs1(x + 1, y, res);
    dfs1(x, y + 1, res);
}

void dfs2(int x, int y, int res){
    if (x + y == n + 1){
        ans += mp[{{x, y}, res}];
        return ;
    }
    res ^= a[x][y];
    dfs2(x - 1, y, res);
    dfs2(x, y - 1, res);
}

signed main(){
    cin >> n;
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= n; j++) cin >> a[i][j];
    }
    dfs1(1, 1, 0);
    dfs2(n, n, 0);
    cout << ans;
    return 0;
}

AT_abc273 复盘

A

直接按照公式模拟就好

AC code:

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n;

int f(int k){
	if (k == 0) return 1;
	return k * f(k - 1);
}

signed main(){
	cin >> n;
	cout << f(n);
	return 0;
} 

B

直接暴力,模拟每一次四舍五入,再判断是舍去还是进位。

AC code:

#include<bits/stdc++.h>
#define int long long
using namespace std;

int k, n, l, r;

signed main(){
	cin >> n >> k;
	for (int i = 1; i <= k; i++){
		l = n / (int)pow(10, i) * (int)pow(10, i);
		r = l + pow(10, i);
		if (abs(n - l) < abs(n - r)) n = l;
		else n = r;
	}
	cout << n;
	return 0;
}

C

不难想到先 sort 一遍,然后判断前面有多少个不同的数比我大,拿一个桶记录一下,最后输出桶。

AC code:

#include<bits/stdc++.h>
#define int long long
using namespace std;

int n;
int a[200005], b[200005], l[200005];

signed main(){
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i];
	sort(a + 1, a + n + 1);
	for (int i = n; i >= 1; i--){
		b[i] = b[i + 1];
		if (a[i] != a[i + 1]) b[i]++;
		l[b[i]]++;
	}
	for (int i = 1; i <= n; i++) cout << l[i] << endl;
	return 0;
}

D

还是模拟就好,记录每一行每一列每一个障碍的位置。对于每一次移动,判断移动距离是否会撞到障碍物(取 min 或 max)。

找障碍物的时候可以参考之前切蛋糕,用二分去找每一个障碍物。但要求单调性,所以得用 set 才能确保在 \(n logn\) 的复杂度内解决。

AC code:

#include <bits/stdc++.h>
using namespace std;

int h, w, x, y, n, q;
string opt;
map<int, set<int>> r, c;

int main(){
    cin >> h >> w >> x >> y >> n;
    for (int i = 1, xx, yy; i <= n; i++){
        cin >> xx >> yy;
        r[xx].insert(yy);
        c[yy].insert(xx);
    }
    cin >> q;
    for (int _ = 1, d; _ <= q; _++){
        cin >> opt >> d;
        r[x].insert(0), r[x].insert(w + 1);
        c[y].insert(0), c[y].insert(h + 1);
        if (opt == "L"){
        	auto t = r[x].lower_bound(y);
        	if (t == r[x].begin()) y = max(y - d, 1);
        	else y = max(y - d, *--t + 1);
		}
		else if (opt == "R"){
			auto t = r[x].lower_bound(y);
			if (t == r[x].end()) y = min(y + d, w);
			else y = min(y + d, *t - 1);
		}
		else if (opt == "U"){
			auto t = c[y].lower_bound(x);
			if (t == c[y].begin()) x = max(x - d, 1);
			else x = max(x - d, *--t + 1);
		}
		else if (opt == "D"){
			auto t = c[y].lower_bound(x);
			if (t == c[y].end()) x = min(x + d, h);
			else x = min(x + d, *t - 1);
		}
		cout << x << " " << y << endl;
    }
    return 0;
}

E

考虑暴力,把每一个版本都存一次,肯定炸。肯定是用一个数组去维护当前版本,很多个数组去维护这些保存的版本。

很明显当前版本是一个链表,用数组模拟链表,一个 \(val\) 数组表示这个元素的值,\(fa\) 数组表示这个链表上一个元素的位置,当前列表最后一个数位置为 \(now\)

添加和删除操作都是在尾部进行的不会影响到前面的值,那么我们的存档操作可以直接记录 \(now\),然后读档时直接更新 \(now\) 到保存的位置上。

这又引出一个问题,如何处理删除操作又不影响当前列表?不难想到直接更改 \(now\) 就好了,因为在添加的时候是将新来的数挂在 \(now\) 后面的,直接把前面删去的数覆盖了(虽然此时可能又多个数的 \(fa\)\(now\),但是在添加操作的时候只会把 \(now\) 更新到最新的数上,保存也只会保存到 \(now\),和那些 \(fa\) 相同的无关)。

最后输出部分,我们的下标都是从 \(1\) 开始的,而且所有输入的值都是大于 \(1\) 的,只要判断 \(val_{now}\) 是否大于 \(0\)

注意,存档时的 \(x\) 高达 \(1 \times 10^9\),需要使用 map 去存是哪一个存档对应的位置。

AC code:

#include <bits/stdc++.h>
using namespace std;

int n, x, pos, now;
int val[500005], fa[500005];
string opt;
map<int, int> save;

int main(){
	cin >> n;
	for (int i = 1; i <= n; i++){
		cin >> opt;
		if (opt == "ADD"){
			cin >> x;
			fa[++pos] = now;//类似链表的加入
			val[pos] = x;
			now = pos;//跳到最新的上面
		}
		if (opt == "DELETE") now = fa[now];//只需要挪动now就行
		if (opt == "SAVE"){
			cin >> x;
			save[x] = now;//存档时只需要存位置
		}
		if (opt == "LOAD"){
			cin >> x;
			now = save[x];
		}
		cout << ((val[now] == 0) ? -1 : val[now]) << endl;//判断是否为空
	}
	return 0;
}

AT_abc278 复盘

A

非常水,只是注意在输出 \(0\) 的时候要判断 \(k\) 是否大于 \(n\)

AC code:

#include <bits/stdc++.h>
using namespace std;

int n, k, a[105];

int main(){
    cin >> n >> k;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = k + 1; i <= n; i++) cout << a[i] << " ";
	for (int i = 1; i <= min(n, k); i++) cout << "0 ";
	return 0;
} 

B

有点恶心,模拟题,分类讨论,在当前小时和不在当前小时,如果当天都没有,那么输出 0 0。再加上一点点的数位拆分就刑了。

AC code:

#include <bits/stdc++.h>
using namespace std;

int h, m, a, b, c, d;

int main(){
	cin >> h >> m;
	a = h / 10, b = h % 10;
	for (int i = m; i <= 59; i++){
		c = i / 10, d = i % 10;
		if ((a < 2 || (a == 2 && c <= 3)) && b <= 5) return cout << h << " " << i << endl, 0;
	}
	for (int i = h + 1; i <= 23; i++){
		for (int j = 0; j <= 59; j++){
			a = i / 10, b = i % 10, c = j / 10, d = j % 10;
			if ((a < 2 || (a == 2 && c <= 3)) && b <= 5) return cout << i << " " << j << endl, 0;
		}
	}
	cout << 0 << " " << 0 << endl;
	return 0;
} 

C

比 B 题简单多了,只需要一个 map 套 pair 记录一下就好了。

注意不能用二维数组,会炸空间。

AC code:

#include <bits/stdc++.h>
using namespace std;

int n, q, op, a, b;
map<pair<int, int>, int> mp;

int main(){
	cin >> n >> q;
	while (q--){
		cin >> op >> a >> b;
		if (op == 1) mp[{a, b}] = 1;
		else if (op == 2) mp[{a, b}] = 0;
		else if (op == 3){
			if (mp[{a, b}] && mp[{b, a}]) cout << "Yes\n";
			else cout << "No\n";
		}
	}
	return 0;
}

D

很容易想到可以记录当前整个数组被初始化成啥样了(即记录最近一个 \(1\)\(k\) 值),然后对每一项进行操作 \(2\),最后输出操作 \(2\) 的结果与操作 \(1\) 的结果之和。

注意:每次清空数组不能用 \(memset\),不然会 TLE,可以采用二维数组的办法,没遇到一个操作 \(1\) 就跳到下一维,用 \(map\),数组会炸。

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int n, q, op, k, x, p, res;
map<int, int> a[200005];

signed main(){
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[p][i];
    cin >> q;
    for (int i = 1; i <= q; i++){
        cin >> op;
        if (op == 1){
            cin >> k;
            p++, res = k;
        }
        if (op == 2){
            cin >> x >> k;
            a[p][x] += k;
        }
        if (op == 3){
            cin >> x;
            cout << a[p][x] + res << endl;
        }
    }
	return 0;
}

E

感觉 E 的思维量比 D 少。只需要一个前缀和记录每个数字在 \((i,j)\) 前出现了多少次,然后看除了框出来的矩阵其他有多少个数出现次数大于 0 就刑。

数据范围很小,开三维数组没问题,前两维标准前缀和,第三维表示是哪个数。

AC code:

#include <bits/stdc++.h>
using namespace std;

int n, m, k, h, w;
int a[305][305], s[305][305][305];

int main(){
    cin >> n >> m >> k >> h >> w;
    for (int i = 1; i <= n; i++){
        for (int j = 1; j <= m; j++){
            cin >> a[i][j];
            for (int l = 1; l <= k; l++) s[i][j][l] = s[i - 1][j][l] + s[i][j - 1][l] - s[i - 1][j - 1][l] + (l == a[i][j]);
        }
    }
    for (int i = h; i <= n; i++){
        for (int j = w; j <= m; j++){
            int cnt = 0;
            for (int l = 1; l <= k; l++){
                if (s[n][m][l] - (s[i][j][l] - s[i - h][j][l] - s[i][j - w][l] + s[i - h][j - w][l]) >= 1) cnt++;
            }
            cout << cnt << " ";
        }
        cout << endl;
    }
	return 0;
}

AT_abc279 复盘

A

水题,只需要判断是 \(v\) 还是 \(w\)

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int cnt;
string s;

signed main(){
	cin >> s;
	for (int i = 0; i < s.size(); i++){
		if (s[i] == 'v') cnt++;
		else cnt += 2;
	}
	cout << cnt;
    return 0;
}

B

只需要看 \(t\) 在不在 \(s\) 中就刑,数据范围很小直接暴力,也可以使用 kmp。

AC code:

#include<bits/stdc++.h>
using namespace std;

int nxt[1000005] = {-1}, lens, lenp; 
string s, p;

signed main(){
    cin >> s >> p;
    lens = s.size(), lenp = p.size();
    for (int i = 1, len = -1; i < lenp; i++){     
	    while (len > -1 && p[i] != p[len + 1]) len = nxt[len]; 
        if (p[len + 1] == p[i]) len++;
        nxt[i] = len;
    }
    for (int i = 0, len = -1; i < lens; i++){
        while (len > -1 && p[len + 1] != s[i]) len = nxt[len];
        if (p[len + 1] == s[i]) len++;
        if (len == lenp - 1) return cout << "Yes", 0;
    }
    cout << "No";
    return 0;
}

C

显然,我们可以记录每一列的情况然后排序,最后判断两个矩阵中排好序的列是否相等同。

注意:不能只看列中一个字符的个数;不能记录行中每个字符的个数。

AC code:

#include <bits/stdc++.h>
using namespace std;

int h, w;
string a, s[400005], t[400005];

int main(){
	cin >> h >> w;
	for (int i = 1; i <= h; i++){
        cin >> a;
        for (int j = 0; j < w; j++) s[j] += a[j];
    }
    sort(s, s + w);
	for (int i = 1; i <= h; i++){
		cin >> a; 
		for (int j = 0; j < w; j++) t[j] += a[j]; 
	}
    sort(t, t + w);
    for (int i = 0; i < w; i++){
        if (s[i] != t[i]) return cout << "No", 0;
    }
	cout << "Yes";
	return 0;
}

D

观察函数,化简一下发现是一个反比例和指数函数的复合函数加上一个一次函数。可以发现是一个在正数域单调递减的函数加上一个单调递增的函数,是一个向下突出的函数并且只有两个单调区间,所以可以用求导法直接找出极值。

输出时判断 \(x\) 的范围,如果小于 \(1\) 就输出 \(f(1)\),否则输出 \(x\) 向上取整和向下取整的最小函数值。

注意:一定要按照题目的函数来写!!!该用 long long 的时候用 long long,不要随便用 double!!!

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int a, b;

double f(double x){
    return a / sqrt(x) + b * (x - 1);
}

signed main(){
    cin >> a >> b;
    double x = pow(a / b / 2.0, 2.0 / 3.0);
    if (x < 1) printf("%.10lf", f(1));
    else printf("%.10lf", min(f(ceil(x)), f(floor(x))));
	return 0;
}

E

一开始想找 swap 操作的特性,结果发现没有什么明显的性质。但是把去掉 2 号操作之前的和之后的结果写出来,发现操作之后 1 对应的位置恰好是与操作 1 交换的那个书的位置。这是我们可以大胆猜想,去掉拿一个操作就是输出原本与之交换的数的位置。这时候又发现如果 1 没有参与交换,显然,它的位置不变,直接输出最终结果。

细节,可以使用两个数组记录第 \(i\) 次操作的两个数,用 \(c\) 数组记录每一个数不经过忽略操作最终的位置。

AC code:

#include <bits/stdc++.h>
using namespace std;

int n, m;
int a[200005], b[200005], c[200005], x[200005], y[200005];

int main(){
	cin >> n >> m;
	for (int i = 1; i <= n; i++) b[i] = i;
	for (int i = 1; i <= m; i++){
		cin >> a[i];
		x[i] = b[a[i]], y[i] = b[a[i] + 1];
		swap(b[a[i]], b[a[i] + 1]);
	}
	for (int i = 1; i <= n; i++) c[b[i]] = i;
	for (int i = 1; i <= m; i++){
		if (x[i] == 1) cout << c[y[i]] << endl;
		else if (y[i] == 1) cout << c[x[i]] << endl;
		else cout << c[1] << endl;
	}
	return 0;
}

AT_abc301 复盘

A

一眼水,只需要遍历一遍数组,记录哪一个胜利场数先打到 \((n + 1) / 2\) 就好了。

AC code:

#include <bits/stdc++.h>
using namespace std;

int n, c1, c2;
string s;

int main(){
    cin >> n >> s;
    for (int i = 0; i < n; i++){
        if (s[i] == 'T') c1++;
        else c2++;
        if (c1 >= (n + 1) / 2) return cout << "T", 0;
        if (c2 >= (n + 1) / 2) return cout << "A", 0;   
    }
    return 0;
}

B

依旧很水,只需要看当前与下一个数之间的趋势,然后输出就好。

注意边界,如果输出一个区间包含前面但不含后面的话,最后一个数要单独输出。

AC code:

#include <bits/stdc++.h>
using namespace std;

int n;
int a[105];

int main(){
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i < n; i++){
        if (a[i] < a[i + 1]){
            for (int j = a[i]; j < a[i + 1]; j++) cout << j << " ";
        }
        else {
            for (int j = a[i]; j > a[i + 1]; j--) cout << j << " ";
        }
    }
    cout << a[n] << endl;
    return 0;
}

C

依旧是字符串,先记录下每一个字母的出现情况和两个字符串中 @ 的数量。之后遍历一遍字母,如果缺失(两个字符串字母出现次数不同)的字母是 atcoder 中的一个,那么记录需要的 @ 的数量。

最后判断所需 @ 符是否超过字符串已有的,或者两个字符串剩的 @ 符不同。

AC code:

#include <bits/stdc++.h>
using namespace std;

string s, t;
int cnts, cntt;
int ms[30], mt[30];

int main(){
    cin >> s >> t;
    for (int i = 0; i < s.size(); i++){
        if (s[i] == '@') cnts++;
        if (t[i] == '@') cntt++;
        if (s[i] != '@') ms[s[i] - 'a']++;
        if (t[i] != '@') mt[t[i] - 'a']++;
    }
    for (char i = 'a'; i <= 'z'; i++){
        if (ms[i - 'a'] != mt[i - 'a']){
            if (i == 'a' || i == 't' || i == 'c' || i == 'o' || i == 'd' || i == 'e' || i == 'r'){
                if (ms[i - 'a'] < mt[i - 'a']) cnts -= (mt[i - 'a'] - ms[i - 'a']);
                else cntt -= (ms[i - 'a'] - mt[i - 'a']);
            }
            else return cout << "No", 0;
        }
    }
    if (cnts < 0 || cntt < 0 || cnts - cntt != 0) return cout << "No", 0;
    cout << "Yes";
    return 0;
}

D

算是比较水的 D,只要先记录初始的数,特判一下是否大于 \(n\)。然后从高位到低位去看每一个 ? 是否能变成 \(1\),如果能,就加上这个数二进制还原回来的数,否则跳过。

注意:不开 long long 见祖宗。

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

string s;
int n, sum, cnt = 1;
int a[65];

signed main(){
    cin >> s >> n;
    for (int i = s.size() - 1; i >= 0; i--) a[i] = cnt, cnt *= 2;
    for (int i = 0; i < s.size(); i++) sum += (s[i] == '1') ? a[i] : 0;
    if (sum > n) return cout << -1, 0;
    for (int i = 0; i < s.size(); i++){
        if (s[i] == '?' && sum + a[i] <= n) sum += a[i];
    }
    cout << sum << endl;
    return 0;
}

E

跟我们出的题很像,直接状压dp + 最短路就行,只不过这里不需要最短路,直接 BFS。

考虑状压dp,设置状态为:走到第 \(i\) 个猴子的位置且状态为 \(j\),如果 \(j\) 的第 \(k\) 位为 \(1\) 则走到了第 \(k\) 个点,值为当前状态所花时间的最小值。basecase:设起点为 0 号猴子,终点为 \(cnt+1\) 号猴子,则 basecase 为 \(dp_{0,1}=0\)。answer:当 \(dp_{cnt + 1,j} \le t\) 时,\(j\) 中包含 \(1\) 的个数的最大值。

坑点不多,注意在装完终点后,真正猴子的数量为 \(cnt-1\)

\(d\) 数组要开到 305,调了一个月,警钟敲烂

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int dx[4] = {0, 1, 0, -1};
int dy[4] = {1, 0, -1, 0};
int h, w, t, sx, sy, ex, ey, cnt, ans;
int d[305][305], dis[20][20], vis[305][305], dp[20][1500005];
pair<int, int> v[25];
string s[305];

void bfs(int x, int y){
    memset(d, 0x3f, sizeof(d));
    memset(vis, 0, sizeof(vis));
    queue<pair<int, int>> q;
    q.push({x, y});
    d[x][y] = 0, vis[x][y] = 1;
    while (!q.empty()){
        pair<int, int> u = q.front();q.pop();
        for (int i = 0; i < 4; i++){
            int nx = u.first + dx[i], ny = u.second + dy[i];
            if (nx >= 0 && nx < h && ny >= 0 && ny < w && s[nx][ny] != '#' && vis[nx][ny] == 0){
                vis[nx][ny] = 1;
                d[nx][ny] = d[u.first][u.second] + 1;
                q.push({nx, ny});
            }
        }
    }
}

signed main(){
    cin >> h >> w >> t;
    for (int i = 0; i < h; i++){
        cin >> s[i];
        for (int j = 0; j < w; j++){
            if (s[i][j] == 'S') sx = i, sy = j;
            else if (s[i][j] == 'G') ex = i, ey = j;
            else if (s[i][j] == 'o') v[++cnt] = {i, j}; 
        }
    }
    v[0] = {sx, sy}, v[++cnt] = {ex, ey};
    for (int i = 0; i <= cnt; i++){
        bfs(v[i].first, v[i].second);
        for (int j = 0; j <= cnt; j++) dis[i][j] = d[v[j].first][v[j].second];
    }
    if (dis[0][cnt] > t) return cout << "-1", 0;
    memset(dp, 0x3f, sizeof(dp));
    dp[0][1] = 0;
    for (int k = 0; k < (1 << (cnt + 1)); k++){
        for (int i = 0; i <= cnt; i++){
            if (dp[i][k] > t) continue;
            for (int j = 0; j <= cnt; j++) dp[j][k | (1 << j)] = min(dp[j][k | (1 << j)], dp[i][k] + dis[i][j]);
        }
    }
    for (int k = 0; k < (1 << (cnt + 1)); k++){
        if (dp[cnt][k] <= t) ans = max(ans, (int)__builtin_popcount(k)); 
    }
    cout << ans - 2 << endl;
    return 0;
}

AT_abc312 复盘

A

一眼过去直接 \(if\) 秒了

AC code:

#include <bits/stdc++.h>
using namespace std;

string s;

int main(){
    cin >> s;
    if (s == "ACE" || s == "BDF" || s == "CEG" || s == "DFA" || s == "EGB" || s == "FAC" || s == "GBD") cout << "Yes";
    else cout <<" No";
    return 0;
}

B

直接暴力枚举左上角的点,然后去判断整一个矩阵是否满足要求。

注意边界。

AC code:

#include <bits/stdc++.h>
using namespace std;

int n, m;
string s[105];

bool check(int x, int y){
    for (int i = x; i <= x + 2; i++){
        for (int j = y; j <= y + 2; j++){
            if (s[i][j] == '.') return 0;
        }
    }
    for (int i = x; i <= x + 3; i++){
        if (s[i][y + 3] == '#') return 0;
    }
    for (int i = y; i <= y + 3; i++){
        if (s[x + 3][i] == '#') return 0;
    }
    for (int i = x + 6; i <= x + 8; i++){
        for (int j = y + 6; j <= y + 8; j++){
            if (s[i][j] == '.') return 0;
        }
    }
    for (int i = x + 5; i <= x + 8; i++){
        if (s[i][y + 5] == '#') return 0;
    }
    for (int i = y + 5; i <= y + 8; i++){
        if (s[x + 5][i] == '#') return 0;
    }
    return 1;
}

int main(){
    cin >> n >> m;
    for (int i = 0; i < n; i++) cin >> s[i];
    for (int i = 0; i <= n - 9; i++){
        for (int j = 0; j <= m - 9; j++){
            if (check(i, j)) cout << i + 1 << " " << j + 1 << endl;
        }
    }
    return 0;
}

C

一眼二分答案,直接二分最低的价格就好了,\(check\) 里面就写满足卖家的数量和买家的数量是否满足条件。

注意:二分的有边界要设为 \(10^{9} + 1\),不然可能取不到极限 1e9。

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int n, m;
int a[200005], b[200005];

bool check(int mid){
    int cnt1 = 0, cnt2 = 0;
    for (int i = 1; i <= n; i++){
        if (a[i] <= mid) cnt1++;
    }
    for (int i = 1; i <= m; i++){
        if (b[i] >= mid) cnt2++;
    }
    return cnt1 >= cnt2;
}

signed main(){
    cin >> n >> m;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= m; i++) cin >> b[i];
    int l = 1, r = 1e9 + 1;
    while (l < r){
        int mid = l + r >> 1;
        if (check(mid)) r = mid;
        else l = mid + 1;
    }
    cout << l << endl;
    return 0;
}

D

根据题面很容易想到用递归,由小的可以推出来大的,显然可以推出一个递推或者说 \(dp\) 式子。
不难想到可以用 \(dp_{i,j}\) 表示第 \(i\) 个数前面有 \(j\) 个右括号,其中最后答案为 \(dp_{n, n/2}\)
转移方程就是分类讨论,?(),其中 ? 为左括号和右括号之和再模上 \(998244353\)。转移方程不难,只要想到当前情况是由这一个带右括号,或者不带右括号转移过来的就行了。

注意:在计算右括号部分的时候记得特判一下前面有没有右括号,不然会 RE。

AC code:

#include <bits/stdc++.h>
using namespace std;

const int mod = 998244353;
string s;
int dp[3005][3005];

int main(){
    cin >> s;
    s = ' ' + s;
    int len = s.size() - 1;
    if (len % 2 == 1) return cout << "0", 0;
    dp[0][0] = 1;
    for (int i = 1; i <= len; i++){
        for (int j = 0; j * 2 <= i; j++){
            if (s[i] == '?') dp[i][j] = ((j ? dp[i - 1][j - 1] : 0) + dp[i - 1][j]) % mod;
            else if (s[i] == '(') dp[i][j] = dp[i - 1][j];
            else dp[i][j] = j ? dp[i - 1][j - 1] : 0;
        }
    }
    cout << dp[len][len / 2];
    return 0;
}

F

二分答案。很容易想到直接三层枚举(实际为两层),仔细一想,其实第二层枚举拉环罐头的具有单调性,开罐器越多,拉环罐头开得越多,我们可以枚举普通罐头,然后拉环罐头和开罐器总和一定,之后二分拉环罐头的个数,求出最大值。在这之前很容易想到贪心一下,给权值从大到小排序。

注意:在二分的时候记得特判一下边界,防止 RE;不开 long long 见祖宗

AC code:

#include <bits/stdc++.h>
#define int long long
using namespace std;

int n, m, t, x, ans;
int sa[200005], sb[200005], sc[200005];
vector<int> a, b, c;

signed main(){
    cin >> n >> m;
    for (int i = 1; i <= n; i++){
        cin >> t >> x;
        if (t == 0) a.push_back(x);
        if (t == 1) b.push_back(x);
        if (t == 2) c.push_back(x); 
    }
    sort(a.begin(), a.end(), greater<int>());
    sort(b.begin(), b.end(), greater<int>());
    sort(c.begin(), c.end(), greater<int>());
    for (int i = 1; i < a.size(); i++) a[i] += a[i - 1];
    for (int i = 1; i < b.size(); i++) b[i] += b[i - 1];
    for (int i = 1; i < c.size(); i++) c[i] += c[i - 1];
    ans = a.size() ? a[min(m - 1, (int)a.size() - 1)] : 0;
    for (int i = 0; i < b.size(); i++){
        int l = 0, r = c.size() - 1;
        while (l < r){
            int mid = l + r >> 1;
            if (c[mid] >= i + 1) r = mid;
            else l = mid + 1;
        }
        if (r < 0 || c[r] < i + 1) continue;
        int res = m - (i + 1) - (r + 1);
        if (res >= 0) ans = max(ans, b[i] + (res ? a[min((int)a.size() - 1, res - 1)] : 0));
    }
    cout << ans << endl;
    return 0;
}
}
posted @ 2023-12-12 21:30  CCF_IOI  阅读(109)  评论(0)    收藏  举报