2026CSP-S模拟16

T1 整齐的队列

CF1458A Row GCD

题目描述

给定两个正整数序列 \(a_1, \ldots, a_n\)\(b_1, \ldots, b_m\)。对于每一个 \(j = 1, \ldots, m\),求 \(\gcd(a_1 + b_j, \ldots, a_n + b_j)\) 的值。

输入格式

第一行包含两个整数 \(n\)\(m\)\(1 \leq n, m \leq 2 \times 10^5\))。

第二行包含 \(n\) 个整数 \(a_1, \ldots, a_n\)\(1 \leq a_i \leq 10^{18}\))。

第三行包含 \(m\) 个整数 \(b_1, \ldots, b_m\)\(1 \leq b_j \leq 10^{18}\))。

输出格式

输出 \(m\) 个整数,第 \(j\) 个数表示 \(\gcd(a_1 + b_j, \ldots, a_n + b_j)\) 的值。

输入输出样例 #1

输入 #1

4 4
1 25 121 169
1 2 7 23

输出 #1

2 3 8 24

打比赛的时候这题我不太会没有想到那么高深的东西 , 我就使用了传统做题方式——打表 , 你别说还让我打出来了 , 我发现\(a\)序列的差取得最大公约数 ,在与\(a[1]\) + \(b[j]\) 取最大公约数就是答案 , 我就写过了, 可是为啥呢 , 赛后我翻看了一下题解 , 其中提到\(gcd(x , y)\)= \(gcd(y , x - y)\)\(gcd(6 , 4)\) = \(gcd(4 , 2)\) , 其实这个东西与\(gcd(x , y)\) = \(gcd(y , x \bmod y)\)有异曲同工之妙,只不过\(mod\)多减了几次 ;
回到正题 要求\(gcd(a_1 + b_j , a_2 + b_j , ...,a_n + b_j)\) , 这时我们使用上面的定理将其转化为\(gcd(a_1 + b_j , gcd(a_2 - a_1 , a_3 - a_2 , ... , a_n , a_n - 1))\),只需预处理出差分后的最大公约数即可;
时间复杂度:\(O(n log v + m log v)\) , 一次预处理加每一次的辗转相除;
上代码——
不要忘记\(long long\)

代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;
int a[N] , b[N] , n , m;
signed main(){
	freopen("gcd.in" , "r" , stdin);
	freopen("gcd.out" , "w" , stdout);
	cin >> n >> m;
	for(int i = 1 ; i <= n ; i ++) cin >> a[i];
	for(int i = 1 ; i <= m ; i ++) cin >> b[i];
	sort(a + 1 , a + 1 + n);
	int len = unique(a + 1 , a + 1 + n) - a - 1;
	int d2 = a[2] - a[1];
	if(len == 1) d2 = a[1];
	for(int i = 3 ; i <= len ; i ++){
		d2 = __gcd(d2 , (a[i] - a[i - 1]));
	}
	for(int i = 1 ; i <= m ; i ++){
		if(len == 1) cout << a[1] + b[i] << " ";
		else cout << __gcd(d2 , (a[1] + b[i])) << " ";
	}
	return 0;
}

T2 词典压缩

CF1200E Compress Words

题目描述

Amugae 有一个由 \(n\) 个单词组成的句子。他想把这个句子压缩成一个单词。Amugae 不喜欢重复,因此当他将两个单词合并成一个单词时,会移除第二个单词中与第一个单词后缀相同的最长前缀。例如,他将 "sample" 和 "please" 合并成 "samplease"。

Amugae 会从左到右依次合并他的句子(即,先合并前两个单词,然后将结果与第三个单词合并,依此类推)。请编写程序输出合并过程结束后得到的压缩单词。

输入格式

第一行包含一个整数 \(n\)\(1 \le n \le 10^5\)),表示 Amugae 句子中的单词数。

第二行包含 \(n\) 个由单个空格分隔的单词。每个单词非空,仅由大小写英文字母和数字('A', 'B', ..., 'Z', 'a', 'b', ..., 'z', '0', '1', ..., '9')组成。所有单词的总长度不超过 \(10^6\)

输出格式

输出一行,表示合并过程结束后得到的压缩单词。

输入输出样例 #1

输入 #1

5
I want to order pizza

输出 #1

Iwantorderpizza

输入输出样例 #2

输入 #2

5
sample please ease in out

输出 #2

sampleaseinout

其实这道题其实并没有那么太难,但我没有场切,其实这个东西就是一个\(kmp\) ,\(kmp\)维护的就是一个最长公共前缀与后缀,举个\(example\),\(aabacaab\)的就为\(aab\) , \(kmp\) 维护的就是每个位置的前后缀,时间复杂度能做到\(O(n)\) , 这个题就是把新加入的字符串放在前面 , 之前合并好的字符串放在后面 ,中间加一个无用字符 , 跑\(kmp\)即可解决;在这里我还想说一下为什么要加无用字符 , 因为假如你原来合并的为\(aaaa\) , 后来加入\(aa\) , 这时候你的最长公共前缀与后缀就算多了 , 就假了 , 这个题就完了;
时间复杂度\(O(2 * 总长)\)

代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n;
string s , ans;
int kmp(string a){
	int nex[N];
	for(int i = 2 , j = 0 ; i < (int)a.size() ; i ++){
		while(j && a[i] != a[j + 1]) j = nex[j];
		if(a[i] == a[j + 1]) j ++;
		nex[i] = j;
	}
	return nex[(int)a.size() - 1];
}
int main(){
	freopen("words.in" , "r" , stdin);
	freopen("words.out" , "w" , stdout);
	cin >> n;
	for(int i = 1 ; i <= n ; i ++){
		cin >> s;
		if(i == 1) ans = s;
		else{
			string a = ans.substr(max(0 , (int)ans.size() - (int)s.size()) , s.size());
			a = " " + s + "#" + a;
			int len = kmp(a);
			ans += s.substr(len , s.size());
		}
	}
	cout << ans << endl;
	return 0;
}

T3密码碎片

CF508D Tanya and Password

题目描述

在爸爸上班的时候,小女孩 Tanya 决定玩弄一下爸爸的秘密数据库密码。爸爸的密码是一个包含 \(n+2\) 个字符的字符串。她把密码所有可能的 \(n\) 个连续三字母子串都写在小纸条上,每个子串出现几次就写几次,然后把密码扔了。每张纸条上都只写了一个出现在密码里的三字母子串。

之后 Tanya 忽然意识到如果爸爸知道她玩的这个游戏会生气,于是决定尝试复原密码,或者至少还原出一个能对应这些子串集合的字符串。你需要帮她完成这项艰巨的任务。已知爸爸的密码只包含大小写拉丁字母和数字。大小写字母视为不同字符。

输入格式

第一行包含一个整数 \(n\)\(1 \le n \le 2 \cdot 10^{5}\)),表示 Tanya 得到的三字母子串的数量。

接下来的 \(n\) 行每行包含三个字符,表示爸爸密码的一个子串。输入中的每个字符为小写字母、大写字母或数字。

输出格式

如果 Tanya 在游戏过程中出现了错误,导致不存在能由这些子串组成的字符串,则输出 “NO”。

如果存在可能的原始密码字符串,请输出 “YES”,并在下一行输出任意一个满足条件的密码字符串。

输入输出样例 #1

输入 #1

5
aca
aba
aba
cab
bac

输出 #1

YES
abacaba

输入输出样例 #2

输入 #2

4
abc
bCb
cb1
b13

输出 #2

NO

输入输出样例 #3

输入 #3

7
aaa
aaa
aaa
aaa
aaa
aaa
aaa

输出 #3

YES
aaaaaaaaa

原题链接
这又是一道字符串的题, 这题的思路十分之巧妙 , 首先你发现想要合并\(2\)个字符串,你得保证其中一个的前两个字母与另一个的后两个字母相同你才能合并, 于是你就想到将他们记录下来,在某位神犇的讲解下,我们知道了将一个三字符的前两个字母和后两个字母运用哈希值进行存储 , 然后将两个串连一条有向边 , 跑一个欧拉路径就行了 , 这时问题来啦 , 欧拉路径是啥 ,其实就是一笔画问题 , 就是你能遍历所有的边且每条边只经过一次;
欧拉路径
无标题
这时跑一遍欧拉路径发现可以 , 就能构造;构造欧拉路径的的条件(不是欧拉回路)有一个出度比入度多一的当作起点 , 有一个出度比入度多一的当作起点 , 其他点的出度和入度相同 , (是欧拉回路)每个点的出度和入度都相同 , 否则就不是欧拉路径;
好了,这题也结束了;
时间复杂度\(O(n + 62^2)\)

代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define pb push_back
const int N = 1e6 + 10;
const int base = 131;
int n , ru[N] , ch[N] , b , t[2];
string s;
vector<int> edge[N];
int stk[N] , top , head[N];
void dfs(int x){
	for(int &i = head[x] ; i < (int)edge[x].size() ; ) dfs(edge[x][i ++]);
	stk[++ top] = (x % base);
}
signed main(){
	freopen("shards.in" , "r" , stdin);
	freopen("shards.out" , "w" , stdout);
	cin >> n;
	int maxn = 0;
	for(int i = 1 ; i <= n ; i ++){
		cin >> s;
		int x = s[0] * base + s[1] , y = s[1] * base + s[2] ;
		maxn = max({maxn , x , y});
		edge[x].pb(y) , ch[x] ++ , ru[y] ++;
	}
	bool f = 0; b = maxn;
	for(int i = 0 ; i <= maxn ; i ++){
		if(ru[i] != ch[i]){
			f = 1;
			if(ch[i] - ru[i] == 1) b = i , t[0] ++;
			else if(ru[i] - ch[i] == 1) t[1] ++;
			else return cout << "NO" << endl , 0;
		}
	}
	if(f && !(t[0] == t[1] && t[0] == 1)) return cout << "NO" << endl , 0;
	dfs(b);
	if(top != n + 1) return cout << "NO" << endl , 0;
	cout << "YES" << endl << (char)(b / base);
	for(int i = top ; i >= 1 ; i --) cout << (char)stk[i];
	cout << endl;
	return 0;
} 

T4费里斯转轮

CF321E Ciel and Gondolas

题目描述

Fox Ciel 正在游乐园,现在她正在摩天轮前面排队。队伍中共有 \(n\) 个人(准确来说是狐狸):我们用第一个人指队伍最前面的人,用第 \(n\) 个人指队伍最后一个人。

将有 \(k\) 个座舱,分配座舱的方法如下:

  • 当第一个座舱到来时,队伍前面的 \(q_1\) 个人进入座舱。
  • 当第二个座舱到来时,剩下队伍的前面 \(q_2\) 个人进入座舱。......
  • 剩下的 \(q_k\) 个人进入最后(第 \(k\) 个)座舱。

注意,\(q_1, q_2, ..., q_k\) 必须都是正整数。你可以从题意得出 \(q_1 + q_2 + \cdots + q_k = n\),且 \(q_i > 0\)

你知道,每个人都不愿和陌生人同舱,所以你的任务是找到一种最优分配方式(即找到一个最优的 \(q\) 序列),使得大家都尽可能满意。对于每一对乘客 \(i\)\(j\),存在一个值 \(u_{ij}\) 表示他们之间的陌生程度。你可以假设对于所有的 \(i, j\) 都有 \(u_{ij} = u_{ji}\),且对于所有 \(i\)\(u_{ii} = 0\),其中 \(1 \leq i, j \leq n\)。一个座舱的陌生值等于该座舱中所有两两乘客之间的陌生程度之和。

总陌生值等于所有座舱的陌生值之和。请你帮助 Fox Ciel 找到某种最优分配方式,使总陌生值的最小可能值是多少。

输入格式

第一行为两个整数 \(n\)\(k\)\(1 \leq n \leq 4000\)\(1 \leq k \leq \min(n, 800)\)),分别表示排队人数和座舱数量。接下来的 \(n\) 行,每行包含 \(n\) 个整数,表示矩阵 \(u\),其中 \(0 \leq u_{ij} \leq 9\),且 \(u_{ij} = u_{ji}\)\(u_{ii} = 0\)

请使用高效的输入方式(例如,对于 Java 请使用 BufferedReader 而不是 Scanner)。

输出格式

输出一个整数,表示最小可能的总陌生值。

输入输出样例 #1

输入 #1

5 2
0 0 1 1 1
0 0 1 1 1
1 1 0 0 0
1 1 0 0 0
1 1 0 0 0

输出 #1

0

输入输出样例 #2

输入 #2

8 3
0 1 1 1 1 1 1 1
1 0 1 1 1 1 1 1
1 1 0 1 1 1 1 1
1 1 1 0 1 1 1 1
1 1 1 1 0 1 1 1
1 1 1 1 1 0 1 1
1 1 1 1 1 1 0 1
1 1 1 1 1 1 1 0

输出 #2

7

输入输出样例 #3

输入 #3

3 2
0 2 0
2 0 3
0 3 0

输出 #3

2

说明/提示

在第一个样例中,可以这样分配:{1, 2} 进同一个座舱,{3, 4, 5} 进另一个座舱。

在第二个样例中,最优方案是:{1, 2, 3} | {4, 5, 6} | {7, 8}。
此题先要想最简单的\(dp\)
定义\(dp[i][j]\) 为前\(i\)个人分成\(j\)组得到的最小陌生值
\(sum[i][j]\)表示\(i\)\(j\) 之间的陌生值之和
状态转移即为\(dp[i][j]\) = \(\min\limits_{j <= z <= i}\) \(dp[z - 1][j - 1] + sum[z][i]\)
好了你已经获得\(50pts\)
然后我们考虑优化\(dp\)
你用暴力代码输出你每次转移时的决策点

暴力
#include<bits/stdc++.h>
using namespace std;
const int N = 5e3 + 10;
int sum[N][N] , a , n , k;
int dp[N][N];
int calc(int x , int y){
	return sum[x][x] - sum[x][y - 1] - sum[y - 1][x] + sum[y - 1][y - 1];
}
int main(){
	freopen("ferris4.in" , "r" , stdin);
	freopen("ferris.out" , "w" , stdout);
	cin >> n >> k;
	for(int i = 1 ; i <= n ; i ++)
		for(int j = 1 ; j <= n ; j ++)
			cin >> a , sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a;
	for(int i = 0 ; i <= n ; i ++)
		for(int j = 0 ; j <= k ; j ++) 
			dp[i][j] = 0x3f3f3f3f;
	dp[0][0] = 0;
	for(int i = 1 ; i <= n ; i ++){
		for(int j = 1 ; j <= k ; j ++){
			int pos = 0;
			for(int z = j - 1 ; z < i ; z ++){
				if(dp[i][j] > dp[z][j - 1] + calc(i , z + 1))dp[i][j] = min(dp[z][j - 1] + calc(i , z + 1) , dp[i][j]) , pos = z;	
			}
			cout.width(5);
			cout << pos << " ";
		}
		cout << endl;
	}			
	cout << dp[n][k] / 2 << endl;
	return 0;
}

批注 2026-08-26 073911
你会发现每个数上面的数都比这个数小,右边的数都比这个数要大,所以你就由上面的数到右面的数转移即可,这就是决策单调性,注意\(n\)要倒序枚举;
时间复杂度:读入\(O(n^2)\) + \(dp O(nklogn)\)

码$1$
#include<bits/stdc++.h>
using namespace std;
const int N = 5e3 + 10;
int sum[N][N] , a , n , k;
int dp[N][N] , f[N][N];
int calc(int x , int y){
	return sum[x][x] - sum[x][y - 1] - sum[y - 1][x] + sum[y - 1][y - 1];
}
int main(){
	freopen("ferris.in" , "r" , stdin);
	freopen("ferris.out" , "w" , stdout);
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	cin >> n >> k;
	for(int i = 1 ; i <= n ; i ++)
		for(int j = 1 ; j <= n ; j ++)
			cin >> a , sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + a;
	for(int i = 0 ; i <= n ; i ++)
		for(int j = 0 ; j <= k ; j ++) 
			dp[i][j] = 0x3f3f3f3f;
	dp[0][0] = 0;
	for(int i = 1 ; i <= k ; i ++) f[i][n + 1] = n;
	for(int j = 1 ; j <= k ; j ++){
		for(int i = n ; i >= 1 ; i --){
			for(int z = max(1 , f[j - 1][i]) ; z <= f[j][i + 1] ; z ++){
				if(dp[i][j] > dp[z - 1][j - 1] + calc(i , z)) dp[i][j] = dp[z - 1][j - 1] + calc(i , z) , f[j][i] = z;	
			}
		}
	}
	cout << dp[n][k] / 2 << endl;
	return 0;
}
码$2$
#include<bits/stdc++.h>
using namespace std;
const int N = 5e3 + 10;
int sum[N][N] , a[N][N] , n , k;
int dp[N][N] , f[N][N];
int main(){
	freopen("ferris.in" , "r" , stdin);
	freopen("ferris.out" , "w" , stdout);
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin >> n >> k;
	for(int i = 1 ; i <= n ; i ++)
		for(int j = 1 ; j <= n ; j ++) cin >> a[i][j];
	for(int i = 2 ; i <= n ; i ++){
		int pre = 0;
		for(int j = i - 1 ; j >= 1 ; j --){
			pre += a[j][i] , sum[j][i] = sum[j][i - 1] + pre;
		}
	}
	for(int i = 0 ; i <= n ; i ++)
		for(int j = 0 ; j <= k ; j ++) 
			dp[i][j] = 0x3f3f3f3f;
	dp[0][0] = 0;
	for(int i = 1 ; i <= k ; i ++) f[i][n + 1] = n;
	for(int j = 1 ; j <= k ; j ++){
		for(int i = n ; i >= 1 ; i --){
			for(int z = max(1 , f[j - 1][i]) ; z <= f[j][i + 1] ; z ++){
				if(dp[i][j] > dp[z - 1][j - 1] + sum[z][i]) dp[i][j] = dp[z - 1][j - 1] + sum[z][i] , f[j][i] = z;	
			}
		}
	}
	cout << dp[n][k] << endl;
	return 0;
}
posted @ 2026-08-26 07:49  wang_daylight  阅读(30)  评论(0)    收藏  举报