西工大冬季校赛题解

比赛链接

题解链接

E题 欢迎来到西北工业大学冬季赛

输出西工大缩写 NPU 即可。(我第一遍还以为要输出 hello,然后就GG了)

print("NPU")

H题 松果痰抖闪电鞭

一个递推式,理论上照着递推就行了。

但是0<α,β,k1×108 的数据规模,显然不太方便线性来写,需要优化。

滚动数组

这个可以将空间复杂度压到O(1),但是时间复杂度依然很高,理论上如果评测机快的话还是可以水过去的。(不知道为啥我WA了)

找规律

在调试上面那个WA的程序时,我发现这个递推数组似乎有一定的规律,然后看群里面有人发,就知道了:数组周期为5。

那么我们就可以将时空复杂度双双压到O(1),轻松AC。

#include<bits/stdc++.h> using namespace std; int a, b, k; double ans[10]; int main() { scanf("%d%d%d", &a, &b, &k); ans[1] = a, ans[2] = b; for (int i = 3; i <= 5; ++i) ans[i] = (1 + ans[i - 1]) / ans[i - 2]; k %= 5; if(k == 0) k += 5; printf("%.8lf", ans[k]); return 0; }

给出证明:

x1=a,x2=b,x3=1+ba,x4=1+1+bab=a+b+1ab,x5=1+a+b+1ab1+ba=a+1b

然后就可以得到:

x6=1+a+1ba+b+1ab=a,x7=1+aa+1b=b

得到周期性证明

J题 不讲武德

这个混元形意排序是这样的:将数列分成两半,分别分治,然后如果前一半数列的第一项比后一半数列的大的话,就将这两个数列置换。

很显然,这个排序法是错误的,但是构造数据属实让我想了好一会。考虑到分治的性质,我便从这里入手:

构造方式:数列一分为二,左右均严格单调递增,但是数列本身不是有序的,且左半段的最小值小于右半段最小值,那么:

如果分治过程中破坏了左右两个子数列的顺序结构,因为他们本来就是有序的,那么分治完成后,他们就是不有序的,那么整个数列最后必然不会有序。

如果没有破坏,那么两个分治是无效的,到达合并环节,根据构造性质,函数不会对我的数列进行修改,程序结束,此时我的序列是无序的。

综上,这种构造方式构造出来的数列可以成功 Hack 掉程序,此题结束。

#include<bits/stdc++.h> using namespace std; int n, N, ans[5010]; int main() { scanf("%d", &N); if (N % 2 == 0) { n = N / 2; for (int i = 1; i <= n; ++i) ans[i] = 2 * i - 1, ans[n + i] = 2 * i; } else { n = (N - 1) / 2; int mid = n + 1; for (int i = 1; i <= mid; ++i) ans[i] = 2 * i - 1; for(int i = 1; i <= n; ++i) ans[mid + i] = 2 * i; } for (int i = 1; i < N; ++i) printf("%d ", ans[i]); printf("%d", ans[N]); return 0; }

F题 反复读密码锁

这个题目很像 NOIP2019 D1T1 【格雷码】,在一个构造的规律01序列寻找第 n 位的值。(n1018)

我们发现,可以构造 n=2x+p(1p<2x),那么我们可以将长2x+1的序列的第n位转化成2x长的反序列的第p位。

根据01序列的有序性,我们可以简单写一个分治函数,记录状态,不断将其压下去,直到可以直接求解。

对了,题目说的是下一个,所以我们最好把 n 加1之后再计算。

#include<bits/stdc++.h> using namespace std; long long m[70]; int calc(long long n, int x, int f) { printf("n=%lld x=%d f=%d\n", n, x, f); string a1 = "01101001", a2 = "10010110"; if (n <= 8) { if (f) return a2[n - 1] - '0'; else return a1[n - 1] - '0'; } if (n <= m[x - 1]) return calc(n, x - 1, f); else return calc(n - m[x - 1], x - 1, 1 - f); } int main() { m[0] = 1; for (int i = 1; i <= 62; ++i) m[i] = m[i - 1] << 1; int T, ans; cin>>T; while (T--) { long long n; cin>>n; n += 1; int x = -1; for (int i = 1; i <= 62; ++i) if (n > m[i - 1] && n <= m[i]) { x = i; break; } if (x == -1) x = 63; ans = calc(n, x, 0); cout<<ans<<endl; } return 0; }

I题 ACM基地招新大会

我们简单观察可以得出结论:如果一个人前面比他强的人少于两个,那么他就可以到第一名,否则就只能到第二个比他强的人的后面。

我们可以用O(n2)来写,但是数据规模达到了105,显然会T,我们必须优化。

这个数列不用修改,所以我们可以使用一个ST表来记录一个区间的最大值(以及他的位置),但是我们需要找的并非最大值,而是所有比某个人强的人之中最靠近的一个,例如 3 1 4 6 5 2,区间[1,5]中最大值是6,但是最靠近2的却是5。

我们可以这么写:记录下最大值的下标p,然后再计算p到数字左边的范围的最大值,如果有的话就继续更新p的值,以此类推,这样就可以保证题目的正确性。

对于随机数据,这个复杂度还是OK的,但是对于特殊构造的数据,例如 6 5 4 3 2 1,p的更新速度直接退化到了O(n),还不如直接暴力来的妥当。

我们又注意到,这个更新顺序具有单调性,所以我们可以直接进行二分,将复杂度成功优化到了O(nlogn),能够成功AC。

#include<bits/stdc++.h> using namespace std; const int N = 100010; int n, a[N], s[N]; struct node{ int val, p; bool operator <(const node rhs) const { if (val != rhs.val) return val < rhs.val; return p < rhs.p; } }; node aa[N]; //ST node ST[N][25]; void init(){ for(int i = 1; i <= n; ++i) aa[i] = (node){a[i], i}; for(int i=1;i<=n;++i) ST[i][0]=aa[i]; for(int k=1;k<=20;++k){ for(int i=1;i+(1<<k)-1<=n;++i){ ST[i][k]=max(ST[i][k-1],ST[i+(1<<(k-1))][k-1]); } } } inline node query(int l,int r){ int k=log2(r-l+1); return max(ST[l][k],ST[r-(1<<k)+1][k]); } int ask(int L, int R, int val) { if (L > R)return -1; node aaa = query(L, R); if (aaa.val <= val) return -1; int l = aaa.p, r = R; while (l < r) { int mid = (l + r + 1) >> 1; aaa = query(mid, r); if (aaa.val <= val) r = mid - 1; else l = mid; } return l; } int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", &a[i]); init(); s[1] = s[2] = 1; for (int i = 3; i <= n; ++i) { int p1 = ask(1, i - 1, a[i]), p2; if (p1 == -1) s[i] = 1; else { p2 = ask(1, p1 - 1, a[i]); if(p2 == -1) s[i] = 1; else s[i] = p2 + 1; } } for (int i = 1; i <= n; ++i) printf("%d\n", s[i]); return 0; }

D题 玩具

这题的正答率其实挺高,但是我做的惨不忍睹,各种想法都出来了......

实际上这题有一个结论:若存在符合条件的区间,那么最长区间必然包含有整个数列的众数。

证明:

当数列有多个众数时,以整个数列作为区间即可

只有一个众数时,不妨记他为x,出现了n次。

如果不存在一个区间,符合条件且不包含x,那么可以得出结论:区间必然包含x

如果存在这样一个区间,那么我们可以尝试将这个区间进行扩展。显然,我们可以将整个区间扩展到这样一种情况:序列中含有mxm<n),且区间内还有一个或多个数的数量为m。(原因:x的数量从0开始线性增加,而且其总数量总是大于任意一个数字的数量,所以总是能够将他微调到和某一个数的数量相同的情形)。

证毕(证的并不是很毕)

无解的情况也很简单:所有数字全部相同

那么我们本质上只需要求出数列的众数即可

#include<bits/stdc++.h> using namespace std; const int N = 100010; int n, a[N], h[N]; int main() { scanf("%d", &n); for (int i = 1; i <= n; ++i) scanf("%d", &a[i]); sort(a + 1, a + n + 1); if (a[1] == a[n]) { puts("-1"); return 0; } for (int i = 1; i <= n; ++i) h[a[i]]++; int count = -1, ans; for (int i = 1; i <= n; ++i) if (count < h[i]) count = h[i], ans = i; printf("%d", ans); return 0; }

B题 运筹帷幄

数据规模这么小,直接爆搜就行了(枚举排列,DFS啥的都行,实现方式千奇百怪)。

第 6 类牌没有啥子用,纯粹凑数的,删掉就行。

英雄技能一回合只能用一次,反正只有一回合,那就当一张新牌用就行了。

然后就是喜闻乐见的调 bug 阶段了,反正我从下午四点半一直调到半夜,发现了不限于以下的若干 BUG:

  1. vis[i] = 1 写成vis[i] == 1
  2. continue 写成 break
  3. 把英雄技能消耗的能量打错了
  4. 忘了考虑没法打牌的情况
  5. 忘了拖到最下面,看看那个加伤害是什么情况
  6. 等等等等

这里给出代码吧,调的心累,WA 了 10 次才 AC:

#include<bits/stdc++.h> using namespace std; int ans = 0; int cnt, n, m, h; int attack = 0; bool isFrozen = false; int pai[20],vis[20]; void dfs(int depth, int have) { if (depth == cnt + 1) { ans = max(ans, have); return; } ans = max(ans, have); for (int i = 1; i <= cnt; ++i) { if (vis[i]) continue; vis[i] = 1; if (pai[i] == 1 && m >= 2) { if (!isFrozen) { isFrozen = true; m -= 2; dfs(depth + 1, have + 3 + attack); m += 2; isFrozen = false; } else { m -= 2; dfs(depth + 1, have + 3 + attack); m += 2; } } else if (pai[i] == 2 && m >= 1) { m -= 1; if (isFrozen) dfs(depth + 1, have + 4 + attack); else { isFrozen = true; dfs(depth + 1, have); isFrozen = false; } m += 1; } else if (pai[i] == 3 && m >= 4) { m -= 4; dfs(depth + 1, have + 6 + attack); m += 4; } else if (pai[i] == 4 && m >= 2) { m -= 2, attack += 1; dfs(depth + 1, have); m += 2, attack -= 1; } else if (pai[i] == 5 && m >= 4) { m -= 4, attack += 2; dfs(depth + 1, have); m += 4, attack -= 2; } else if(pai[i] == 7 && m >= 2) { m -= 2; dfs(depth + 1, have + 1); m += 2; } vis[i] = 0; } } int main() { cin>>n>>m>>h; for (int i = 1; i <= n; ++i) { string s; cin>>s; if(s == "Frostbolt") pai[++cnt] = 1; else if(s == "IceLance") pai[++cnt] = 2; else if(s == "Fireball") pai[++cnt] = 3; else if(s == "BloodmageThalnos") pai[++cnt] = 4; else if(s == "CosmicAnomaly") pai[++cnt] = 5; } pai[++cnt] = 7; dfs(0, 0); if (ans >= h)cout<<"Win"; else cout<<"Lose"<<endl<<ans; return 0; }

其他题目

后续再更


__EOF__

本文作者cyhforlight
本文链接https://www.cnblogs.com/cyhforlight/p/14287073.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   cyhforlight  阅读(134)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话
点击右上角即可分享
微信分享提示