2018年全国多校算法寒假训练营练习比赛(第一场)题解

时间:2020-11-24 19:01:20

A:贪心+暴力


链接:大吉大利,今晚吃鸡——枪械篇


思路:我用了一个map使配件种类对应一个最大威力的配件。然后暴力判断每把枪都装上最优的配件以后的威力,求一个极大值即可。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
int n, m, k;
double p[MAXN];//枪的威力
set<int> s[MAXN];//配件种类
map<int, double> M;//最优配件
int main()
{
while (~scanf("%d%d", &n, &m))
{
M.clear();
for (int i = 0; i < n; i++)
{
s[i].clear();
scanf("%lf%d", &p[i], &k);
while (k--)
{
int t; scanf("%d", &t);
s[i].insert(t);
}
}
while (m--)
{
int q; double b;
scanf ("%d%lf", &q, &b);
if (M.count(q))
{
M[q] = max(M[q], b);//保存q配件的最大威力
}
else M[q] = b;
}
double MAX = -1.0;
for (int i = 0; i < n; i++)
{
double t = 1.0;
for (auto j: s[i])
{
t = t + M[j];
}
MAX = max(MAX, t * p[i]);
}
printf("%.4f\n", MAX);
}
return 0;
}
/*
3 6
120 3 1 2 3
100 4 1 2 3 4
110 3 2 3 4
1 0.12
2 0.23
2 0.26
4 0.57
3 0.35
5 0.41
*/

B:vector模拟


链接:最强的决斗者一切都是必然的!



思路:直接模拟就好了,题意比较难读,主要需要看样例。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
int n;
struct Node
{
int s, t, x;
}a[MAXN];
int main()
{
while (~scanf("%d", &n))
{
for (int i = 1; i <= n; i++)
{
scanf("%d%d", &a[i].s, &a[i].t);
if (a[i].t == 1 || a[i].t == 2) scanf("%d", &a[i].x);
}
long long ans = 0;//伤害
vector<Node> v;//连锁序列,下标表示连锁数
for (int i = 1; i <= n; i++)
{
if (a[i].s >= a[i - 1].s)//连锁
{
v.push_back(a[i]);//放入vector
}
else//连锁结束
{
//计算此次连锁的卡牌的伤害
for (int j = v.size() - 1; j >= 0; j--)
{
if (v[j].t == 3) break;//所有卡牌无效,则直接跳出
if (v[j].t == 4) { j--; continue; }//上一张卡牌无效
if (v[j].t == 1) ans = ans + v[j].x;//伤害为x
if (v[j].t == 2) ans = ans + (j + 1) * v[j].x;//伤害为x*连锁数
}
v.clear();//清空连锁卡牌
v.push_back(a[i]);//新的连锁
}
}
//计算最后一次连锁伤害
for (int j = v.size() - 1; j >= 0; j--)
{
if (v[j].t == 3) break;
if (v[j].t == 4) { j--; continue; }
if (v[j].t == 1) ans = ans + v[j].x;
if (v[j].t == 2) ans = ans + (j + 1) * v[j].x;
}
printf("%lld\n", ans);
}
return 0;
}
/*
9
1 1 300
2 2 400
2 3
2 2 500
1 1 1000
3 4
2 1 600
3 3
3 4
*/

C:模拟


链接:六子冲 


思路:共有八种情况,模拟一下即可。情况比较多,别错了就好。实在是没想到其他好的办法,感觉自己的方法太蠢了。

#include<bits/stdc++.h>
using namespace std;
int n, a[10][10], b[10], dir[5][2] = {{0, 0}, {-1, 0}, {1, 0}, {0, -1}, {0, 1}};
void init()
{
memset(a, 0, sizeof(a));
//黑棋
a[3][1] = 1; a[4][1] = 2; a[4][2] = 3; a[4][3] = 4; a[4][4] = 5; a[3][4] = 6;
//白棋
a[2][4] = 7; a[1][4] = 8; a[1][3] = 9; a[1][2] = 10; a[1][1] = 11; a[2][1] = 12;
}
void Find(int q, int& row, int& col)//求得q棋子的位置
{
for (int i = 1; i <= 4; i++)
{
for (int j = 1; j <= 4; j++)
{
if (a[i][j] == q)
{
row = i; col = j;
}
}
}
}
int judge(int num)
{
if (1 <= num && num <= 6) return 1;//黑
else if(7 <= num && num <= 12) return -1;//白
return 0;//空
}
void work(int row, int col)
{
for(int i = 1; i <= 4; i++) b[i] = judge(a[row][i]);//消除行
if((b[2]==1 && b[3]==1 && b[1]==-1 && judge(a[row][col])==1 || b[2]==-1 && b[3]==-1 && b[1]==1 && judge(a[row][col])==-1) && b[4]==0)
a[row][1] = 0;
if((b[3]==1 && b[4]==1 && b[2]==-1 && judge(a[row][col])==1 || b[3]==-1 && b[4]==-1 && b[2]==1 && judge(a[row][col])==-1) && b[1]==0)
a[row][2] = 0;
if((b[1]==1 && b[2]==1 && b[3]==-1 && judge(a[row][col])==1 || b[1]==-1 && b[2]==-1 && b[3]==1 && judge(a[row][col])==-1) && b[4]==0)
a[row][3] = 0;
if((b[2]==1 && b[3]==1 && b[4]==-1 && judge(a[row][col])==1 || b[2]==-1 && b[3]==-1 && b[4]==1 && judge(a[row][col])==-1) && b[1]==0)
a[row][4] = 0;

for(int i = 1; i <= 4; i++) b[i] = judge(a[i][col]);//消除列
if((b[2]==1 && b[3]==1 && b[1]==-1 && judge(a[row][col])==1 || b[2]==-1 && b[3]==-1 && b[1]==1 && judge(a[row][col])==-1) && b[4]==0)
a[1][col] = 0;
if((b[3]==1 && b[4]==1 && b[2]==-1 && judge(a[row][col])==1 || b[3]==-1 && b[4]==-1 && b[2]==1 && judge(a[row][col])==-1) && b[1]==0)
a[2][col] = 0;
if((b[1]==1 && b[2]==1 && b[3]==-1 && judge(a[row][col])==1 || b[1]==-1 && b[2]==-1 && b[3]==1 && judge(a[row][col])==-1) && b[4]==0)
a[3][col] = 0;
if((b[2]==1 && b[3]==1 && b[4]==-1 && judge(a[row][col])==1 || b[2]==-1 && b[3]==-1 && b[4]==1 && judge(a[row][col])==-1) && b[1]==0)
a[4][col] = 0;
}
int main()
{
int CASE = 1;
while (~scanf("%d", &n))
{
init();
for (int i = 0; i < n; i++)
{
int q, p;
scanf("%d%d", &q, &p);
int row, col;
Find(q, row, col);
int row2 = row + dir[p][0], col2 = col + dir[p][1];
swap(a[row][col], a[row2][col2]);//走子
work(row2, col2);//吃子
}
printf("#Case %d:\n", CASE++);
for (int i = 1; i <= 4; i++)
{
for (int j = 1; j <= 4; j++)
{
printf("%3d", a[i][j]);
}
printf("\n");
}
}
return 0;
}
/*
8
7 3
6 1
12 4
1 1
12 2
2 1
10 2
4 1
*/



D:规律


链接:N阶汉诺塔变形


思路:我不会,哈哈哈。参考大神题解勉强理解。

大神题解链接:N阶汉诺塔变形

#include<bits/stdc++.h>
using namespace std;
long long n, k;
int main()
{
while (~scanf("%lld%lld", &n, &k))
{
vector<int> v[3];
long long base = 1;
for (int i = 1; i <= n; i++)
{
int t = (k / base) % 6;
if (t > 2) t = 5 - t;
v[t].push_back(i);
base *= 3;
}
for (int i = 0; i < 3; i++)
{
if (v[i].size())
{
for (int j = v[i].size() - 1; j >= 0; j--)
{
printf("%d", v[i][j]);
if (j != 0) printf(" ");
}
}
else printf("0");
printf("\n");
}
}
return 0;
}
/*
3 5
4 10
*/

E:深搜 


链接:恋与程序员


思路:其实题意就是在一个N个点M条边的图中,求1~C的最短路,只不过边权和平时的不太一样,边权为卡片的种类,卡片只需要买一次,共有K种卡片,每张卡片有对应的花费。问1~C的最小花费。

深搜,走过的点进行标记,用过的卡片进行标记,当走某一条边的时候分两种情况,如果这个边的那张卡片买过了就不花钱,否则就需要花钱。用一个全局变量统计答案,每次走到终点C看是否需要更新答案。

当时直接想到了最短路,发现不会修改,然后就GG了。赛后看了眼别人的代码,原来是个深搜,很快就A了。

#include<bits/stdc++.h>
using namespace std;
const int INF = 1e9 + 5;
const int MAXN = 105;
int n, m, k, c, ans;
int G[MAXN][MAXN], card[1005];//G为图,card[i]表示编号为i的卡片价格为card[i]
bool vis[MAXN];//该点有没有被走过
bool use[1005];//卡片是否被使用
void dfs(int u, int sum)
{
if (u == c){ ans = min(ans, sum); return ; }
for (int v = 1; v <= n; v++)
{
if (G[u][v] && !vis[v])//有一条u到v的边,且v点没被走过
{
vis[v] = true;
if (use[G[u][v]]) dfs(v, sum);//该卡片被用过
else//该卡片没被用过
{
use[G[u][v]] = true;
dfs(v, sum + card[G[u][v]]);
use[G[u][v]] = false;
}
vis[v] = false;
}
}
}
int main()
{
while (~scanf("%d%d%d%d", &n, &m, &k, &c))
{
ans = INF;
memset(G, 0, sizeof(G));
memset(vis, 0, sizeof(vis));
memset(use, 0, sizeof(use));
for (int i = 0; i < m; i++)
{
int u, v, e;
scanf("%d%d%d", &u, &v, &e);
G[u][v] = e;
}
for (int i = 0; i < k; i++)
{
int a, b;
scanf("%d%d", &a, &b);
card[a] = b;
}
vis[1] = true;
dfs(1, 0);
printf("%d\n", ans);
}
return 0;
}
/*
6 7 5 6
2 3 2
4 3 3
1 2 1
1 5 4
4 6 5
1 4 2
5 6 3
1 100
3 422
2 210
5 107
4 38
*/


F:模拟


链接:大吉大利,今晚吃鸡——跑毒篇


思路:朋友说有坑,我没感觉到,迷之AC。每次使离终点步数减一,步数减一之后就掉血,如果<=0那就GG,如果没有判断是否需要打血,即血量小于7*a且大于6*a,如果小于等于6*a的话打血过程中会被毒死,如果大于等于7*a的话还可以再跑一米再打血。

一个队友把使用急救包的意思理解为直接+80血了(其实是把血两补到80),结果wa到怀疑人生。

后来他说:吃了没玩过吃鸡的亏哈哈哈。

#include<bits/stdc++.h>
using namespace std;
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
int a, b, c, h = 100;
scanf("%d%d%d", &a, &b, &c);
bool flag = true;
b--;
while (b--)
{
h = h - a;
if (h <= 0) { flag = false; break; }
if (6 * a < h && h <= 7 * a && c >= 1) { h = 80; c--; }
}
if (flag) printf("YES\n");
else printf("NO\n");
}
return 0;
}
/*
3
1 100 2
6 31 2
7 31 2
*/

G:递归 + 找规律    

    

链接:圆圈



思路:估计是POJ ~ 2083的改编。图形可以分为上下左右四个部分,递归打印即可。主要是找到规律:对于每阶图形,由该阶图形左上角的点,可以推出其他下一阶图形的左上角的点的位置,如果只有一个点了那么就直接赋值为'O'。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2200;
int n, row, col;//行和列的值
char a[MAXN][MAXN];
void init()//初始化
{
n++;//下面递归中给解释
row = pow(3, n - 1); col = pow(3, n - 1);
memset(a, ' ', sizeof(a));//先把所有地方都置为空格
}
void draw(int n, int x, int y)//生成图形
{
if (n == 1) { a[x][y] = 'O'; return ; }
//如果n=0作为结束条件,那么在n=1的时候t=0.333,数组就出错了,所以我让n++且n=1的时候作为结束条件
int t = pow(3, n - 2);
draw(n - 1, x , y + t );//上
draw(n - 1, x + t , y );//左
draw(n - 1, x + t , y + 2 * t);//右
draw(n - 1, x + 2 * t, y + t );//下
}
void print()//输出
{
for (int i = 0; i < row; i++)
{
for (int j = col - 1; j >= 0; j--)
{
if(a[i][j] == 'O') { a[i][j + 1] = '\0'; break; }//控制每一行的最后没有空格
}
}
for (int i = 0; i < row; i++) puts(a[i]);
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
scanf("%d", &n);
init();
draw(n, 0, 0);
print();
}
return 0;
}



H:斐波那契数列


链接:方块与收纳盒


思路:听见他们说是斐波那契数列,然后就敲一发试试,发现数据不超long long,直接A了

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 85;
long long a[MAXN];
void Fib()
{
a[0] = 1; a[1] = 1;
for (int i = 2; i < MAXN; i++) a[i] = a[i - 1] + a[i - 2];
}
int main()
{
Fib();
int T;
scanf("%d", &T);
while (T--)
{
int t;
scanf("%d", &t);
printf("%lld\n", a[t]);
}
return 0;
}
/*
3
1
2
4
*/

I:暴力


链接:找数字个数


思路:求出先求出a和b数字的每一位数字,放入set。然后暴力判断1~1000,是a和b倍数的直接跳过,分理处当前数字的每一位,如果某一位在set中出现过,也跳过,否则答案++。

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 85;
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
int a, b;
scanf("%d%d", &a, &b);
int a1 = a, b1 = b;
set<int> s;
while (a1)
{
s.insert(a1 % 10);
a1 /= 10;
}
while (b1)
{
s.insert(b1 % 10);
b1 /= 10;
}
//for (auto i: s) cout << i << " "; cout << endl;
long long ans = 0;
for (int i = 1; i <= 1000; i++)
{
if (i % a == 0 || i % b == 0) { continue; }//倍数
int t = i;
bool flag = false;
while (t)
{
if (s.count(t % 10)) { flag = true; break; }//含有某一个数字
t /= 10;
}
if (flag) continue;
ans++;
}
printf("%lld\n", ans);
}
return 0;
}
/*
3
2 3
14 23
1234 5678
*/

J:规律 + 递归

链接:闯关的lulu


思路:主要是题意,每走一层他会获得一个0,

2个0 == 1个1

3个1 == 1个2

4个2 == 1个3

5个3 == 1个4

。。。

可以推出来:n个k == n/(k-2)个k

他走N层总共会获得N个0,然后我们递归去算有几个几就好了,然后回溯过程种输出。

注意N的奇偶性,如果N为奇数直接在最后输一个0。

#include<bits/stdc++.h>
using namespace std;
int n;
void put(int n, int k)
{
if (n == 0) return ;
put(n / (k + 2), k + 1);//有N / (k + 2)个k+1
n = n % (k + 2);//还有n % (k + 2)个k
while (n--) printf("%d", k);
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
{
scanf("%d", &n);
if (n&1){ put(n - 1, 1); printf("0"); }//奇数个0
else put(n, 1);//偶数个0
printf("\n");
}
return 0;
}