西工大冬季校赛题解

比赛链接

题解链接

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;
}

给出证明:

\[x_1 = a,x_2 = b,x_3 = \frac{1+b}{a},x_4 = \frac{1 + \frac{1+b}{a}}{b} = \frac{a+b+1}{ab},x_5=\frac{1+\frac{a+b+1}{ab}}{\frac{1+b}{a}}=\frac{a+1}{b} \]

然后就可以得到:

\[x_6=\frac{1+\frac{a+1}{b}}{\frac{a+b+1}{ab}}=a,x_7=\frac{1+a}{\frac{a+1}{b}}=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\) 位的值。(\(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:

  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;
}

其他题目

后续再更

posted @ 2021-01-16 19:51  cyhforlight  阅读(127)  评论(0编辑  收藏  举报