西工大冬季校赛题解
E题 欢迎来到西北工业大学冬季赛
输出西工大缩写 NPU 即可。(我第一遍还以为要输出 hello,然后就GG了)
print("NPU")
H题 松果痰抖闪电鞭
一个递推式,理论上照着递推就行了。
但是\(0\lt \alpha,\beta,k \le 1\times 10^8\) 的数据规模,显然不太方便线性来写,需要优化。
滚动数组
这个可以将空间复杂度压到\(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;
}
给出证明:
然后就可以得到:
得到周期性证明
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\) 位的值。(\(n \leq 10^{18}\))
我们发现,可以构造 \(n = 2^x + p (1 \leq p < 2^x)\),那么我们可以将长\(2^{x+1}\)的序列的第\(n\)位转化成\(2^x\)长的反序列的第\(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(n^2)\)来写,但是数据规模达到了\(10^5\),显然会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(n \log n)\),能够成功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\)
如果存在这样一个区间,那么我们可以尝试将这个区间进行扩展。显然,我们可以将整个区间扩展到这样一种情况:序列中含有\(m\)个\(x\)(\(m < 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:
- vis[i] = 1 写成vis[i] == 1
- continue 写成 break
- 把英雄技能消耗的能量打错了
- 忘了考虑没法打牌的情况
- 忘了拖到最下面,看看那个加伤害是什么情况
- 等等等等
这里给出代码吧,调的心累,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;
}
其他题目
后续再更