SSY20260131模拟赛题解

发布于

T1

题目描述

小明和你正在玩一个游戏。

有一个字符串池,池内只有三种字符串:ABC, BCA, CAB,每种字符串有无数个。

小明首先构造一个字符串。他从字符串池中选取了 N 个字符串,然后把这些字符串拼接成一个字符串,记为 S。显然,S 的长度为 3N,即 S 包含 3N 个字符。

然后小明让你把他构造的字符串清空,即通过若干次删除字符的操作把 S 变成空串。

但小明要求的删除操作非常奇特:每次删除操作,把你准备删除的字符按在 S 中的顺序拼接成一个字符串 T,你必须保证 T 是由两个相同的字符串拼接成的字符串,这次删除操作才可成功执行。

问:你能否将小明构造的字符串 S 清空?

如果不能,请输出一行一个整数 -1;如果能,请你输出你的操作方案,具体输出分两行:

第一行:一个整数 K,表示你删除操作的次数;
第二行:3N 个整数,第 i 个整数表示 S 的第 i 个字符在第几次操作时被删除。当然,你输出的这些整数应该介于 1 和 K 之间。
为了增加游戏难度,小明又给你提了一项要求:他告诉你一个参数 P,并且 P 要么为 0,要么为 1。如果 P = 0, 那么你必须要使用最少的操作次数将字符串清空;如果 P = 1,那么允许你操作的次数可以比最少的操作次数最多多一次。答案可能不唯一,按要求输出满足要求的任意一种可行方案即可。

每个测试点包含多组测试数据,而小明告诉你的参数 P则是该测试点中所有测试数据通用的。

输入格式

第一行:两个整数 T,P,分别表示数据组数和小明给出的参数。
对于每组数据:
第一行:一个整数 N
第二行:一个字符串 S数据保证所有输入的 N 之和 ≤ 10^5

输出格式

每组数据的答案占一行或两行,格式如题所述。

sol

task1:p=1时20分
task2:p=0
所有的字符串都可以在两次以内删除,答案<=2。
如果长度为奇数一定无解
否则从中间劈开,若左边等于右边一次删光。
一次删不完就构造,
ABC BCA CAB,没有BCA、BAC。可循环

每两个长度为3的,一定都有一个最长公共字串/前后缀
例如:ABCCABABCABC
ABCCAB为2(BC)
从中间劈开,因为任何两个要么相同要么就是公共子串2,
相同的可以直接匹配。不同的是公共字串2、1。
不妨将AB作为第一次删除的序列中,C放在第二个。
CAB相当于构造了一个AB,是第一次要删除的序列。ABCAB相当于ABC+AB,后面也有了ABC+AB
第二次删除C就可以了。多试几次就可以看出规律了。
思维题、构造题

思考&改题:如果有ABC BCA CAB CBA 六种?

参考

#include<bits/stdc++.h>
using namespace std;
int T,K,P;
int ans[300005];
int fl[100005];
string s;
int main(){
	ios::sync_with_stdio(false);cin.tie(0);
	cin>>T>>P;
	while(T--){
		cin>>K>>s;
		if(K%2==1){
			cout<<"-1\n";
			continue;
		}
		s='#'+s;
		for(int i=1;i<=K;++i){
			if(s[i*3-2]=='A'&&s[i*3-1]=='B'&&s[i*3]=='C'){
				fl[i]=1;
			}
			else if(s[i*3-2]=='B'&&s[i*3-1]=='C'&&s[i*3]=='A'){
				fl[i]=2;
			}
			else{
				fl[i]=3;
			}
		} 
		int p=K/2,maxx=-1;
		for(int i=1;i<=p;++i){
			if(fl[i]==fl[i+p]){
				ans[i*3-1]=1;
				ans[i*3-2]=1;
				ans[i*3]=1;
				ans[(i+p)*3-1]=1;
				ans[(i+p)*3-2]=1;
				ans[(i+p)*3]=1;
				maxx=max(maxx,1);
			}
			else{
				if(fl[i]==1){
					if(fl[i+p]==2){ //ABC BCA
						ans[i*3-2]=2; 
						ans[i*3-1]=1;
						ans[i*3]=1;
						ans[(i+p)*3-2]=1;
						ans[(i+p)*3-1]=1;
						ans[(i+p)*3]=2;
					}
					if(fl[i+p]==3){ //ABC CAB
						ans[i*3-2]=1; 
						ans[i*3-1]=1;
						ans[i*3]=2;
						ans[(i+p)*3-2]=2;
						ans[(i+p)*3-1]=1;
						ans[(i+p)*3]=1;
					}
				}
				if(fl[i]==2){
					if(fl[i+p]==1){ //BCA ABC
						ans[i*3-2]=1; 
						ans[i*3-1]=1;
						ans[i*3]=2;
						ans[(i+p)*3-2]=2;
						ans[(i+p)*3-1]=1;
						ans[(i+p)*3]=1;
					}
					if(fl[i+p]==3){ //BCA CAB
						ans[i*3-2]=2; 
						ans[i*3-1]=1;
						ans[i*3]=1;
						ans[(i+p)*3-2]=1;
						ans[(i+p)*3-1]=1;
						ans[(i+p)*3]=2;
					}
				}
				if(fl[i]==3){
					if(fl[i+p]==1){ //CAB ABC
						ans[i*3-2]=2; 
						ans[i*3-1]=1;
						ans[i*3]=1;
						ans[(i+p)*3-2]=1;
						ans[(i+p)*3-1]=1;
						ans[(i+p)*3]=2;
					}
					if(fl[i+p]==2){ //CAB BCA
						ans[i*3-2]=1; 
						ans[i*3-1]=1;
						ans[i*3]=2;
						ans[(i+p)*3-2]=2;
						ans[(i+p)*3-1]=1;
						ans[(i+p)*3]=1;
					}
				}
				maxx=2;
			}
		}
		if(maxx==1){
			cout<<"1\n";
			for(int i=1;i<3*K;++i){
				cout<<"1 ";
			}
			cout<<"1\n";
		}
		else{
			cout<<"2\n";
			for(int i=1;i<3*K;++i){
				cout<<ans[i]<<' ';
			}
			cout<<ans[3*K]<<'\n';
		}
	}
	return 0;
}

T2

题目描述

有两个长度为 ​N 的正整数序列:

  • ​A = [A_1, A_2, \dots, A_N]
  • ​B = [B_1, B_2, \dots, B_N]

你可以进行恰好一次操作:选择序列 ​A 的一个区间 ​[l, r]​1 \le l \le r \le N),将该区间内的元素整体翻转。
总共有 ​\frac{(N+1) \times N}{2} 种不同的区间可供选择。

对于第 ​i 种操作方式(即选择第 ​i 个区间),翻转后会得到一个新的序列 ​A'。我们统计该序列中满足 ​A'_p = B_p 的位置个数,记为 ​M_i

最终,我们需要求所有 ​M_i 的总和:

\sum_{i=1}^{(N+1)N/2} M_i

输入格式

  1. 第一行:一个整数 ​N
  2. 第二行:序列 ​A​N 个元素 ​A_1, A_2, \dots, A_N
  3. 第三行:序列 ​B​N 个元素 ​B_1, B_2, \dots, B_N

输出格式

一个整数,表示所有 ​M_i 的总和。

输入样例

3
1 2 3
3 2 1

输出样例

6

样例解释

​N=3 时,总共有 ​\frac{3 \times 4}{2} = 6 种操作方式:

  1. 翻转长度为 1 的区间(共 3 个):翻转后 ​M_i = 1
  2. 翻转长度为 2 的区间(共 2 个):翻转后 ​M_i = 0
  3. 翻转长度为 3 的区间(共 1 个):翻转后 ​M_i = 3

总和为 ​1+1+1+0+0+3 = 6

数据范围

  • 100% 数据​1 \le N \le 5 \times 10^5​1 \le A_i, B_i \le N
  • 10% 数据​N \le 100
  • 10% 数据​N \le 5000
  • 20% 数据​A_i, B_i 随机生成
  • 20% 数据​1 \le A_i, B_i \le 2 且随机生成

sol

拆贡献。
每个axby映射到对答案的贡献上。

123 321中,2与2相等。
反转区间不包含这个2的其他区间反转一定就会有它的贡献。不包含2的区间不管怎么反转,它的贡献始终存在。

对于每一个上下相等的就可以求。
若上下不相等:
​a_xxxxxxxxxxxxxx
xxxxxxxx​b_y(a_x)
位置不一样,且ax=by。ax可以反转到右侧,当然,by后面可能也有许多相等的。我们要计算出所有ax=by的贡献。

左侧有多少个数,右边就有多少个,这样相对。

参考:

#include<bits/stdc++.h>
using namespace std;
#define int long long
int T,n,m;
int a[501000],b[501000];
vector<int> posa[501000],posb[501000],sumb[501000];
signed main(){
//	freopen("ex.in","r",stdin);
	ios::sync_with_stdio(0);
	cin.tie(0),cout.tie(0);
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		posa[a[i]].push_back(i);
		posb[i].push_back(0);
	}
	int ans=0;
	for(int i=1;i<=n;i++){
		cin>>b[i];
		if(a[i]==b[i]) ans++;
		posb[b[i]].push_back(i);
	}
	for(int i=1;i<=n;i++){
		sumb[i].push_back(0);
		for(int j=1;j<posb[i].size();j++){
			sumb[i].push_back(sumb[i][j-1]+posb[i][j]);
		}
	}
	ans*=(n*n+n)/2;
	for(int i=1;i<=n;i++){
		for(int j=0;j<posa[i].size();j++){
			if(posb[i].size()==1) break;
			int id=lower_bound(posb[i].begin()+1,posb[i].end(),posa[i][j])-posb[i].begin()-1;
//			printf("%d\n",id);
			if(id>=1){
				int k=upper_bound(posb[i].begin()+1,posb[i].end(),n-posa[i][j]+1)-posb[i].begin()-1;
//				printf("%d %d\n",k,id);
				if(k>=1){
					ans+=sumb[i][min(k,id)];
				}
				if(k<id){
					ans+=(id-k)*(n-posa[i][j]+1);
				}
			}
			id=upper_bound(posb[i].begin()+1,posb[i].end(),posa[i][j])-posb[i].begin();
//			printf("%d\n",id);
			if(id<posb[i].size()){
				int k=lower_bound(posb[i].begin()+1,posb[i].end(),n-posa[i][j]+1)-posb[i].begin();
//				printf("%d %d\n",k,id);
				if(k<posb[i].size()){
					ans+=min(posb[i].size()-k,posb[i].size()-id)*(n+1)-(sumb[i][posb[i].size()-1]-sumb[i][max(k-1,id-1)]);
				}
				if(k>id){
					ans+=(k-id)*(posa[i][j]);
				}
			}
			id=*lower_bound(posb[i].begin()+1,posb[i].end(),posa[i][j]);
			if(id==posa[i][j]) ans+=min(id,n-id+1);
//			for(int k=0;k<posb[i].size();k++){
//				int x=posa[i][j],y=posb[i][k];
//				if(x>y) swap(x,y);
//				ans+=min(x,n-y+1);
//			}
		}
	}
	for(int i=1;i<=n;i++){
		if(a[i]==b[i]) ans-=i*(n-i+1);
	}
	cout<<ans<<'\n';
	return 0;
}

T3

题目描述

小明写了一个程序,输入一个整数 N,便可以生成一个 N×N 的矩阵。程序如下:

#include<bits/stdc++.h>
using namespace std;
int main()
{
    int N;
    cin>>N;
    for(int i=1;i<=N;i++)
    {
        for(int j=1;j<=N;j++)
        {
            cout<<i+j<<' ';
        }
        cout<<'\n';
    }
    return 0;
}

第一步:操作了若干次(可以为 0 次),每次操作将矩阵中的某两行进行交换;

第二步:操作了若干次(可以为 0 次),每次操作将矩阵中的某两列进行交换;

第三步:操作了若干次(可以为 0 次),每次操作选取矩阵中的某两个数进行全部互换。即,假设某次操作选取的两个数是 x, y,则将矩阵中所有的 x 替换为 y, 所有的 y 替换为 x。

注意,三步操作是依次进行的,下一步开始后,不能返回上一步进行操作。

全部操作完成后,小明向你展示了最终的矩阵。但是他已经忘了自己是如何操作的。你能帮他还原一下他可能的操作吗?

你只需要输出小明前两步操作结束之后,第三步操作开始之前,矩阵的一种可能状态。答案可能很多,你需要输出字典序最小的那个矩阵。

两个N×N 的矩阵的字典序大小关系,等于它们先按行、再按列依次比较每个元素时,第一次遇到的不同元素的大小关系。

输入格式

第一行:一个整数 N。

接下来是一个 N×N 的矩阵,表示小明三步操作结束后的矩阵。

输出格式

一个 N×N 的矩阵:共 N 行,每行 N 个数,同一行的数之间以一个空格隔开。数据保证有解。

样例

输入

1
2

输出

2
输入
3
3 6 5
6 2 4
5 4 6
输出
2 4 3
4 6 5
3 5 4
样例2解释

该样例中 N=3

--- 初始矩阵 ---

2 3 4
3 4 5
4 5 6

--- 第一步 ---

=== 第二行和第三行交换 ===>

2 3 4
4 5 6
3 4 5

---第二步---

=== 第二列和第三列交换 ===>

2 4 3
4 6 5
3 5 4

---第三步---

=== (1) 交换 2 和 4 ===>

4 2 3
2 6 5
3 5 2

=== (2) 交换 3 和 4 ===>

3 2 4
2 6 5
4 5 2

=== (3) 交换 4 和 5 ===>

3 2 5
2 6 4
5 4 2

=== (4) 交换 2 和 6 ===>

3 6 5
6 2 4
5 4 6

至此得到小明的序列,在第三步开始之前的矩阵为:

2 4 3
4 6 5
3 5 4

是字典序最小的。

下列也是一种可能的操作序列,但在第三步开始之前的矩阵不是字典序最小的。

---初始矩阵---

2 3 4
3 4 5
4 5 6

---第一步---

=== 第一行和第三行交换 ===>

4 5 6
3 4 5
2 3 4

=== 第二行和第三行交换 ===>

4 5 6
2 3 4
3 4 5

---第二步---

=== 第一列和第二列交换 ===>

5 4 6
3 2 4
4 3 5

=== 第一列和第三列交换 ===>

6 4 5
4 2 3
5 3 4

---第三步---

=== (1) 交换 3 和 4 ===>

6 3 5
3 2 4
5 4 3

=== (2) 交换 3 和 6 ===>

3 6 5
6 2 4
5 4 6

至此得到小明的序列,在第三步开始之前的矩阵为:

6 4 5
4 2 3
5 3 4

但它不是字典序最小的。

数据范围

20% 的数据:N≤6
40% 的数据:N≤8。
70% 的数据:N≤100。
100% 的数据:N≤1000

sol

随便生成一个加法表

2 3 4 5 6
3 4 5 6 7
4 5 6 7 8
5 6 7 8 9
6 7 8 9 10
性质一:2与10只出现过1次。只需要统计次数,找出最大值和最小值,因为这两个只能出现一次。
性质二:行列交换,不管如何变幻都在一行、都在一列。
性质三:对角线上数字出现次数最多。

2和10是最好判断的。那么假如第一个是2,推断后面。假如第一个是10,推断后面。
最后判断两个矩阵的字典序,取小的就可以了。

#include <bits/stdc++.h>
 
using namespace std;
const int MAXN = 1010;

struct Node{
	int r, c;
};

int N;
int F[MAXN][MAXN], ansss[MAXN][MAXN], cans[MAXN][MAXN];	//ansss
int rip[MAXN], cip[MAXN];
map<int, int> counts;
int cnt[MAXN], ccnt = 0;
map<int, Node> vNode;

void solve(int a) {
	Node p = vNode[a];
	int r0 = p.r;
	int c0 = p.c;
	for (int j = 1; j <= N; ++j) {
		int tmp = F[r0][j];
		cip[j] = counts[tmp];
	}
	for (int i = 1; i <= N; ++i) {
		int val = F[i][c0];
		rip[i] = counts[val];
	}
	for (int i = 1; i <= N; ++i) {
		for (int j = 1; j <= N; ++j) {
			cans[i][j] = rip[i] + cip[j];
		}
	}
}

bool cmp() {
	for (int i = 1; i <= N; ++i)
		for (int j = 1; j <= N; ++j)
			if (cans[i][j] != ansss[i][j]) return cans[i][j] < ansss[i][j];

	return false;
}

int main() {
    //
	cin >> N;
	for (int i = 1; i <= N; ++i) {
		for (int j = 1; j <= N; ++j) {
			cin >> F[i][j];
			counts[F[i][j]]++;
			if (vNode.find(F[i][j]) == vNode.end()) {
				vNode[F[i][j]] = {i, j};
			}
		}
	}

	if (N == 1) {
		cout << 2 << endl;
		return 0;
	}

	for (map<int, int>::iterator it = counts.begin(); it != counts.end(); it++) {
		if (it->second == 1) {
			cnt[ccnt++] = it->first;
		}
	}
	bool first = true;


	for (int k = 0; k < ccnt; ++k) {
		solve(cnt[k]);
		if(first == true){
			for (int i = 1; i <= N; i++) {
                for (int j = 1; j <= N; j++) {
                    ansss[i][j] = cans[i][j];
                }
            }
			first = false;
		}else {
			if(cmp()){
                for (int i = 1; i <= N; i++) {
                    for (int j = 1; j <= N; j++) {
                        ansss[i][j] = cans[i][j];
                    }
                }
            }
		}
	}

	for (int i = 1; i <= N; ++i) {
		for (int j = 1; j <= N; ++j) {
			cout << ansss[i][j] << " ";
		}
		cout << endl;
	}

	return 0;
}

总结

平时学的各种算法在这里都用不上,原因何在?
需要训练思维。学的精、学的方式、思维、方法要比模板更有用。

浏览(3)
评论

请登录后发表观点

暂无数据