CSP-J 2024题解
T1
排序,计数器。
测试点1:
n = 1时,答案为51。
测试点2~4:
判断出输入的牌各不相同后,答案为52 - n。
测试点5~7:
判定出牌以升序出现后,用每张牌和前一张作比较的道重复牌数,答案是52减去不重复牌数。
测试点8~10:
从保证升序的性质B获得启发,可以直接按字典序对牌排序,再统计不重复牌数。答案是52减去不重复牌数。
T2
二维数组,模拟。
测试点1~4:
k = 1时,只有一次操作,如果到达点没有越界且不是障碍物,则答案是2,否则答案为1。
测试点5、7:
地图均为空地,则执行k次操作, 只要没有越界,则可以前进,否则向右转。统计途径了几个点,即为答案。
其他测试点:
模拟
T3
贪心,规律,分类讨论
测试点1:
若余数为
T4
有
- 接龙序列一定是某一个人的数字序列中的一段连续子序列
- 接龙序列的长度在
[2,k] 之间 - 不能出现连续两次接龙是同一个人的情况T
5分做法
注意到第
单次询问的复杂度为
#include<bits/stdc++.h>
using namespace std;
void solve(){
int n, k, q;
cin >> n >> k >> q;
vector<int>L(n);
vector<vector<int>> a(n);
for(int i = 0; i < n; i++){
cin >> L[i];
a[i].resize(L[i]);
for(int j = 0; j < L[i]; j++)
cin >> a[i][j];
}
const int M = 2e5 + 10;
vector<bool>ans(M);
for(int i = 0; i < n; i++){
for(int r = 0; r < L[i]; r++){
for(int l = 0; l < r; l++){
if(r - l + 1 <= k && a[i][l] == 1)
ans[a[i][r]] = true;
}
}
}
while(q--){
int r, c;
cin >> r >> c;
cout << ans[c] << '\n';
}
}
int main(){
int T;
cin >> T;
while(T--){
solve();
}
return 0;
}
另外10分做法
(
但是我们还需要注意到的一个条件就是:连续两次接龙的人不能是同一个。因此,在
至此,我们可以这样设计暴力搜索的函数去回答每一次询问:
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n, k, q;
vector<int>L;
vector<int> a[N];
int r, c;
bool dfs(int t, int last, int x){
if(t == r) return x == c;
for(int i = 0; i < n; i++){
if(i == last) continue;
for(int rr = 0; rr < L[i]; rr++){
for(int ll = rr - 1; ll >= 0; ll--){
if(rr - ll + 1 > k) break;
if(a[i][ll] == x && dfs(t + 1, i, a[i][rr]))
return true;
}
}
}
return false;
}
void solve(){
cin >> n >> k >> q;
L.resize(n);
for(int i = 0; i < n; i++){
cin >> L[i];
a[i].resize(L[i]);
for(int j = 0; j < L[i]; j++){
cin >> a[i][j];
}
}
while(q--){
cin >> r >> c;
if(dfs(0, -1, 1)) puts("1");
else puts("0");
}
}
int main(){
int T;
cin >> T;
while(T--){
solve();
}
return 0;
}
另外55分做法
(动态规划、降维、暴力枚举、特殊性质B)
回想一下暴力搜索的过程,如果我们知道第
对于第
若
接下来,我们需要所有状态对应的
当
注意:如果上一轮接龙的人选如果是同一个人,那么是需要跳过当次转移的!
这个做法的时间复杂度是
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n, k, q;
vector<int>L;
vector<int> a[N];
int r, c;
int dp[110][N];
void solve(){
cin >> n >> k >> q;
L.resize(n);
for(int i = 0; i < n; i++){
cin >> L[i];
a[i].resize(L[i]);
for(int j = 0; j < L[i]; j++){
cin >> a[i][j];
}
}
memset(dp, -1, sizeof dp);
dp[0][1] = n; // 第0轮以1为结尾是可行,且方案很多。
for(int t = 1; t <= 10; t++){//第t轮
for(int i = 0; i < n; i++){//第i个人
for(int rr = 0; rr < L[i]; rr++){ // 右端点
for(int ll = rr - 1; ll >= 0; ll--){ //左端点
if(rr - ll + 1 > k) break;
int cc = a[i][rr], x = a[i][ll];
if(dp[t - 1][x] == i) continue; //上一轮同一个人
if(dp[t][cc] == -1){
if(dp[t - 1][x] != -1)
dp[t][cc] = i;
}
else if(dp[t][cc] != i && dp[t - 1][x] != -1){
dp[t][cc] = n;
}
}
}
}
}
while(q--){
cin >> r >> c;
if(dp[r][c] >= 0) puts("1");
else puts("0");
}
}
int main(){
int T;
cin >> T;
while(T--)
solve();
return 0;
}
满分做法
到目前为止,动态规划的时间复杂度过高,瓶颈在于需要暴力枚举上一轮以哪一个数字为结尾,才能转移到本轮的状态。我们的做法是枚举每个人的区间,时间复杂度为
能不能不枚举区间?
现在考虑第
若在上一轮(第
例如
第
那么第1能管后面1,又可以管住后面
整体时间复杂度为
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n, k, q;
vector<int>L;
vector<int> a[N];
int r, c;
int dp[110][N];
void solve(){
cin >> n >> k >> q;
L.resize(n);
for(int i = 0; i < n; i++){
cin >> L[i];
a[i].resize(L[i]);
for(int j = 0; j < L[i]; j++){
cin >> a[i][j];
}
}
memset(dp, -1, sizeof dp);
dp[0][1] = n; // 第0轮以1为结尾是可行,且方案很多。
for(int t = 1; t <= 100; t++){//第t轮
for(int i = 0; i < n; i++){//第i个人
int cnt = 0; // 能往后转移的长度
for(int x = 0; x < L[i]; x++){
int cc = a[i][x];
if(cnt-- > 0) {
if(dp[t][cc] == -1)
dp[t][cc] = i;
else if(dp[t][cc] != i)
dp[t][cc] = n;
}
if(dp[t - 1][cc] != i && dp[t - 1][cc] != -1)
cnt = k - 1;
}
}
}
while(q--){
cin >> r >> c;
if(dp[r][c] >= 0) puts("1");
else puts("0");
}
}
int main(){
int T;
cin >> T;
while(T--)
solve();
return 0;
}