2021“MINIEYE杯”中国大学生算法设计超级联赛(2)部分题解
I love cube
-
题意
给你一个 n − 1 n-1 n−1变成的立方体,需要你计算在立方体内部有多少个等边三角形,其中需要满足每条边必须平行于坐标轴,每个点的坐标必须是整数。 -
解题思路
当看到 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:8∗23+83:8∗33+8∗23+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 c⊕a≤b。 -
解题思路
由于查询次数过多,且涉及区间查询,必然会有区间的交集,导致了大量的重复工作。所以我们可以作离线查询,先将这 q q q次询问存储下来按右端点分组,然后,由于是区间查询,我们可以利用树状数组前缀和实现 c a l ( r ) − c a l ( l − 1 ) cal(r)-cal(l-1) cal(r)−cal(l−1),那么为了利用前面的查询,所以我们可以利用 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} 2len−1。 -
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[i−1][k−j][l−x]+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&j≥k)计算 ∑ k = 0 n − 1 C k \sum^{n-1}_{k=0}C_k ∑k=0n−1Ck -
解题思路
感觉我说的不是很清楚,这里引用大佬的思路:
-
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】