2021“MINIEYE杯”中国大学生算法设计超级联赛(2)部分题解

I love cube

  • 题意
    给你一个 n − 1 n-1 n1变成的立方体,需要你计算在立方体内部有多少个等边三角形,其中需要满足每条边必须平行于坐标轴,每个点的坐标必须是整数。

  • 解题思路
    当看到 n n n的取值 [ 0 , 1 e 18 ] [0,1e18] [0,1e18]的时候就应该要注意到了,这种应该是推导题,不难发现,当立方体边长为 1 1 1的时候,我们可以画出符合等边三角行的数目:(这里只列举一种画法)
    在这里插入图片描述

8 8 8种,那么我们再看一下,当边长为 2 2 2的时候,我们发现,其可以看成 8 8 8个变成为 1 1 1的小正方形,则有 8 × 8 8\times8 8×8个,而再看它自身也是符合要求的,则有 8 8 8个,而其他是不可能得,因为不等边,也不是整数坐标点。
往下推导得:

1 : 8 2 : 8 ∗ 2 3 + 8 3 : 8 ∗ 3 3 + 8 ∗ 2 3 + 8 4 : . . . . . n : 8 ∗ ( 1 + 2 3 + 3 3 + . . . + n 3 ) 1:8\\ 2:8*2^3+8\\ 3:8 *3^3+8*2^3+8\\ 4:.....\\ n:8*(1+2^3+3^3+...+n^3) 1:82:823+83:833+823+84:.....n:8(1+23+33+...+n3)

立方和的计算公式为 n 2 ( n + 1 ) 2 4 \frac{n^2(n+1)^2}{4} 4n2(n+1)2。故此题得解。

  • AC代码
/**
  *@filename:1001
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-22 12:01
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 100000 + 5;
const int P = 1e9+7;

int t;
ll n;
ll quick_pow(ll n, ll q){
    ll ans = 1;
    while(q){
        if(q & 1)ans = ans * n % P;
        n = n * n % P;
        q >>= 1;
    }
    return ans;
}
void solve(){
    cout << quick_pow((n - 1) % P,2) * quick_pow(n % P,2) % P * 2 % P << endl;   
}
int main(){
    cin >> t;
    while(t -- ){
        cin >> n;
        solve();
    }
    return 0;
}

I love counting

  • 题意
    给你一个序列 a a a,其中每个元素的权重为 c c c,有 q q q次查询,问 [ l , r ] [l,r] [l,r]这段区间有多少元素符合 c ⊕ a ≤ b c\oplus a\leq b cab

  • 解题思路
    由于查询次数过多,且涉及区间查询,必然会有区间的交集,导致了大量的重复工作。所以我们可以作离线查询,先将这 q q q次询问存储下来按右端点分组,然后,由于是区间查询,我们可以利用树状数组前缀和实现 c a l ( r ) − c a l ( l − 1 ) cal(r)-cal(l-1) cal(r)cal(l1),那么为了利用前面的查询,所以我们可以利用 t i r e tire tire树来维护答案。具体看代码。

  • AC代码

/**
  *@filename:1004
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-22 13:37
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 1e5 + 5;
const int P = 1e9+7;

int n,x,q;
int trie[N * 400][2],tot,sum[N * 400];//字典树。sum[i]表示当前的节点总和。
int preIdx[N];//preIdx[i]表示i值上一次出现的下标。
int rt[N];//树状数组。
int a[N];
struct Node{
    int l,a,b,id;
};
vector<Node> v[N];//离线询问。常用技巧,能大幅提高效率。
int ans[N];//存储答案。
void insert(int &root,int v,int x){
    if(!root){
        root = ++ tot;
    }
    int u = root;
    for(int i = 20; i >= 0; -- i){
        int p = x >> i & 1;
        if(!trie[u][p]){
            //说明该节点不存在。我们创建该节点。
            trie[u][p] = ++ tot;
        }
        u = trie[u][p];
        sum[u] += v;
    }
}
int query(int root,int a,int b){
    if(!root)return 0;
    int res = 0,u = root;
    for(int i = 20; i >= 0; -- i){
        int p1 = a >> i & 1 , p2 = b >> i & 1;
        if(p2){
            if(p1){
                res += sum[trie[u][1]];
                u = trie[u][0];
            }
            else{
                res += sum[trie[u][0]];
                u = trie[u][1];
            }
        }
        else{
            if(p1){
                u = trie[u][1];
            }
            else{
                u = trie[u][0];
            }
        }
        if(!u)break;
    }
    return res + sum[u];
}
int lowbit(int x){
    return x & - x;
}
void add(int u,int v,int x){
    for(int i = u; i <= n; i += lowbit(i)){
        insert(rt[i],v,x);
    }
}
int cal(int u,int a,int b){
    int res = 0;
    for(int i = u; i > 0; i -= lowbit(i)){
        res += query(rt[i],a,b);
    }
    return res;
}
void solve(){
    for(int i = 1; i <= n; ++ i){
        if(preIdx[a[i]]){
            add(preIdx[a[i]], -1,a[i]);
        }
        preIdx[a[i]] = i;
        add(i,1,a[i]);
        for(auto iter : v[i]){
            int a = iter.a, b = iter.b;
            ans[iter.id] = cal(i,a,b) - cal(iter.l - 1,a,b);
        }
    }
    for(int i = 1; i <= q; ++ i){
        cout << ans[i] << endl;
    }
}
int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; ++ i){
        scanf("%d", &a[i]);
    }
    scanf("%d", &q);
    for(int i = 1; i <= q; ++ i){
        int l, r, a, b;
        scanf("%d%d%d%d", &l, &r, &a, &b);
        v[r].push_back({l,a,b,i});
    }
    solve();
    return 0;
}

I love string

  • 题意
    给你一个操作顺序,其可以构造字符串,其中每一个操作,都可以将当前要操作的字符放在构建的字符串前面或者后面。问你需要使得构造出的字符串字典序最小的方案数。

  • 解题思路
    不难发现,第一个操作只能有一种方法,而其决定了字典序的开头,那么接下来的如果和其不相同,那么放前面或者放后面的字典序是一定不同的,所以这只有一种方法,而如果一直都是相同的,那么我们自然可得,放前面或者放后面都可以。所以这个问题实际上就是求最长的相同字符前缀长度,那么答案就是 2 l e n − 1 2^{len-1} 2len1

  • AC代码

/**
  *@filename:1005
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-22 12:09
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 100000 + 5;
const int P = 1e9+7;

int t,n;
string s;
ll quick_pow(ll n, ll q){
    ll ans = 1;
    while(q){
        if(q & 1)ans = ans * n % P;
        n = n * n % P;
        q >>= 1;
    }
    return ans;
}
void solve(){
    //多少种操作方式能够得到这最大分数。
    ll cnt = 0;
    char pre = s[0];
    for(int i = 0; i < n; ++ i){
        if(pre == s[i])cnt++;
        else break;
    }
    cout << quick_pow(2,cnt - 1) << endl;
}
int main(){
    cin >> t;
    while(t -- ){
        cin >> n >> s;
        solve();
    }
    return 0;
}

I love exam

  • 题意
    n n n门课程,你还有 t t t天时间复习,其中有 m m m个复习资料,其中可以在 y y y天内提高 x x x分。你最多可以挂 p p p门课,问你能获得的最大分数。

  • 解题思路
    我们对这 m m m个复习资料按课程分组,不难发现,针对每一门课单独来看,这就是一个背包问题,即选择该复习资料复习还是不选择。那么我们可以用 f [ i ] f[i] f[i]来表示前 i i i天最多可以得多少分。利用 01 01 01背包动态规划处理完 f f f数组后就可以开展每门课程得合并了,即我们可以用 d p [ i ] [ k ] [ l ] dp[i][k][l] dp[i][k][l]则表示前i门课程,复习了k天,挂了l门的最大分数,那么状态转移方程自然可以列写: d p [ i ] [ k ] [ l ] = m a x ( d p [ i − 1 ] [ k − j ] [ l − x ] + f [ j ] , d p [ i ] [ k ] [ l ] ) dp[i][k][l] = max(dp[i - 1][k - j][l - x] + f[j],dp[i][k][l]) dp[i][k][l]=max(dp[i1][kj][lx]+f[j],dp[i][k][l]),其中 x x x代表当前课会不会挂的状态消息。故按此进行动态规划即可。

  • AC代码

/**
  *@filename:1008
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-22 13:57
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 15000 + 10;
const int P = 1e9+7;
const int INF = 0x3f3f3f3f;

int q,t,n,m,p;//t为最大复习天数,m为复习资料数量。p为不及格课程上限。
//首先用01背包求出每门课花费k天可以最多得到多少分。
int f[550];//f[i]表示前i天最多可以得到多少分。
int dp[55][550][550];//dp[i][k][l]则表示前i门课程,复习了k天,挂了l门的最大分数。
//dp[i][k][l] = max(dp[i - 1][k - j][l - x] + f[j],dp[i][k][l])
map<string,int> mp;
struct node{
    int x,y;//x为提高的分数,y为所需要学习的天数。
};
vector<node> item[55];
void solve(){
    fill(dp[0][0],dp[0][0] + 55 * 550 * 550,-INF);
    dp[0][0][0] = 0;//初始状态。
    for(int i = 1; i <= n; ++ i){
        //求第i门课程花费k天可以最多得到多少分。
        fill(f,f + 550,-INF);
        f[0] = 0;
        for(int j = 0; j < item[i].size(); ++ j){
            for(int k = t; k >= item[i][j].y; -- k){
                f[k] = max(f[k],f[k - item[i][j].y] + item[i][j].x);
            }
        }
        for(int j = 1; j <= t; ++ j){
            for(int k = j; k <= t; ++ k){
                for(int l = 0; l <= p; ++ l){
                    if(f[j] < 0)continue;//说明没有被初始化到。
                    int flag = 0;
                    if(f[j] < 60)flag = 1;//说明这个天数学习的第i个课程没有及格。
                    if(f[j] > 100)f[j] = 100;
                    if(l >= flag){ 
                        dp[i][k][l] = max(dp[i - 1][k - j][l - flag] + f[j],dp[i][k][l]);
                    }
                }
            }
        }
    }
    int maxx = -INF;
    for(int k = 0; k <= t; ++ k){
        for(int l = 0; l <= p; ++ l){
            maxx = max(dp[n][k][l],maxx);
        }
    }
    if(maxx < 0)maxx = -1;
    cout << maxx << endl;
    for(int i = 1; i <= n; ++ i){
        item[i].clear();
    }
    mp.clear();
}
int main(){
    cin >> q;
    while(q -- ){
        cin >> n;
        string s;
        int x,y;
        for(int i = 1; i <= n; ++ i){
            cin >> s;
            mp[s] = i;
        }
        cin >> m;
        for(int i = 1; i <= m; ++ i){
            cin >> s >> x >> y;
            item[mp[s]].push_back({x,y});
        }
        cin >> t >> p;
        solve();
    }
    return 0;
}

I love max and multiply

  • 题意
    给定两个大小为n的数组A和B。定义一个数组C,其中 C k = m a x { A i × B j } , ( i & j ≥ k ) C_k=max\{A_i\times B_j\},(i\&j\ge k) Ck=max{Ai×Bj}(i&jk)计算 ∑ k = 0 n − 1 C k \sum^{n-1}_{k=0}C_k k=0n1Ck

  • 解题思路
    感觉我说的不是很清楚,这里引用大佬的思路:
    在这里插入图片描述

  • AC代码

/**
  *@filename:1011
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-30 10:28
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 1e6 + 5;
const int P = 998244353;
const ll INF = 1e9 + 10;

int t,n;
ll a[N],b[N];//存储最小的值
ll A[N],B[N];//存储最大的值。
void solve(){
    int m = 1;
    while(m < n){
        m <<= 1;
    }
    for(int i = n; i < m; ++ i){
        A[i] = B[i] = -INF;
        a[i] = b[i] = INF;
    }
    for(int i = 1; i < m; i <<= 1){
        for(int k = 0; k < n; ++ k){
            if(!(i & k)){
                //说明k的第i位不是0.
                A[k] = max(A[k], A[i ^ k]);
                B[k] = max(B[k], B[i ^ k]);
                a[k] = min(a[k], a[i ^ k]);
                b[k] = min(b[k], b[i ^ k]);
            }
        }
    }
    ll maxx = -1e18,ans = 0;
    for(int i = n - 1; i >= 0; -- i){
        maxx = max(maxx,A[i] * B[i]);
        maxx = max(maxx,A[i] * b[i]);
        maxx = max(maxx,a[i] * B[i]);
        maxx = max(maxx,a[i] * b[i]);
        ans = (ans + maxx % P) % P; 
    } 
    ans = (ans + P) % P;
    printf("%lld\n", ans);
}
int main(){
    scanf("%d", &t);
    while(t -- ){
        scanf("%d", &n);
        for(int i = 0; i < n; ++ i){
            scanf("%lld", &a[i]);
            A[i] = a[i];
        }
        for(int i = 0; i < n; ++ i){
            scanf("%lld", &b[i]);
            B[i] = b[i];
        }
        solve();
    }
    return 0;
}

I love 114514

  • 题意
    给你一个字符串,判断该字符串的字串是不是有114514

  • 解题思路
    直接匹配即可。

  • AC代码

/**
  *@filename:1012
  *@author: pursuit
  *@csdn:unique_pursuit
  *@email: 2825841950@qq.com
  *@created: 2021-07-22 12:01
**/
#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
const int N = 100000 + 5;
const int P = 1e9+7;

int t;
string s;
void solve(){
    string str = "114514";
    int l = 0;
    bool flag = false;
    for(int i = 0; i < s.size(); ++ i){
        if(s[i] == str[l]){
            l ++;
            if(l == str.size()){
                flag = true;
                break;
            }
        }
    }
    if(flag){
        cout << "AAAAAA" << endl;
    }
    else{
        cout << "Abuchulaile" << endl;
    }
}
int main(){
    cin >> t;
    while(t -- ){
        cin >> s;
        solve();
    }
    return 0;
}
posted @   unique_pursuit  阅读(50)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示