2023第五届CCPC河南省赛题解

补题链接

题目链接

目录

A. 小水獭游河南(签到)

B. Art for Rest

C. Toxel 与随机数生成器(概率、常识)

E. 矩阵游戏(DP,需要压缩维度)

F. Art for Last(维护区间最值、单调队列、ST表)

G. Toxel 与字符画(大模拟)

H. Travel Begins(构造)

K. 排列与质数


A. 小水獭游河南(签到)

题目说的比较清晰了,s 必须由两个非空字符串 a 和 b 组成,所以 s 长度最小是 2。

首先从前往后遍历,找到第一个出现重复字符的位置(wei),那么从这个位置往前(wei - 1)到首个字符,其中的子串都是满足条件的 a 串(没有重复字符)。

如果没找到这个位置,说明 s 串没有重复字符,即合法(前 len - 1 个字符作为 a 串,最后一个字符作为 b 串)。

接下来就枚举 b 字符串的起始位置——从第 2 个字符开始(至少要留一个作为 a 串)到第wei位都有可能是起始位置,而结束位置固定为最后一个字符。

每次判断 b 串是不是回文即可,是的话就证明合法, 如果所有的 b 串都不是回文,就不合法。

#include<bits/stdc++.h>
using namespace std;
int t;
string s;
bool b[27];

int main() {
    cin >> t;
    while(t--) {
        cin >> s;
        int len = s.length();
        memset(b, false, sizeof(b));
        if(len == 1)
            cout << "NaN\n";    
        else {
            int wei = -1;
            for(int i = 0; i < len; i++) {
                if(b[s[i] - 'a' + 1]) {
                    wei = i;
                    break;
                }
                b[s[i] - 'a' + 1] = 1;
            }
            if(wei == -1) {
                cout << "HE\n"; continue;
            }
            bool flag = 0;
            for(int i = 1; i <= wei; i++) {
                if(flag)
                    break;
                int head = i, tail = len - 1;
                while(s[head] == s[tail] && head <= tail) {
                    head++, tail--;
                }
                if(head > tail)
                    flag = 1;
            }
            if(flag)
                cout << "HE\n";
            else
                cout << "NaN\n";
        }
    }
    return 0;
}

B. Art for Rest

题目意思就是每 k 个数字为一组(最后一组可能不足 k 个),第 i 组的最大值  第 i + 1 组的最小值。

因为 1 ≤ k ≤ n,所以我们先倒序求出第 j (1 ≤ j ≤ n)位至第 n 位的最小值。

然后判断对于第 i 位(1 ≤ i ≤ n),前 i 个数字中的最大值是否小于等于第 i + 1 位至第 n 位的最小值,如果是就把第 i 位打上标记。

对于每个 k,如果每段最后一个位置被打了标记,就说明到目前为止这一段合法,如果所有段都合法,就证明这个 k 是一个可行解, ans++。

#include<bits/stdc++.h>
using namespace std;
int n, a[1000001];
int minn[1000005], ok[1000001];
int ans;
int flag;

int main() {
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) scanf("%d", a + i);
    minn[n + 1] = 1e9 + 2;

    // 求第 i 位的后缀数组的最小值
    for(int i = n; i >= 1; i--)
        minn[i] = min(minn[i + 1], a[i]);
    
    int maxx = a[1];
    for(int i = 1; i <= n; i++) {
        maxx = max(maxx, a[i]);
        // 如果前 i 位最大值 ≤ i + 1 的后缀数组的最小值,那么前 i 位就是合法的
        if(maxx <= minn[i + 1])
            ok[i] = 1;
    }

    for(int k = 1; k <= n; k++) {
        flag = 1;
        for(int j = k; j <= n; j += k) {
            if(!ok[j]) {
                flag = 0;
                break;
            }
        }
        // 如果全都合法就加上1,否则加的是0
        ans += flag;
    }
    cout << ans;
    return 0;
}

C. Toxel 与随机数生成器(概率、常识)

感觉是个诈骗题(没错我被骗到了)

题目说“按照错误的代码,每lengthi个位置,序列就会重复”,那么就是前lengthi个字符组成的串一定会重复,还有1e3 ≤ lengthi ≤ 1e4。

按照正确的随机数生成代码来生成01串的话,出现重复串的概率很小。假设重复串的长度为L,那么概率就是(0.5 ^ L),如果 L 是100位的话那么概率很小我们就认为不可能出现(更别说1e3——1e4的长度了),如果出现了的话就认为是随机数生成代码有问题。

所以截取前100位字符,找找后面有没有一样的,没有就是代码正确,否则就是代码错误。

#include<bits/stdc++.h>
using namespace std;
string s, s2;

int main() {
	cin >> s;
	s2 = s.substr(0, 100);
	if(s.find(s2, 100) == string :: npos)
		cout << "YES";
	else
		cout << "NO";
	return 0;
}

E. 矩阵游戏(DP,需要压缩维度)

上面是官方题解。

主要的问题是空间复杂度太大了会超内存限制,所以需要从三维压到二维。

先放一个三维的:

#include<bits/stdc++.h>
using namespace std;
int t, n, m, x;
char ch[502][502];
int f[501][501][1001];
int main() {
    scanf("%d", &t);
    while(t--) {
        // 怕 memset 超时,就没用
        // memset(f, 0, sizeof(f));
        scanf("%d%d%d", &n, &m, &x);
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= m; j++)
                for(int k = 1; k <= x; k++)
                    f[i][j][k] = 0;

        for(int i = 1; i <= n; i++)
            scanf("%s", ch[i] + 1);

        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= m; j++) {
                for(int k = 0; k <= x; k++) {
                    if(ch[i][j] == '0')
                        f[i][j][k] = max(f[i - 1][j][k], f[i][j - 1][k]);
                    if(ch[i][j] == '1')
                        f[i][j][k] = max(f[i - 1][j][k], f[i][j - 1][k]) + 1;
                    if(ch[i][j] == '?') {
                        int a = f[i - 1][j][k];
                        int b = f[i][j - 1][k];
                        int c = f[i - 1][j][k - 1] + 1;
                        int d = f[i][j - 1][k - 1] + 1;

                        if(k >= 1)
                            f[i][j][k] = max(a, max(b, max(c, d)));
                        else
                            f[i][j][k] = max(a, b);
                    }
                }
            }
        }
        cout << f[n][m][x] << endl;
    }
    return 0;
}

然后就是考虑压缩:

其实我们状态转移就只用到了前一列的数据,所以两个数组就行,一个用来记当前数据,一个用来记上一列的数据,每次循环来回滚动(滚动数组)。

我这里是直接用 f [ 2 ] [ m ] [ x ] ,相当于两个 f [ m ] [ x ]。

如果看不懂 f 数组第一维中的 & 1,可以网上搜一搜关于如何滚动数组,这是其中一个方法,这里就不展开讲了。

#include<bits/stdc++.h>
using namespace std;
int t, n, m, x;
char ch[502][502];
int f[2][501][1001];
int main() {
    scanf("%d", &t);
    while(t--) {        
        scanf("%d%d%d", &n, &m, &x);
        for(int i = 0; i < 2; i++)
            for(int j = 1; j <= m; j++)
                for(int k = 0; k <= x; k++)
                    f[i][j][k] = 0;

        for(int i = 1; i <= n; i++)
            scanf("%s", ch[i] + 1);

        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= m; j++) {
                for(int k = 0; k <= x; k++) {
                    if(ch[i][j] == '0')
                        f[i & 1][j][k] = max(f[(i - 1) & 1][j][k], f[i & 1][j - 1][k]);
                    if(ch[i][j] == '1')
                        f[i & 1][j][k] = max(f[(i - 1) & 1][j][k], f[i & 1][j - 1][k]) + 1;
                    if(ch[i][j] == '?') {
                        // 方便下面的 max() 套 max(),先记下来四个数
                        int a = f[(i - 1) & 1][j][k];
                        int b = f[i & 1][j - 1][k];
                        int c = f[(i - 1) & 1][j][k - 1] + 1;
                        int d = f[i & 1][j - 1][k - 1] + 1;

                        if(k >= 1)
                            f[i & 1][j][k] = max(a, max(b, max(c, d)));
                        else
                            f[i & 1][j][k] = max(a, b);
                    }
                }
            }
        }
        cout << f[n & 1][m][x] << endl;
    }
    return 0;
}

F. Art for Last(维护区间最值、单调队列、ST表)

题意就是说选 k 个数,

求这 k 个数中 min ( 任意两个数绝对值差的最大值 * 任意两个数绝对值差的最小值 )

先把 n 个数排序,对于每个长度为 k 的子区间,首尾两个数绝对值差最大,然后需要找这个区间中相邻两数绝对值差的最小值。(因为已经排过序了,所以这个区间内任意两数绝对值差的最小值肯定是某一对儿相邻两个数的绝对值差的最小值)

肯定不能暴力,会超时,所以问题演化成如何维护某段区间的最值问题,可以用单调队列。

这里用的是 multiset——和 set 的区别是可以存重复的元素。

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

int n, k, a[500001];
long long ans = 1e18;
multiset<int> q;

int main() {
 
    cin >> n >> k;
    for(int i = 1; i <= n; i++) scanf("%d", a + i);
    sort(a + 1, a + 1 + n);

    for(int i = 2; i <= k - 1; i++)
        q.insert(a[i] - a[i - 1]);

    for(int i = k; i <= n; i++) {
        q.insert(a[i] - a[i - 1]);

        ans = min(ans, (long long)*q.begin() * (a[i] - a[i - k + 1]));

         // i - k + 2 和 i - k + 1 就是第 k 段的第二个元素和第一个元素
         // 因为每次 i++,长度为 k 的窗口会向右滑动一位,
         // 所以要把新加入窗口的数与其前一个数的差值加到集合中
         // 把刚离开窗口的数与其后一个数的差值删掉

        q.erase(q.lower_bound(a[i - k + 2] - a[i - k + 1]));
    }
    cout << ans;
    return 0;
}

G. Toxel 与字符画(大模拟)

1、求 x ^ y 的 pow 函数需要自己写,不然数太大精度会有问题。

2、判断 x ^ y ≤ 1e18 需要用到一些数学知识,硬算然后直接判断会溢出,很麻烦。

判断:

y * log(x) <= log(1e18)

这样可以转化为浮点数比较,避免了整型的数据溢出问题。

这里是一个数学上的推导,使用了“对数运算”的性质:

我们可以将 x 的 y 次幂写成如下形式:

x^y = e ^ (y * log(e, x))

其中,e 是自然对数的底数,log(e, x) 表示以 e 为底数,x 的对数。因此,我们希望判断 x^y 是否小于等于 1e18,等价于求解如下方程:

e ^ (y * log(e, x)) <= e ^ 18

即可得到:

y * log(e, x) <= 18

由于 e 是一个常数,因此可以用 log 函数来表示。同时,C++ 中的 log 函数默认以 e 为底,因此可以写成:

y * log(x) <= log(e, 1e18)

由于 log 函数中底数为 e 的情况比较特殊,实际上 e 的值可以直接近似为 2.71828,而 1e18 可以简化为 10 ^ 18,于是有:

y * log(x) <= log(10^18, 2.71828)

log(10^18, 2.71828) 可以通过计算得到约等于 41.446。将其代入原式,即可得到:

y * log(x) <= 41.446

同样地,为了避免数据溢出,我们需要将上述式子变形为:y * log10(x) <= 18,再进行实际的代码实现。

#include<bits/stdc++.h>
using namespace std;
int t;
long long x, y;
vector<int> a, b, c;
char big[10][10][9] = {
   {"........",
    "........",
    ".0000000",
    ".0.....0",
    ".0.....0",
    ".0.....0",
    ".0.....0",
    ".0.....0",
    ".0000000",
    "........"}, 
   
   {"........",
    "........",
    ".......1",
    ".......1",
    ".......1",
    ".......1",
    ".......1",
    ".......1",
    ".......1",
    "........"}, 
   
   {"........",
    "........",
    ".2222222",
    ".......2",
    ".......2",
    ".2222222",
    ".2......",
    ".2......",
    ".2222222",
    "........"}, 
   
   {"........",
    "........",
    ".3333333",
    ".......3",
    ".......3",
    ".3333333",
    ".......3",
    ".......3",
    ".3333333",
    "........"},

   {
	"........",
    "........",
    ".4.....4",
    ".4.....4",
    ".4.....4",
    ".4444444",
    ".......4",
    ".......4",
    ".......4",
    "........"}, 
   
   {"........",
    "........",
    ".5555555",
    ".5......",
    ".5......",
    ".5555555",
    ".......5",
    ".......5",
    ".5555555",
    "........"}, 
   
   {"........",
    "........",
    ".6666666",
    ".6......",
    ".6......",
    ".6666666",
    ".6.....6",
    ".6.....6",
    ".6666666",
    "........"}, 
   
   {"........",
    "........",
    ".7777777",
    ".......7",
    ".......7",
    ".......7",
    ".......7",
    ".......7",
    ".......7",
    "........"}, 
	
   {"........",
    "........",
    ".8888888",
    ".8.....8",
    ".8.....8",
    ".8888888",
    ".8.....8",
    ".8.....8",
    ".8888888",
    "........"},
	
   {"........",
    "........",
    ".9999999",
    ".9.....9",
    ".9.....9",
    ".9999999",
    ".......9",
    ".......9",
    ".9999999",
    "........"}};
char sml[10][10][7] = {   
	{"......",
	 ".00000",
	 ".0...0",
	 ".0...0",
	 ".0...0",
	 ".00000",
	 "......",
	 "......",
	 "......",
	 "......"}, 

	
	{"......",
	 ".....1",
	 ".....1",
	 ".....1",
	 ".....1",
	 ".....1",
	 "......",
	 "......",
	 "......",
	 "......"}, 

	
	{"......",
	 ".22222",
	 ".....2",
	 ".22222",
	 ".2....",
	 ".22222",
	 "......",
	 "......",
	 "......",
	 "......"}, 

	 {"......",
	 ".33333",
	 ".....3",
	 ".33333",
	 ".....3",
	 ".33333",
	 "......",
	 "......",
	 "......",
	 "......"}, 

	{"......",
	 ".4...4",
	 ".4...4",
	 ".44444",
	 ".....4",
	 ".....4",
	 "......",
	 "......",
	 "......",
	 "......"}, 
	 
	{"......",
	 ".55555",
	 ".5....",
	 ".55555",
	 ".....5",
	 ".55555",
	 "......",
	 "......",
	 "......",
	 "......"},
	
	{"......",
	 ".66666",
	 ".6....",
	 ".66666",
	 ".6...6",
	 ".66666",
	 "......",
	 "......",
	 "......",
	 "......"},
	
	{"......",
	 ".77777",
	 ".....7",
	 ".....7",
	 ".....7",
	 ".....7",
	 "......",
	 "......",
	 "......",
	 "......"},

	{"......",
	 ".88888",
	 ".8...8",
	 ".88888",
	 ".8...8",
	 ".88888",
	 "......",
	 "......",
	 "......",
	 "......"},

	{"......",
	 ".99999",
	 ".9...9",
	 ".99999",
	 ".....9",
	 ".99999",
	 "......",
	 "......",
	 "......",
	 "......"}
	};
char inf[10][25] = {
    "........................",
	"........................",
	".IIIIIII.N.....N.FFFFFFF",
	"....I....NN....N.F......",
	"....I....N.N...N.F......",
	"....I....N..N..N.FFFFFFF",
	"....I....N...N.N.F......",
	"....I....N....NN.F......",
	".IIIIIII.N.....N.F......",
	"........................"};
char deng[10][9] = {
	 "........",
	 "........",
	 "........",
	 "........",
	 ".=======",
	 "........",
	 ".=======",
	 "........",
	 "........",
	 "........"};
string ANS[450];

// bool judge(long long x, long long y) {
// 	if(x == 1)	return true;
	
// 	for(int i = 1; i <= y; i++) {
// 		res *= x;
// 		if(res > 1e18)
// 			return false;
// 	}
// 	return true
// }

long long poww(long long x, long long y) {
	long long ans = 1;
	if(x == 1)
		return 1;
	
	for(int i = 1; i <= y; i++)
		ans *= x;
	return ans;
}

int main() {
	scanf("%d", &t);
	while(t--) {
		a.clear();	b.clear();	c.clear();
		for(int i = 0; i < 450; i++)
			ANS[i] = "";

		scanf("%lld^{%lld}", &x, &y);
		long long tx = x, ty = y;
		while(tx) {
			a.push_back(tx % 10);
			tx /= 10;
		}
		while(ty) {
			b.push_back(ty % 10);
			ty /= 10;
		}
		reverse(a.begin(), a.end());
		reverse(b.begin(), b.end());

		// 先把等号及以前的部分加上
		for(int i = 0; i < 10; i++) {
			for(auto k : a)
				ANS[i] += big[k][i];
			for(auto k : b)
				ANS[i] += sml[k][i];
			ANS[i] += deng[i];
		}

		// log(pow(x, y)) < log(1e18)
		// y * log10(x) <= 18.0
		if(y * log10(x) <= 18.0) {
			long long ans = poww(x, y);
			while(ans) {
				c.push_back(ans % 10);
				ans /= 10;
			}
			reverse(c.begin(), c.end());
			// 加上 x ^ y
			for(int i = 0; i < 10; i++) {
				for(auto k : c)
					ANS[i] += big[k][i];
			}
		}
		else {
			// 加上 INF
			for(int i = 0; i < 10; i++)
				ANS[i] += inf[i];
		}
		// 每行末尾再补上一个 '.'
		for(int i = 0; i < 10; i++)
			ANS[i] += '.';

		for(int i = 0; i < 10; i++)
			cout << ANS[i] << endl;
	}
	return 0;
}

H. Travel Begins(构造)

题意就是你需要构造 k 个实数,使它们的和为 n。

在此基础上,对于每个数都可以用一个四舍五入规则。

记 T 为一个充分小的正实数,例如 T = 1 >> 30。

1、构造这 k 个数,使得和为 n ,且所有数四舍五入后求和,和最小为多少?

        使 { ai } 中包含尽可能多的 0.5 - T。

2、构造这 k 个数,使得和为 n ,且所有数四舍五入后求和,和最大为多少?

        使 { ai } 中包含尽可能多的 0.5。

然后求剩下数的四舍五入值。

#include<bits/stdc++.h>
using namespace std;
int t, n, k;
double T = 1 >> 30;

int judge(double x) {
    return (x - int(x)) >= 0.5 ? 1 : 0;
}

int main() {
    cin >> t;
    while(t--) {
        cin >> n >> k;
        long long maxx, minn, num;

        // num 记录最多能构造多少个 0.5 或 0.5 - T
        // 和为 n 的数,最多可以分出 n * 2 个 0.5
        // 但是最多 k 个数,所以具体分出几个会有限制

        if(2 * n > k)
            num = k - 1; // 前 k - 1 个数是 0.5, 最后一个数是 n - (k - 1) * 0.5
        else
            num = 2 * n; // 前 2 * n 个数是 0.5, 后 k - 2 * n 个数为 0


        //  num: num 个 0.5 四舍五入后和就是 num
        //  int(n - 0.5 * num): 剩下数的整数部分
        //  judge(n - 0.5 * num): 剩下数的小数部分
        maxx = num + int(n - 0.5 * num) + judge(n - 0.5 * num);
    

        // 接下来 num 的含义为最多有 num 个 0.5 - T
        // 等于的情况和前面不一样,单独判断,如果不等的话用前面的 num 就行
        if(n * 2 == k)
            num = 2 * n - 1;
        
        // 前 num 个数都小于 0.5, 四舍五入后为 0 ,只算剩余部分四舍五入的值
        minn = int(n - (0.5 - T) * num) + judge(n - (0.5 - T) * num);

        cout << minn << " " << maxx << endl;
    }
    return 0;
}

K. 排列与质数

没什么规律。

所有相邻的奇数和所有相邻的偶数之间差值都为2,合法。

重点是把奇数和偶数合并到一起,中间连接处、首尾元素的差值不合法,需要调整位置。

例如: 1 3 5 7 9 10 8 6 4 2

我们发现把奇数升序和偶数降序排列在一起,要调整位置的数就是末尾的 2 和 奇偶链接处的 n - 1,只有这两个数需要调位置。

我们的方案是把 2 插到 5 和 7 之间,把 n - 1 插到 n - 6 和 n - 8 之间。

由于 n = 13 时 ,n - 6 和 n - 8 对应就是 7 和 5 ,和 2 的插入位置重复了,所以需要单独判断。

其余打表的情况就是没有 5 和 7 或者没有 n - 6 和 n - 8 或者没有 n - 6 和 n - 8 之间的位置。

当 n 是偶数的时候 n - 6 和 n - 8 在偶数序列部分;

当 n 是奇数的时候 n - 6 和 n - 8 在奇数序列部分;

所以需要分开判断、插入。

自己在草稿纸上划拉几下就会发现了。

#include<bits/stdc++.h>
using namespace std;
int n;
vector<int> a, b;
int main() {
    scanf("%d", &n);
    if(n <= 4)
        cout << "-1";
    else if(n == 5)
        cout << "1 3 5 2 4";
    else if(n == 6)
        cout << "1 6 3 5 2 4";
    else if(n == 7)
        cout << "1 3 5 7 2 4 6";
    else if(n == 8)
        cout << "1 3 5 7 2 4 6 8";
    else if(n == 10)
        cout << "1 3 5 10 7 9 2 4 6 8";
    else if(n == 13)
        cout << "1 3 5 2 7 12 9 11 13 10 8 6 4";
    else {
        if(n % 2 == 0) {
            for(int i = 1; i <= n - 3; i += 2) {
                a.push_back(i);
                if(i == 5)
                    a.push_back(2);
            }
            for(int i = n; i >= 4; i -= 2) {
                b.push_back(i);
                if(i == n - 6)
                    b.push_back(n - 1);
            }
        }
        else {
            for(int i = 1; i <= n; i += 2) {
                a.push_back(i);
                if(i == 5)
                    a.push_back(2);
                if(i == n - 8)
                    a.push_back(n - 1);
            }
            for(int i = n - 3; i >= 4; i -= 2)
                b.push_back(i);
        }
        for(auto i : a)
            cout << i << " ";
        for(auto i : b)
            cout << i << " ";
    }
    return 0;
}
  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值