5 2025 04 30 模拟赛题解
成绩表
挂了六十多
![[12 题解/photo/{B3D2BB57-4F5A-4A38-8BBB-2FDB70C38240}.png]]
做题情况 & 赛时心态
- T1:题面很恶心,赛时没有做这题,0pts
- T2:一个dp优化题目,用前缀和优化,赛时没想出来怎么优化,66pts
- T3:不算太大的模拟,我用string打了部分分,但因为没有读入k=1时的数据,导致挂了42pts
- T4:比赛前刚做的原题,但是没有A,原因是赛前做题的时侯没有理解好这道题,细节没有处理好
赛时压力不大,因为T4原,但T1题面很恶心,所以直接放了,按理来说有时间还应该再琢磨一下,说不定能打一些分数,其实这次T3的暴力打的挺好,赛后把读入加上,就是42pts,下一次一定不能犯这么唐的错。
题面及题解
T1
题面
有 \(n\) 个城市,编号为 \(0\sim n - 1\) ,小乐要去旅行,从 \(0\) 城市出发,终点为 \(n-1\) 节点,编号越大的城市距离 \(n-1\) 城市越近,每个城市有 \(h\) 条到达其他城市的路 \((i, j,k)\) ,表示有一条从 \(i\to j\,\) 的路,到达\(j\)城市后至少需要停留 \(k\) 天(也就是说,小乐可以在走这条路后在 \(j\) 城市停留 \(k+1,\,k+2\dots\) 天),但不可以在\(0\)城市停留,第 \(i+1\)天停留城市的编号严格大于第\(i\)天停留的城市(忽略重点 \(j\) 编号小于等于\(i\)的路 \(i, j, k\))
分别求在 \(1\sim m\) 天离开 \(n-1\) 城市的方案数,若方案数超过 \(5\times 10^{8}\) ,只需输出 \(5\times 10^{8}+1\)即可
\(n\leq 10^{4},\ m\leq 400,\ h\leq 100\)
题解
根据题意,我们需要求方案数,而后一个节点的方案数一定是由另一个编号更小的节点转移过来,所以我们可以进行DP
设 \(f[i][j]\) 表示在第 \(j\) 天离开 \(i\) 节点的方案数,边界\(f[0][1]=1\)
那么转移方程也不难得到
另外,我们还要注意一个不太显眼的条件,到达\(j\)城市后至少需要停留 \(k\) 天,这个条件意味着,我们可以在这个城市多待几天,因此,有下面的转移方程
从小到大枚举每个节点 \(O(n)\)
枚举每个节点的出边 \(O(h)\)
枚举每个可能的\(k\), \(O(m)\)
总时间复杂度 \(O(nmh)\), \(4\times 10^{8}\) 勉强能过,但原题数据很水,才\(82ms\)
code
typedef long long ll;
const int N = 1e6 + 10;
const int inf = 5e8;
int n, m, hh;
int h[N], ver[N], ne[N], e[N], tot;
ll f[10050][410];
void add (int x, int y, int z) {
ver[++tot] = y;
ne[tot] = h[x];
e[tot] = z;
h[x] = tot;
}
int main () {
scanf("%d%d%d", &n, &m, &hh);
for (int i = 0; i < n - 1; i++) {
for (int j = 1; j <= hh; j++) {
int y, z;
scanf("%d%d", &y, &z);
if (y > i) add (i, y, z);
}
}
f[0][1] = 1;
//f[i][j]表示在第j天离开i城的方案数
for (int i = 0; i < n - 1; i++) {
//只要i不是起点,那么可以停留
//也就是后一天离开的方案数加上前一天离开的方案数
if (i != 0) {
for (int j = 1; j <= m; j++) {
f[i][j] += f[i][j - 1];
if (f[i][j] > inf) f[i][j] = inf + 1;
}
}
for (int j = h[i]; j; j = ne[j]) {
int y = ver[j];
int z = e[j];
for (int k = z + 1; k <= m; k++) {
f[y][k] += f[i][k - z];
if (f[y][k] > inf) f[y][k] = inf + 1;
}
}
}
for (int i = 1; i <= m; i++) {
f[n - 1][i] += f[n - 1][i - 1];
if (f[n - 1][i] > inf)
f[n - 1][i] = inf + 1;
}
for (int i = 1; i <= m; i++) {
printf("%lld ", f[n - 1][i]);
}
return 0;
}
T2
题面
给定 \(n,\ k\) ,求满足下列条件的不同的序列\(a\)的数量
- 若 \(a_{i}>a_{i+1}\) ,则 \(a_{i+1}<a_{i+2}\)
- 若 \(a_{i}<a_{i+1}\) ,则 \(a_{i+1}>a_{i+2}\)
- \(a_{1}=k\)
- \(\sum_{i=1}^{m} a_{i}=n\),\(m\)的值任意
\(1\leq k\leq n\leq 5000\)
题解
这题赛时想了一个区间dp的解法,mtx说这个解法太抽象,但我觉得还可以,当然,这种解法我还不太会优化,只能拿66pts
先说题解中的解法,能够进行前缀和优化到\(O(n^{2})\)
这题用dp应该是比较容易想出来的
定义 \(f[i][j][0/1]\) 表示前面(包括当前)已经有的数的和为 \(i\) ,当前数为 \(j\) 下一个数应该比当前数 小/大。
状态转移方程
code
const int N = 5e3 + 10, mod = 1e9 + 7;
typedef long long ll;
int n, k;
int f[N][N][2];
int sum[N][N][2];
int main () {
scanf("%d%d", &n, &k);
//特判
if (n == k) {
printf("1\n");
return 0;
}
//在总价值为k的情况下当前元素为k的情况初始化为1
f[k][k][0] = f[k][k][1] = 1;
//记录总价值为k的前缀和
for (int i = k; i <= n; i++) {
sum[k][i][0] = sum[k][i - 1][0] + f[k][i][0];
sum[k][i][1] = sum[k][i - 1][1] + f[k][i][1];
}
for (int i = k + 1; i <= n; i++) {
for (int j = 1; j < i; j++) {
f[i][j][0] = sum[i - j][j - 1][1] % mod;
f[i][j][1] = (sum[i - j][n][0] - sum[i - j][j][0] + mod) % mod;
}
//记录总价值为i的前缀和
for (int j = 1; j <= n; j++) {
sum[i][j][0] = (sum[i][j - 1][0] + f[i][j][0]) % mod;
sum[i][j][1] = (sum[i][j - 1][1] + f[i][j][1]) % mod;
}
}
printf("%d\n", (sum[n][n][0] + sum[n][n][1]) % mod);
return 0;
}
DLC
我自己在赛时想出的思路
首先定义一个状态 \(f[i][j][0 / 1]\) 表示当前元素为 \(i\) ,还剩 \(j\) (包括当前元素)的价值没有用, 下一个元素更 小/大
我们把每个状态看作一个抽象的区间
手模样例发现每个大的区间都可以由一个更小的区间转移过来,于是用\(f[x][y][0]\)来证明一下
\(f[x][y][1]\) 也是一样的这里不再证明
所以我们可以枚举区间长度,枚举左端点,枚举下一个数 \(k\) ,从而实现小区间到大区间的转移,时间复杂度\(O(n^{3})\)
DLC code
const int N = 5e3 + 10, mod = 1e9 + 7;
typedef long long ll;
int n, kk;
ll f[N][N][2];
int main () {
scanf("%d%d", &n, &kk);
//注意特判
if (n == kk) {
printf("1\n");
return 0;
}
//当前元素与剩余价值相等,选择当前元素后就没有其它可能了,所以初始化为1
for (int i = 1; i <= n; i++) {
f[i][i][0] = f[i][i][1] = 1;
}
//枚举区间长度
for (int len = 2; len <= n - kk + 2; len++) {
//枚举左端点
for (int i = 1; i + len - 1 <= n; i++) {
//右端点
int j = i + len - 1;
//枚举下一个要选的元素
for (int k = min(i - 1, j - i); k >= 1; k--) {
f[i][j][0] += f[k][j - i][1];
f[i][j][0] %= mod;
}
for (int k = i + 1; k <= min(j - i, n); k++) {
f[i][j][1] += f[k][j - i][0];
f[i][j][1] %= mod;
}
}
}
printf("%lld\n", (ll)(f[kk][n][0] + f[kk][n][1] + mod) % mod);
return 0;
}
再附赠一个递归版本的,66pts
const int N = 5e3 + 10, mod = 1e9 + 7;
typedef long long ll;
int n, kk;
ll f[N][N][2];
ll solve(int x, int y, int tag) {
if (x == y) return 1;
if (x > y) return 0;
if (f[x][y][tag]) return f[x][y][tag];
ll sum = 0;
if (tag == 0) {
for (int i = min(x - 1, y - x); i >= 1; i--) {
sum += solve(i, y - x, 1);
sum %= mod;
}
f[x][y][0] = sum;
} else {
for (int i = x + 1; i <= y - x; i++) {
sum += solve(i, y - x, 0);
sum %= mod;
}
f[x][y][1] = sum;
}
return sum;
}
int main () {
scanf("%d%d", &n, &kk);
if(n == kk) {
printf("1\n");
return 0;
}
printf("%lld\n", (solve(kk, n, 0) + solve(kk, n, 1) + mod) % mod);
return 0;
}
T3
题面
有一种程序,可以执行下面的三种语句
REP 3
PRINT 1
REP 2
PRINT 2
END
END
该程序输出序列 \([1,2,2,1,2,2,1,2,2]\)。
现在给定输出的序列 \(a\) ,序列长度 \(n\) ,使用PRINT
语句的限制数量 \(k\) ,求是否能够通过编写类似上面的程序,输出序列 \(a\)
多组数据
\(1\leq T\leq 100,\ 1\leq n\leq 100\)
\(1\leq a_{i}\leq k\leq 3\)
题解
这题的部分分 \(k=1\) 和 \(k=2\) 比较好打,但 \(k=3\) 的情况比较棘手,后来看到题解中的一句话,我才恍然大悟, \(k=3\) 情况可以由 \(k=1\) 和 \(k=2\) 的情况组合而成,那我打的部分分修改一下不就可以了吗
我们枚举 \(i\) 为 \(k=2\) 和 \(k=1\) 的分界,那么判断一下两边是否满足 \(k=2\) 和 \(k = 1\) 的条件即可
下面说一下如何判断一个序列是否满足 \(k=2\) 的条件
首先,我们知道有两个PRINT
,那么在不考虑大循环的情况下的序列应该是形如 \(111222\) 或 \(111111\)。这样的话,我们直接扫一遍,看是否有大于1个 \(i\) 满足 \(a_{i}\neq a_{i+1}\)即可
那么现在的问题转化为如何才能不考虑最外层的大循环?
考虑上面所列举的子串为原串的一个循环节,那么我们只需枚举 \(n\) 的因子,然后判断开头因子长度的字串是否符合我们上面所说的条件即可。
时间复杂度 \(O(n^{3})\) 具体见代码
这种方法不需要什么脑子,不过你得有些耐心
code
const int N = 110;
int T, n, k;
int a[N];
bool check (string t) {
int cnt = 0;
for (int i = 0; i < (int)t.size() - 1; i++) {
if (t[i] != t[i + 1]) {
cnt++;
}
}
if(cnt > 1) return false;
return true;
}
//判断是否满足k=1的条件
bool check1 (string t) {
int cnt = 0;
for (int i = 0; i < (int)t.size() - 1; i++) {
if (t[i] != t[i + 1]) {
cnt++;
break;
}
}
if(cnt) return false;
return true;
}
//判断是否满足k=2的条件
bool check2 (string ts, int n) {
string s;
vector<int> d;
s = " " + ts;
for (int i = 1; i * i <= n; i++) {
if (n % i == 0) {
if (n / i == i) {
d.push_back(i);
continue;
}
d.push_back(i);
d.push_back(n / i);
}
}
bool ok = 0;
for (int i = 0; i < (int)d.size(); i++) {
string t = s.substr(1, d[i]);
string tt = t;
for (int j = 2; j <= n / d[i]; j++) {
tt += t;
}
tt = " " + tt;
if (tt == s && check(t)) {
ok = 1;
break;
}
}
if (ok) return true;
else return false;
}
int main () {
scanf("%d", &T);
while (T--) {
scanf("%d%d", &n, &k);
if (k == 1) {
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
printf("YES\n");
} else if(k == 2) {
string s;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
s += (a[i] + '0');
}
if (check2(s, n)) {
printf("YES\n");
} else {
printf("NO\n");
}
} else {
string s;
s = " " + s;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
s += (a[i] + '0');
}
vector<int> d;
int mn = n;
for (int i = 1; i * i <= n; i++) {
if (n % i == 0) {
if (n / i == i) {
d.push_back(i);
continue;
}
d.push_back(i);
d.push_back(n / i);
}
}
for (int i = 0; i < (int)d.size(); i++) {
string t = s.substr(1, d[i]);
string tt = t;
for (int j = 2; j <= n / d[i]; j++) {
tt += t;
}
tt = " " + tt;
if (tt == s) {
mn = min(mn, d[i]);
}
}
s = " " + s.substr(1, mn);
bool fn = 0;
for (int i = 1; i <= mn; i++) {
string t1 = s.substr(1, i);
string t2 = s.substr(i + 1, mn - i);
if ((check1(t1) && check2(t2, mn - i)) || (check1(t2) && check2(t1, i))) {
fn = 1;
break;
}
}
if(fn) {
printf("YES\n");
} else {
printf("NO\n");
}
}
}
return 0;
}
T4
这题写过题解,考试时没有初始化,挂了20pts
[[12 题解/6 软件安装 题解|软件安装 题解]]