(KDY)CSP-J模拟赛五补题报告

(KDY)CSP-J模拟赛五补题报告

日期:2023年10月4日


一、AC情况

第一题第二题第三题第四题
AC 100分AC 100分AC 100分WA 0分(赛后AC)

总计300分。

二、赛中概况

第一题很快写完;

第二题相邻一下找到了规律,也很简单,写完;

第三题思考用了大部分时间,算了很长时间,调试了很久,写完;

最后一天没有时间考虑了,于是骗分。

三、解题报告

问题一:重复判断(repeat)

情况:

AC

题意:

判断字符串a是否是字符串b重复若干次得到的。

赛时思路&题解:

两个指针同时遍历,指针 i i i遍历字符串a,同时指针 l l l遍历字符串b,如果字符串b遍历了一遍,就调到开头继续比较,知道把a遍历一遍为止。如果 a i ! = b l a_i!=b_l ai!=bl,则不是重复字符串;如果遍历结束 l l l没有,即a中出了b仍有多余字符,也不是重复字符串。

AC代码&AC代码:

#include <bits/stdc++.h>
using namespace std;
int main() {
    freopen("repeat.in", "r", stdin);
    freopen("repeat.out", "w", stdout);
    int T;
	cin >> T;
	while (T--) {
		string s, t;
		cin >> s >> t;
		int flag = 1, l = 0;
		for (int i = 0; i < s.size(); i++) {  //指针i,遍历字符串a
			if (s[i] != t[l]) {   //比较出不同,不符合题意
				flag = 0;
				break;
			}
			l++;   //同时指针l遍历字符串b
			l %= t.size();  //l保证在[l,size_b)中重复遍历
		}
		if (flag && l == 0) {    //没有多余元素
			printf("YES\n");
		} else {
			printf("NO\n");
		}
	} 
    fclose(stdin);
    fclose(stdout);
	return 0;
}

问题二:歪果仁学乘法(multiplication)

情况:

AC

题意:

现有一种不需要九九乘法表也可以计算乘法的方式,对于a × b:

  1. 将a,b的每一位上的数码画成线,不同位之间分隔开。
  2. a 和 b 的方向垂直画出。
  3. 数出每个方向上交点的个数,即是 c 对应位置上的数码。

样例图给出了计算12 ×13的方法:

  1. 红色线分别画出 1 条和 2 条;
  2. 蓝色线分别画出 1 条和 3 条;
  3. 数出红、蓝色线的交点个数,依次为 1,5,6 个;
  4. 得到答案:12 × 13 = 156。

给出两个数字 a, b,求它们的乘积时交点的总个数是多少。

在这里插入图片描述

赛时思路&题解:

观察一下,我们发现,设 a = x y ‾ a=\overline{xy} a=xy b = p q ‾ b=\overline{pq} b=pq,那么它一共有四个交点(如果有个位数情况,将十位看做 0 0 0来考虑),每个交点各有节点个数: x p xp xp x q xq xq y p yp yp y q yq yq。所以节点总个数为:

a n s = x p + x q + y p + y q ans=xp+xq+yp+yq ans=xp+xq+yp+yq

我们可以对a,b进行数位分离,每位上的数字两两相乘求和。

赛时代码&AC代码:

#include <bits/stdc++.h>
using namespace std;
int A[100], B[100];
typedef long long ll;
int main() {
    freopen("multiplication.in", "r", stdin);
    freopen("multiplication.out", "w", stdout);
    int a, b;
    scanf("%d%d", &a, &b);
    int la = 0, lb = 0;
    while (a) {   //对a数位分离
    	A[++la] = a % 10;
    	a /= 10;
	}
	while (b) {  //对b数位分离
    	B[++lb] = b % 10;
    	b /= 10;
	}
	ll ans = 0;
	for (int i = 1; i <= la; i++) {
		for (int j = 1; j <= lb; j++) {   //两两相乘求和
			ans += A[i] * B[j];
		}
	}
	printf("%lld", ans);
    fclose(stdin);
    fclose(stdout);
	return 0;
}

问题三:去重求和(summation)

情况:

AC

题意:

定义 s u m ( l , r ) sum(l,r) sum(l,r) a l a_l al~ a r a_r ar之间的式子去重后的和。

∑ l = 1 n ∑ r = l n s u m ( l , r ) \sum\limits^n_{l=1}\sum\limits^n_{r=l}sum(l,r) l=1nr=lnsum(l,r)。(结果对 1 0 9 + 7 10^9+7 109+7取余)

赛时思路&题解:

举例a[]={1, 1, 3, 1, 5}

作为l,r时的sum(l,r)a[1]a[2]a[3]a[4]a[5]
a[1]11449
a[2]1449
a[3]349
a[4]16
a[5]5

拆分之后:

作为l,r时的sum(l,r)a[1]a[2]a[3]a[4]a[5]
a[1]111+31+31+3+5
a[2]11+31+31+3+5
a[3]31+31+3+5
a[4]11+5
a[5]5

整理一下每次出现的次数:

作为l时所有sum出现的次数a[1]a[2]a[3]a[4]a[5]总和
a[1]503011×5+1×0+3×3+1×0+5×1
a[2]43011×4+3×3+1×0+5×1
a[3]3213×3+1×2+5×1
a[4]211×2+5×1
a[5]15×1

现在我们可以发现规律:

  1. 如果第 i i i个元素前面(以左端点 l l l为起点)没有重复的,则它在当前着一个 l l l为左端点的所有 s u m sum sum中出现次数为 n − i + 1 n-i+1 ni+1
  2. 如果第 i i i个元素前面(以左端点 l l l为起点)有重复的,则它在当前着一个 l l l为左端点的所有 s u m sum sum中出现次数为 0 0 0
  3. i i i个元素在所有的 s u m sum sum中最多出现了 i i i次;
  4. 如果第 i i i个元素前面相同的元素的编号为 x x x(无相同, x = 0 x=0 x=0),那么第 i i i个元素出现次数不为 0 0 0的个数为 i − x i-x ix(因为在 ( x , i ] (x,i] (x,i] l l l作为左端点的 s u m sum sum中都不受前面相同的元素影响且一定包含第 i i i个元素)。

整理规律:第 i i i个元素的贡献值为:

a n s + = ( n − i + 1 ) × a [ i ] × ( i − x ) ans+=(n-i+1)×a[i]×(i-x) ans+=(ni+1)×a[i]×(ix)

注意取余 m o d mod mod

赛时代码&AC代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 5e5 + 10;
const int mod = 1e9 + 7;
typedef long long ll;
ll a[N], n;
ll ans = 0;
map<ll, ll> p;
int main() {
    freopen("summation.in", "r", stdin);
    freopen("summation.out", "w", stdout);
    scanf("%lld", &n);
    for (ll i = 1; i <= n; i++) scanf("%lld", &a[i]);
    for (ll i = 1; i <= n; i++) {
    	ll x = p[a[i]];
    	ans = (ans + (n - i + 1)  % mod * a[i]  % mod * (i - x) % mod) % mod;   //公式
    	p[a[i]] = i;
	}
	printf("%lld", ans % mod);
    fclose(stdin);
    fclose(stdout);
	return 0;
}

问题四:点集操作(point)

情况:

WA,赛后AC

题意:

一个有向无环图,对这个图做任意次 m o d i f y ( i , j ) modify(i,j) modify(i,j)操作之后的图中剩余的最小点数,其中 1 ≤ i , j ≤ n 1≤i,j≤n 1i,jn,其中 m o d i f y ( i , j ) modify(i,j) modify(i,j)为一次操作:

  1. 任选不同的两个点;
  2. A i A_i Ai i i i能到达的所有点组成的点集, A j A_j Aj j j j能到达的所有点组成的点集 (每个点可以到达的点集包含这个点本身);
  3. B B B为一个最大的点集,满足 B 既是 A i A_i Ai的子集,又是 A j A_j Aj的子集 。
  4. B B B在图中变成一个新点, B B B内的所有边全部删除。点集 B B B以外的点与点集 B B B以内的点的连边关系转移到新点上。

赛时思路:

骗分。

题解:

一般的点有如下几种可能:

在这里插入图片描述

  1. 入度为0:这种点不可能会被合并;
  2. 出度为0:这种点可能被合并;
  3. 出度、入度都为0:这种点一定会被合并。

用数组存储每个点的入度、出度。

a n s = n ans = n ans=n。(初始认为所有的点都不需要合并)

把每个点都遍历一遍。当发现第3种点(入度、出度都大于0),那么把它的能到达的点都要合并到这个点中(因为它的儿子节点一定是第2或第3种点);如果这个点被标记过,那么它的儿子节点一定也要被合并。将没有标记过的点标记并 a n s − − ans-- ans

AC代码:

#include <bits/stdc++.h>
using namespace std;
const int N = 2e6;
typedef long long ll;
ll head[N + 5], v[N + 5], nex[N + 5], cnt;
ll du[N + 5][5];   //用于记录入度[1]和出度[2]
bool vis[N + 5];   //用于标记是否合并
void add(int x, int y) {  //头插法
	v[++cnt] = y;	
	nex[cnt]=head[x];	
	head[x]=cnt;	
}
int main() {
	memset(head, -1, sizeof head);
	memset(vis, 0, sizeof vis);
	ll n, m, x, y;
	cin >> n >> m;
	for(ll i = 1; i <= m; i++) {
		cin >> x >> y;	
		add(x, y);   //存边
		du[y][1]++;  //入度++
		du[x][2]++;  //出度++
	}
	ll ans = n;
	for (ll j = 1; j <= n; j++) {   //遍历每一个点
		if (vis[j] || (du[j][1] > 0 && du[j][2] > 0)) {   //如果这个点被标记或者是第三种情况的点,那么它的儿子节点都需要标记合并
			for(ll i = head[j]; ~i; i = nex[i]) {   //遍历儿子节点
				if (!vis[v[i]]) {   //没有标记过
					ans--;  //ans--,表示这个点合并到父亲节点那里,减少了一个点
				}
				vis[v[i]] = true;   //标记为合并
			}
		}
	}
	cout << ans;
	return 0;
}

总结

还是要注意时间比重的调整。把更多的时间用在第三、第四道题上,尽可能地为后面的题目那数据范围里的10%~50%的点的方法留时间。

注意多举例子。比如第三题,多举几个较长的,有概括性的,能够代表绝大多数情况的样例分析,可以节省很多的调试时间。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值