• [题解] CSP-J 2020题解


    CSP-J 2020题解

    本次考试还是很有用的,至少把我浇了一盆冷水。
    当使用民间数据自测的时候,我就自闭了。
    估分是320,但有些比较低级的错误直接少掉80。
    而且这套题应该上350才正常吧,也不是像平时训练一样难。
    主要是平时的时候太依赖于评测机了,小错误就都没注意,我认为在平时训练当中就应该一次成型,因为考试只有一次提交机会。
    基础概念也需要搞清楚,比如运算优先级等等。
    心态也要调整好。就这次考试而言,我的心态是极差的。多在考试之前鼓励下自己。可能就会好些吧!
    调整心态,继续努力,把自己当做起跑线上的人,路还很长,我们还会继续!!

    不多说了,上题解

    T1 优秀的拆分

    题目描述

    一般来说,一个正整数可以拆分成若干个正整数的和。

    例如,(1=1)(10=1+2+3+4) 等。对于正整数(n)的一种特定拆分,我们称它为“优秀的”,当且仅当在这种拆分下,(n)被分解为了若干个不同的(2)的正整数次幂。注意,一个数(x)能被表示成(2)的正整数次幂,当且仅当(x)能通过正整数个(2)相乘在一起得到。

    例如,(10=8+2=2^3+2^1)是一个优秀的拆分。但是,(7=4+2+1=2^2+2^1+2^0)就不是一个优秀的拆分,因为(1)不是(2)的正整数次幂。

    现在,给定正整数(n),你需要判断这个数的所有拆分中,是否存在优秀的拆分。若存在,请你给出具体的拆分方案。

    输入格式

    输入只有一行,一个整数(n),代表需要判断的数。

    输出格式

    如果这个数的所有拆分中,存在优秀的拆分。那么,你需要从大到小输出这个拆分中的每一个数,相邻两个数之间用一个空格隔开。可以证明,在规定了拆分数字的顺序后,该拆分方案是唯一的。

    若不存在优秀的拆分,输出 -1。

    输入输出样例

    输入 #1

    6
    

    输出 #1

    4 2
    

    输入 #2

    7
    

    输出 #2

    -1
    

    说明/提示
    样例 1 解释
    (6=4+2=2^2+2^1)是一个优秀的拆分。注意,(6=2+2+2)不是一个优秀的拆分,因为拆分成的(3)个数不满足每个数互不相同。

    数据规模与约定

    对于(20\%)的数据,(n le 10)
    对于另外(20\%)的数据,保证(n)为奇数。
    对于另外(20%)的数据,保证(n)(2)的正整数次幂。
    对于(80\%)的数据,(n le 1024)
    对于(100\%)的数据,(1 le n le 1 imes 10^7)

    思路

    简单,倒着枚举二的正整数次幂即可。

    C++代码

    #include <queue>
    #include <cstdio>
    using namespace std;
    queue<int> q;
    int n;
    int main() {
    	scanf("%d", &n);
    	for(int i = 30; i >= 1; i--) {
    		int now = (1 << i);
    		if(now <= n) {
    			n -= now;
    			q.push(now);
    		}
    	}
    	if(n != 0)
    		printf("-1");
    	else
    		while(!q.empty()) {
    			printf("%d ", q.front());
    			q.pop(); 
    		}
    	return 0;
    }
    

    T2 直播获奖

    题目描述

    NOI2130 即将举行。为了增加观赏性,CCF 决定逐一评出每个选手的成绩,并直播即时的获奖分数线。本次竞赛的获奖率为(w\%),即当前排名前(w\%)的选手的最低成绩就是即时的分数线。

    更具体地,若当前已评出了(p)个选手的成绩,则当前计划获奖人数为(max(1, lfloor p * w \% floor)),其中(w)是获奖百分比,(lfloor x floor)表示对(x)向下取整,(max(x,y))表示(x)(y)中较大的数。如有选手成绩相同,则所有成绩并列的选手都能获奖,因此实际获奖人数可能比计划中多。

    作为评测组的技术人员,请你帮 CCF 写一个直播程序。

    输入格式

    第一行有两个整数(n, w)。分别代表选手总数与获奖率。
    第二行有(n)个整数,依次代表逐一评出的选手成绩。

    输出格式

    只有一行,包含(n)个非负整数,依次代表选手成绩逐一评出后,即时的获奖分数线。相邻两个整数间用一个空格分隔。

    输入输出样例

    输入 #1

    10 60
    200 300 400 500 600 600 0 300 200 100
    

    输出 #1

    200 300 400 400 400 500 400 400 300 300
    

    输入 #2

    10 30
    100 100 600 100 100 100 100 100 100 100
    

    输出 #2

    100 100 600 600 600 600 100 100 100 100
    

    说明/提示
    样例 1 解释
    在这里插入图片描述

    数据规模与约定

    在这里插入图片描述

    对于所有测试点,每个选手的成绩均为不超过(600)的非负整数,获奖百分比(w)是一个正整数且(1 le w le 99)

    提示
    在计算计划获奖人数时,如用浮点类型的变量(如 C/C++ 中的 float 、 double,Pascal 中的 real 、 double 、 extended 等)存储获奖比例 w%w%,则计算(5 imes 60\%)时的结果可能为(3.000001),也可能为(2.999999),向下取整后的结果不确定。因此,建议仅使用整型变量,以计算出准确值。

    思路

    使用一个大根堆与一个小根堆来模拟。
    大根堆来储存分数线以下的分数。
    小根堆来储存分数线以上的分数。
    模拟分数线的波动即可。

    C++代码

    #include <queue>
    #include <cstdio>
    #include <vector>
    #include <algorithm>
    using namespace std;
    const int MAXN = 1e5 + 5;
    priority_queue<int, vector<int>, greater<int> > q1;
    priority_queue<int> q2;
    int a[MAXN];
    int n, w;
    int main() {
    	scanf("%d %d", &n, &w);
    	for(int i = 1; i <= n; i++) {
    		scanf("%d", &a[i]);
    	}
    	int id = 1;
    	q1.push(a[1]);
    	printf("%d ", a[1]);
    	for(int i = 2; i <= n; i++) {
    		int now = i * w / 100;
    		if(a[i] > q1.top()) {
    			q1.push(a[i]);
    			int k = q1.top();
    			q2.push(k);
    			q1.pop();
    		}
    		else {
    			q2.push(a[i]);
    		}
    		if(now > id) {
    			id = now;
    			int k = q2.top(); q2.pop();
    			q1.push(k);
    		}
    		printf("%d ", q1.top());
    	}
    	return 0;
    }
    

    T3 表达式

    题目描述

    小 C 热衷于学习数理逻辑。有一天,他发现了一种特别的逻辑表达式。在这种逻辑表达式中,所有操作数都是变量,且它们的取值只能为(0)(1),运算从左往右进行。如果表达式中有括号,则先计算括号内的子表达式的值。特别的,这种表达式有且仅有以下几种运算:

    1. 与运算:a & b。当且仅当(a)(b)的值都为(1)时,该表达式的值为(1)。其余情况该表达式的值为(0)
    2. 或运算:a | b。当且仅当(a)(b)的值都为(0)时,该表达式的值为(0)。其余情况该表达式的值为(1)
    3. 取反运算:!a。当且仅当(a)的值为(0)时,该表达式的值为(1)。其余情况该表达式的值为(0)

    小 C 想知道,给定一个逻辑表达式和其中每一个操作数的初始取值后,再取反某一个操作数的值时,原表达式的值为多少。

    为了化简对表达式的处理,我们有如下约定:

    表达式将采用后缀表达式的方式输入。

    后缀表达式的定义如下:

    1. 如果(E)是一个操作数,则(E)的后缀表达式是它本身。
    2. 如果(E)(E_1~ exttt{op}~E)形式的表达式,其中( exttt{op})是任何二元操作符,且优先级不高于(E_1)(E_2)中括号外的操作符,则(E)的后缀式为 (E_1' E_2' exttt{op}),其中(E_1')(E_2')分别为(E_1)(E_2)的后缀式。
    3. 如果(E)(E_1)形式的表达式,则(E_1)的后缀式就是(E)的后缀式。
      同时为了方便,输入中:

    与运算符(&)、或运算符(|)、取反运算符(!)的左右均有一个空格,但表达式末尾没有空格。
    操作数由小写字母(x)与一个正整数拼接而成,正整数表示这个变量的下标。例如:x10,表示下标为(10)的变量(x_{10})。数据保证每个变量在表达式中出现恰好一次。

    输入格式

    第一行包含一个字符串(s),表示上文描述的表达式。
    第二行包含一个正整数(n),表示表达式中变量的数量。表达式中变量的下标为(1,2, cdots , n)
    第三行包含(n)个整数,第(i)个整数表示变量(x_i)的初值。
    第四行包含一个正整数(q),表示询问的个数。
    接下来(q)行,每行一个正整数,表示需要取反的变量的下标。注意,每一个询问的修改都是临时的,即之前询问中的修改不会对后续的询问造成影响。
    数据保证输入的表达式合法。变量的初值为(0)(1)

    输出格式

    输出一共有(q)行,每行一个(0)(1),表示该询问下表达式的值。

    输入输出样例

    输入 #1

    x1 x2 & x3 |
    3
    1 0 1
    3
    1
    2
    3
    

    输出 #1

    1
    1
    0
    

    输入 #2

    x1 ! x2 x4 | x3 x5 ! & & ! &
    5
    0 1 0 1 1
    3
    1
    3
    5
    

    输出 #2

    0
    1
    1
    

    说明/提示
    样例 1 解释
    该后缀表达式的中缀表达式形式为((x_1 & x_2) | x_3)

    对于第一次询问,将(x_1)的值取反。此时,三个操作数对应的赋值依次为(0)(0)(1)。原表达式的值为((0&0)|1=1)
    对于第二次询问,将(x_2)的值取反。此时,三个操作数对应的赋值依次为(1)(1)(1)。原表达式的值为((1&1)|1=1)
    对于第三次询问,将(x_3)的值取反。此时,三个操作数对应的赋值依次为(1)(0)(0)。原表达式的值为((1&0)|0=0)
    样例 2 解释
    该表达式的中缀表达式形式为((!x_1)&(!((x_2|x_4)&(x_3&(!x_5)))))

    数据规模与约定

    对于(20\%)的数据,表达式中有且仅有与运算(&)或者或运算(|)。
    对于另外(30\%)的数据,(|s| le 1000)(q le 1000)(n le 1000)
    对于另外(20\%)的数据,变量的初值全为(0)或全为(1)
    对于(100\%)的数据,(1 le |s| le 1 imes 10^6)(1 le q le 1 imes 10^5)(2 le n le 1 imes 10^5)
    其中,(|s|)表示字符串(s)的长度。

    思路

    首先对于仅有或运算和且运算的情况,不难想到可以骗分,再利用后缀表达式跑一遍暴力就可以骗到50分,时间复杂度为(O(nq))
    这种做法可以优化吗?或运算和且运算都有两个数字进行运算,是不是很像一颗二叉树。而且这颗二叉树有一个特性:每个父节点都必有两个子节点。利用这一点,可以去看若改变这颗树上的某一个值,会不会对本次计算产生影响,进而推出是否会对整个结果产生影响,预处理一次时间复杂度为(O(n)),即便利这整棵树。总的来看加上后面的查询,时间复杂度为(O(n+q))

    C++代码

    #include <stack>
    #include <cstdio>
    #include <string>
    #include <iostream>
    using namespace std;
    void Quick_Read(int &Number) {
    	Number = 0;
    	char c = getchar();
    	int op = 1;
    	while (c < '0' || c > '9') {
    		if (c == '-')
    			op = -1;
    		c = getchar();
    	}
    	while (c >= '0' && c <= '9') {
    		Number = (Number << 1) + (Number << 3) + c - 48;
    		c = getchar();
    	}
    	Number *= op;
    }
    const int MAXN = 1e6 + 5;
    struct Node {
    	int Left_Child, Right_Child;
    	int Value, Index, Operator;
    };
    stack<int> s;
    string c;
    Node Tree[MAXN];
    bool Alter[MAXN];
    int Num[MAXN];
    int len, n, q;
    void calc(int x) {
    	if(Tree[x].Operator == -1) {
    		Alter[Tree[x].Index] = 1;
    		return;
    	}
    	if(!Tree[x].Operator)
    		calc(Tree[x].Left_Child);
    	else if(Tree[x].Operator == 1) {
    		int Left_Child = Tree[x].Left_Child;
    		int Right_Child = Tree[x].Right_Child;
    		if(Tree[Left_Child].Value + Tree[Right_Child].Value == 2) {
    			calc(Left_Child);
    			calc(Right_Child);
    		}
    		else if(Tree[Left_Child].Value)
    			calc(Right_Child);
    		else if(Tree[Right_Child].Value)
    			calc(Left_Child);
    	}
    	else {
    		int Left_Child = Tree[x].Left_Child;
    		int Right_Child = Tree[x].Right_Child;
    		if(Tree[Left_Child].Value + Tree[Right_Child].Value == 0) {
    			calc(Left_Child);
    			calc(Right_Child);
    		}
    		else if(!Tree[Left_Child].Value)
    			calc(Right_Child);
    		else if(!Tree[Right_Child].Value)
    			calc(Left_Child);
    	}
    }
    void Read() {
    	int A;
    	getline(cin, c);
    	Quick_Read(n);
    	for(int i = 1; i <= n; i++)
    		Quick_Read(Num[i]);
    	for(int i = 0; i < c.length(); i++) {
    		if(c[i] == 'x') {
    			int x = 0;
    			while(c[i + 1] <= '9' && c[i + 1] >= '0') {
    				x = x * 10 + c[i + 1] - 48;
    				i++;
    			}
    			len++;
    			Tree[len].Value = Num[x];
    			Tree[len].Index = x;
    			Tree[len].Operator = -1;
    			s.push(len);
    			continue;
    		}
    		if(c[i] == '!') {
    			len++;
    			Tree[len].Value = !Tree[s.top()].Value;
    			Tree[len].Left_Child = s.top(); s.pop();
    			Tree[len].Operator = 0;
    			s.push(len);
    			continue;
    		}
    		if(c[i] == '&') {
    			len++;
    			int Number1 = s.top(); s.pop();
    			int Number2 = s.top(); s.pop();
    			Tree[len].Value = Tree[Number1].Value & Tree[Number2].Value;
    			Tree[len].Operator = 1;
    			Tree[len].Left_Child = Number1;
    			Tree[len].Right_Child = Number2;
    			s.push(len);
    			continue;
    		}
    		if(c[i] == '|') {
    			len++;
    			int Number1 = s.top(); s.pop();
    			int Number2 = s.top(); s.pop();
    			Tree[len].Value = Tree[Number1].Value | Tree[Number2].Value;
    			Tree[len].Operator = 2;
    			Tree[len].Left_Child = Number1;
    			Tree[len].Right_Child = Number2;
    			s.push(len);
    			continue;
    		}
    	}
    }
    void Write() {
    	int A;
    	Quick_Read(q);
    	for(int i = 1; i <= q; i++) {
    		Quick_Read(A);
    		if(Alter[A])
    			printf("%d
    ", !Tree[len].Value);
    		else
    			printf("%d
    ", Tree[len].Value);
    	}
    }
    int main() {
    	Read(); 
    	calc(len);
    	Write();
    	return 0;
    }
    

    T4 方格取数

    题目描述

    设有(n imes m)的方格图,每个方格中都有一个整数。现有一只小熊,想从图的左上角走到右下角,每一步只能向上、向下或向右走一格,并且不能重复经过已经走过的方格,也不能走出边界。小熊会取走所有经过的方格中的整数,求它能取到的整数之和的最大值。

    输入格式

    第一行有两个整数(n, m)

    接下来(n)行每行(m)个整数,依次代表每个方格中的整数。

    输出格式

    一个整数,表示小熊能取到的整数之和的最大值。

    输入输出样例

    输入 #1

    3 4
    1 -1 3 2
    2 -1 4 -1
    -2 2 -3 -1
    

    输出 #1

    9
    

    输入 #2

    2 5
    -1 -1 -3 -2 -7
    -2 -1 -4 -1 -2
    

    输出 #2

    -10
    

    说明/提示
    在这里插入图片描述

    按上述走法,取到的数之和为(1 + 2 + (-1) + 4 + 3 + 2 + (-1) + (-1) = 9),可以证明为最大值。
    在这里插入图片描述

    注意,上述走法是错误的,因为第(2)行第(2)列的方格走过了两次,而根据题意,不能重复经过已经走过的方格。
    在这里插入图片描述

    另外,上述走法也是错误的,因为没有走到右下角的终点。
    数据规模与约定
    对于(20\%)的数据,(n, m le 5)
    对于(40\%)的数据,(n, m le 50)
    对于(70\%)的数据,(n, m le 300)
    对于(100\%)的数据,(1 le n,m le 10^3)。方格中整数的绝对值不超过(10^4)

    思路

    看到这道题的时候想到的是用最短路。
    但是仔细想想,若用Dijkstra有正边有负边,不可以用。若用SPFA,回有环。若强行用分层图来维护的话,时间复杂度就为(O(n^2mlog(n^2m))),肯定会超时。
    于是在考场上就想了一个70分的dp带前缀和优化,时间复杂度为(O(n^2m))
    代码如下:

    #include <cstdio>
    #include <algorithm>
    using namespace std;
    const int MAXN = 1e3 + 5;
    int dp[MAXN][MAXN];
    int dist[MAXN];
    int a[MAXN][MAXN];
    int n, m;
    int main() {
    	scanf("%d %d", &n, &m);
    	for(int i = 1; i <= n; i++)
    		for(int j = 1; j <= m; j++)
    			scanf("%d", &a[i][j]);
    	for(int i = 1; i <= n; i++) {
    		for(int j = 1; j <= i; j++)
    			dp[1][i] += a[j][1];
    	}
    	for(int i = 2; i <= m; i++) {
    		dist[1] = a[1][i];
    		for(int j = 2; j <= n; j++)
    			dist[j] = dist[j - 1] + a[j][i];
    		for(int j = 1; j <= n; j++)
    			dp[i][j] = dp[i - 1][j] + a[j][i];
    		for(int j = 1; j <= n; j++) {
    			for(int k = 1; k <= n; k++) {
    				dp[i][j] = max(dp[i][j], dp[i - 1][k] + dist[max(j, k)] - dist[min(j, k) - 1]);
    			}
    		}
    	}
    	printf("%d", dp[m][n]);
    	return 0;
    }
    

    (顺便说一句,考试时宏定义Max和Min没加括号,直接导致这题爆零,正确的宏定义应为:#define Min(a, b) ((a) < (b) ? (a) : (b))。)
    如何来优化呢?
    定义两个dp,一个记录从上往下走的答案,一个记录从下往上走的答案。
    状态转移方程:

    1. 只可以向右走,而不能向左走,就是上一层的路径转换到本层即可
      (f[i][j][1]=f[i][j][0]=max(f[i-1][j][1],f[i-1][j][0])+a[i][j])
    2. 向下走
      (f[i][j][1]=max(f[i][j][1],f[i][j−1][1]+a[i][j]))
    3. 向下走
      (f[i][j][0]=max(f[i][j][0],f[i][j+1][0]+a[i][j]))

    C++代码

    #include <cstdio>
    #define LL long long
    #define INF 1e17
    #define Max(a, b) ((a) > (b) ? (a) : (b))
    void Quick_Read(LL &Number) {
    	Number = 0;
    	char c = getchar();
    	LL op = 1;
    	while (c < '0' || c > '9') {
    		if (c == '-')
    			op = -1;
    		c = getchar();
    	}
    	while (c >= '0' && c <= '9') {
    		Number = (Number << 1) + (Number << 3) + c - 48;
    		c = getchar();
    	}
    	Number *= op;
    }
    const LL MAXN = 1e3 + 5;
    LL Map[MAXN][MAXN], dp[MAXN][MAXN][2];
    LL n, m;
    int main() {
    	Quick_Read(n);
    	Quick_Read(m);
    	for(int i = 1; i <= n; i++) {
    		for(int j = 1; j <= m; j++) {
    			Quick_Read(Map[i][j]);
    			dp[i][j][0] = dp[i][j][1] = -INF;
    		}
    	}
    	dp[1][1][0] = dp[1][1][1] = Map[1][1];
    	for(int i = 2; i <= n; i++)
    		dp[i][1][1] = dp[i - 1][1][1] + Map[i][1];
    	for(int j = 2; j <= m; j++) {
    		dp[1][j][1] = dp[1][j][0] = Max(dp[1][j - 1][0], dp[1][j - 1][1]) + Map[1][j];
    		for(int i = 2; i <= n; i++) {
    			dp[i][j][0] = Max(dp[i][j - 1][0], dp[i][j - 1][1]) + Map[i][j];
    			dp[i][j][1] = Max(dp[i][j][0], dp[i - 1][j][1] + Map[i][j]);
    		}
    		for(int i = n - 1; i > 0; i--)
    			dp[i][j][0] = Max(dp[i][j][0], dp[i + 1][j][0] + Map[i][j]);
    	}
    	printf("%lld", Max(dp[n][m][0], dp[n][m][1]));
    	return 0;
    }
    
  • 相关阅读:
    Flink 电商实时数仓(二十三):ClickHouse基础(二)使用基础(2)ClickHouse 的安装(centos)
    Flink 电商实时数仓(二十二):ClickHouse基础(一)使用基础(1)ClickHouse 入门
    Flink 源码(二十六):Flink 内存管理(二)内存数据结构 、管理器
    Flink 源码(二十五):Flink 内存管理(一)内存模型与内存数据结构
    Flink 源码(二十四):Flink 任务调度机制(五)调度
    460. LFU Cache (solution 1)
    785. Is Graph Bipartite? (是否二分图)
    1318. Minimum Flips to Make a OR b Equal to c
    211. Add and Search Word
    188. Best Time to Buy and Sell Stock IV
  • 原文地址:https://www.cnblogs.com/C202202chenkelin/p/13971312.html
Copyright © 2020-2023  润新知