2021年度训练联盟热身训练赛第二场
A-Binarize It
题意
对于给定数,找到第一个大于等于它的2的次方。
思路
算出其二进制表达下有几位(假设 c n t cnt cnt 位),以及本身是否为2的次方,若是2的次方就输出本身,否则输出 2 c n t + 1 2^{cnt + 1} 2cnt+1
代码
#include <iostream>
using namespace std;
int main(){
int T;
cin >> T;
while(T--){
int n;
cin >> n;
cout << "Input value: " << n << endl;
int cnt = 0;
int flag = 0;
while(n){
if(n & 1){
flag++;
}
n >>= 1;
cnt++;
}
if(flag == 1){
cnt--;
}
cout << (1 << cnt) << endl << endl;
}
return 0;
}
B-g2g c u l8r
题意
给定一系列语句的简称,并给出一个由一部分简称和正常语句构成的句子,把它替换成全称句子输出。
思路
字符串处理,简单模拟。具体见代码。
代码
#include <bits/stdc++.h>
using namespace std;
map<string, string> mp;
string s, t, tmp;
inline void read(){ //简称对应全称的读入
char c;
t = "";
getchar();
while((c = getchar()) != '\n'){
t.append(1, c);
}
}
inline char space_read(){ //整个句子的读入,用返回值区分读到了空格还是回车
char c;
tmp = "";
while((c = getchar()) != ' '){
if(c == '\n'){
return c;
}
tmp.append(1, c);
}
return c;
}
int main(){
int T;
cin >> T;
while(T--){
cin >> s;
read(); //cin >> t
mp[s] = t;
}
int q;
cin >> q;
getchar();
for(int i = 0; i < q; i++){
char c;
while(c = space_read()){
if(mp.find(tmp) == mp.end()){
cout << tmp;
}
else{
cout << mp[tmp];
}
if(c == '\n'){
break;
}
cout << ' ';
}
cout << endl;
}
return 0;
}
C-Tip to be Palindrome
题意
大哥吃完饭给小费,给饭钱的20%向上取整,并且要求小费+饭钱的值是一个回文,如果不是回文就加小费到回文状态(加小费当然加的越少越好)。给定饭钱,输出小费和总费用。
思路
算出小费之后在当前小费+总费用的基础上一直加1直到为回文。cf上做过一道有点类似的题,总之找到回文的次数一定是在一个可控范围内的,所以暴力不会超时。
代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f3f3f3f3f
#define PI acos(-1)
#define pb push_back
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
const int N = 500 + 19;
const ll mod = 1e9 + 7;
const double eps = 1e-12;
string s;
bool isP(int x)
{
s = "";
while(x)
{
s.append(1, x % 10);
x /= 10;
}
int n = s.length();
for(int i = 0; i <= n / 2; i++)
{
if(s[i] != s[n - i - 1])
return 0;
}
return 1;
}
int main()
{
int T;
cin >> T;
while(T--)
{
int n;
cin >> n;
cout << "Input cost: " << n << endl;
int tip = n / 5;
if(n - tip * 5 > 0)
tip++;
int bill = tip + n;
while(!isP(bill))
{
bill++;
}
tip = bill - n;
cout << tip << ' ' << bill << endl << endl;
}
return 0;
}
D-Soccer Standings
题意
n n n 支队伍, m m m 场比赛,输了队伍不扣分,赢了加三分,平局各加一分。要求你按照总分数对队伍进行降序排序,若分数相同按照进球数-被进球数降序排序,若这个也相等按照进球数降序排序,若进球数也相等按照名字字典序升序排序,并按序输出每个球队的信息,信息包括:名字,分数,赢场次数,输场次数,平局场次数,进球数,被进球数。
思路
简单模拟。硬模就行。
代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f3f3f3f3f
#define PI acos(-1)
#define pb push_back
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
const int N = 500 + 19;
const ll mod = 1e9 + 7;
const double eps = 1e-12;
map<string, int> mp;
struct node
{
string name;
int score;
int goal;
int allow;
int win;
int lose;
int draw;
bool operator < (const node& obj)const
{
if(score == obj.score)
{
if(goal - allow == obj.goal - obj.allow)
{
if(goal == obj.goal)
{
return name > obj.name;
}
return goal < obj.goal;
}
return goal - allow < obj.goal - obj.allow;
}
return score < obj.score;
}
void clear()
{
score = 0;
goal = 0;
allow = 0;
win = 0;
draw = 0;
lose = 0;
}
} a[N];
int main()
{
int T;
cin >> T;
for(int C = 1; C <= T; C++)
{
int n, m;
cin >> n >> m;
mp.clear();
for(int i = 0; i < n; i++)
{
a[i].clear();
}
for(int i = 0; i < n; i++)
{
cin >> a[i].name;
mp[a[i].name] = i;
}
string x, y;
for(int i = 0, xs, ys; i < m; i++)
{
cin >> x >> xs >> y >> ys;
if(xs > ys)
{
a[mp[x]].score += 3;
a[mp[x]].win++;
a[mp[y]].lose++;
}
else if(xs < ys)
{
a[mp[y]].score += 3;
a[mp[x]].lose++;
a[mp[y]].win++;
}
else
{
a[mp[x]].score++;
a[mp[y]].score++;
a[mp[x]].draw++;
a[mp[y]].draw++;
}
a[mp[x]].goal += xs;
a[mp[x]].allow += ys;
a[mp[y]].goal += ys;
a[mp[y]].allow += xs;
}
sort(a, a + n);
cout << "Group " << C << ":" << endl;
for(int i = n - 1; i >= 0; i--)
{
cout << a[i].name << ' ' <<
a[i].score << ' ' <<
a[i].win << ' ' <<
a[i].lose << ' ' <<
a[i].draw << ' ' <<
a[i].goal << ' ' <<
a[i].allow << endl;
}
cout << endl;
}
return 0;
}
E-NIH Budget
题意
有 d d d 种疾病,每种疾病所投入的钱和所能够救的人不成线性关系,而是一个4段的离散的函数(类似收停车费),有4个等级的投入值,投入钱超过某个等级的值就能救对应量的人。超过这个值但是未达到下一个等级,也只能救前一个等级的人数。
Research Funding | Lives Saved |
---|---|
10 million | 5 |
50 million | 100 |
100 million | 1000 |
250 million | 1100 |
现在给定每个疾病四个分级需要投入的钱和能救的人数,要求你算出在总投入小于给定值的情况下,最多能救多少人?
思路
第一次赛场上这么直接的看出来一道dp题(不是),但最后不是我写的,赛后补也确实发现没有想象的那么容易,不过也不是很难,还是可以做的。
转化问题为:给定背包容量和每个物品的占用容量、价值,最大化背包中物品的价值。没错,是01背包。但是有一个点需要改变,就是一个疾病的4个等级不能看作单独的物品,这四个物品是只能四选一的,所以你相当于要再这四个里面取max而不是按照一般做法把他们都当成单独物品。这点只要稍微改变一下循环就可以,滚动或者二维都行。具体见代码
代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f3f3f3f3f
#define PI acos(-1)
#define pb push_back
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
const int N = 100 + 19;
const int M = 100000;
const ll mod = 1e9 + 7;
const double eps = 1e-12;
int cost[N][5];
int save[N][5];
int dp[M];
int main()
{
int T;
scanf("%d", &T);
for(int C = 1; C <= T; C++)
{
int d, b;
scanf("%d%d", &d, &b);
fill(dp, dp + b + 1, 0);
for(int i = 1; i <= d; i++)
{
for(int j = 1; j <= 4; j++)
{
scanf("%d%d", &cost[i][j], &save[i][j]);
}
}
//dp[i] = max_save
for(int i = 1; i <= d; i++)
{
for(int l = b; l >= 0; l--)
{
for(int j = 1; j <= 4; j++)
{
if(l >= cost[i][j])
dp[l] = max(dp[l], dp[l - cost[i][j]] + save[i][j]);
}
}
}
int ans = 0;
for(int i = 0; i <= b; i++)
{
ans = max(ans, dp[i]);
}
printf("Budget #%d: Maximum of %d lives saved.\n\n", C, ans);
}
return 0;
}
F-Interstellar Love
题意
给定 n n n 个点 m m m 条路,求出其中有几个顶点数大于1的连通分量,以及其中有几个是环。
思路
并查集求连通分量,dfs求环。
代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f3f3f3f3f
#define PI acos(-1)
#define pb push_back
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
const int N = 1000 + 19;
const int M = 100000;
const ll mod = 1e9 + 7;
const double eps = 1e-12;
int par[N];
int rnk[N];
int num[N];
bool vis[N];
int n, m;
vector<int> vec[N];
set<int> st;
void init(int n)
{
st.clear();
for(int i = 0; i <= n; i++)
{
par[i] = i;
rnk[i] = 0;
num[i] = 0;
vis[i] = false;
vec[i].clear();
}
}
int find(int x)
{
if(x == par[x])
return x;
return par[x] = find(par[x]);
}
void unite(int x, int y)
{
x = find(x);
y = find(y);
if(x == y)
return;
if(rnk[x] > rnk[y])
{
par[y] = x;
}
else
{
par[x] = y;
if(x == y)
rnk[x]++;
}
}
void addEdge(int u, int v)
{
vec[u].pb(v);
vec[v].pb(u);
}
bool dfs(int u, int pre)
{
if(vis[u])
{
return 0;
}
vis[u] = 1;
for(auto v : vec[u])
{
if(v == pre)
continue;
if(dfs(v, u))
continue;
return 0;
}
return 1;
}
int main()
{
int T;
scanf("%d", &T);
for(int C = 1; C <= T; C++)
{
cin >> n >> m;
init(n);
for(int i = 0, u, v; i < m; i++)
{
cin >> u >> v;
addEdge(u, v);
unite(u, v);
}
for(int i = 1; i <= n; i++)
{
st.insert(find(i));
num[find(i)]++;
}
int cnt = 0;
int single = 0;
for(auto i : st)
{
if(num[find(i)] <= 1)
single++;
if(!dfs(i, 0))
{
cnt++;
}
}
printf("Night sky #%d: %d constellations, of which %d need to be fixed.\n\n",
C, st.size() - single, cnt);
}
return 0;
}
G-Plate Spinning
题意
n n n 个盘子连成环,最开始都以 p p p 的速度旋转,每秒钟转速减少 5 5 5 ,转速为 0 0 0 时盘子掉落。杂技师可以将一个转速大于等于 0 0 0 的盘子重新转到 p p p 的速度,他从一个盘子换到另一个盘子需要 0.5 0.5 0.5 秒的时间,且给盘子加速不需要花时间。问对于给定的 n , p n, p n,p ,杂技师能保持所有盘子不掉吗?
思路
比赛卡了老久,其实最开始题意理解正确,只是没特判1,以为理解错题意了,于是逐渐往离谱但是符合题目描述的方向走去,,,最后大胆假(窃)设(听) n = 1 n = 1 n=1 的情况是算永远成功,且是加速到 p p p ,才终于过了。
其实很简单,无论你怎么去转一定有一个盘子是最后才转到的,去判断你能否转到最后一个盘子就行。
代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f3f3f3f3f
#define PI acos(-1)
#define pb push_back
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
const int N = 500 + 19;
const ll mod = 1e9 + 7;
const double eps = 1e-12;
int main()
{
int T;
cin >> T;
for(int i = 1; i <= T; i++)
{
int n, p;
cin >> n >> p;
printf("Circus Act %d:\n", i);
if(n == 1){
printf("Chester can do it!\n\n");
continue;
}
if(5 * n <= p * 2)
printf("Chester can do it!\n\n");
else
printf("Chester will fail!\n\n");
}
return 0;
}
H-The Eternal Quest for Caffeine
题意
一个人从自己所在的楼层出发去买快乐水,每层楼都有汽水机,但是都只有一部分零件在运作,他要拿到所有的零件,去某一个有他需要的气泡水的汽水机,修好汽水机、买好饮料并回到原楼层。问最短需要走几层楼(从本楼层出发并回到原楼层)?
思路
这题真就阅读理解,原谅我用翻译软件了(小声
数据范围极小,所以直接枚举走到的最下面的楼层、走到的最上面的楼层,若当前情况满足题意,更新答案(取min)。
代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define pb push_back
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
const int N = 10 + 19;
const int M = 100000;
const ll mod = 1e9 + 7;
const double eps = 1e-12;
vector<int> vec[N];
set<int> st;
char vis[N];
int n, f, p;
void init(int n)
{
for(int i = 0; i <= n; i++)
vec[i].clear();
}
void unite(int i)
{
for(auto j : vec[i])
st.insert(j);
}
bool ok(int down, int up)
{
st.clear();
bool flag = 0;
for(int i = down; i <= up; i++)
{
if(vis[i] == 'Y')
flag = 1;
unite(i);
}
if(st.size() == p && flag)
return 1;
return 0;
}
int main()
{
int cc = 0;
while(cin >> n >> f >> p)
{
if(!n && !f && !p)
break;
init(n);
for(int i = 1, k; i <= n; i++)
{
cin >> k;
for(int j = 0, x; j < k; j++)
{
cin >> x;
vec[i].pb(x);
}
cin >> vis[i];
}
int ans = INF;
for(int i = 0; i <= f; i++)
for(int j = f; j <= n; j++)
if(ok(i, j))
ans = min(ans, (f - i) * 2 + (j - f) * 2);
if(ans == INF)
printf("Test case #%d: Impossible\n\n", ++cc);
else
printf("Test case #%d: %d\n\n", ++cc, ans);
}
return 0;
}
I-Pegasus Circle Shortcut
题意
给定圆的圆心、起点、终点(保证起点终点在圆上),并给定一条从起点到终点共有 n n n 的点的路,问是从圆上逆时针走比较进还是从路上走比较进。
思路
比赛因为精度WA了很多次,加起来这次比赛我WA了9次(#°Д°)
简单模拟,就算出来然后比较就行了,但是最开始我在算弧长的时候套了一个精度不够的公式,是用起点和终点算弦长然后反过来求圆心角从而求出弧长,最后判断叉积是否小于0(小于0逆时针角大于180度)来决定是否要取较大的半圆。疯狂WA找不出错误之后就开始怀疑公式有问题,可能是弦长求圆心角的精度不够。
于是开始找有没有能够算出两个向量逆时针夹角的公式,最后找到 atan2()
函数,表示从
x
x
x 轴正方向逆时针转到该向量的角度,如果是负的就说明夹角超过180度(顺时针转的角度为其绝对值)。因此可以算出终点、起点以圆心为原点和
x
x
x 轴正方向的逆时针转角之差,并规范化(就是使之处于
[
0
,
2
π
)
[0, 2\pi)
[0,2π) 之间),也就不需要判断叉积了。求出角度后,弧长就是圆心角乘半径。
代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f3f3f3f3f
#define PI acos(-1)
#define pb push_back
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
const int N = 500 + 19;
const ll mod = 1e9 + 7;
const double eps = 1e-12;
struct Point
{
double x, y;
Point() {}
Point(double a, double b)
{
x = a;
y = b;
}
bool ed()//判断输入结尾
{
return fabs(x) < eps && fabs(y) < eps;
}
};
double dis(Point a, Point b)//距离
{
return sqrt((a.x - b.x) * (a.x - b.x) + (a.y - b.y) * (a.y - b.y));
}
int cross(Point& point1, Point& point2)//向量叉积(本题最后没有用到
{
double temp = point1.x * point2.y - point2.x * point1.y;
return temp;
}
Point a[N];
int main()
{
Point c, s, t;
for(int T = 1; ; T++)
{
scanf("%lf%lf%lf%lf%lf%lf", &c.x, &c.y, &s.x, &s.y, &t.x, &t.y);
if(c.ed() && s.ed() && t.ed())
break;
int n;
cin >> n;
for(int i = 0; i < n; i++)
cin >> a[i].x >> a[i].y;
//按圆逆时针走
double ans1 = atan2(t.y - c.y, t.x - c.x) - atan2(s.y - c.y, s.x - c.x);
while (ans1 < 0.0)
ans1 += 2 * PI;
while (ans1 > 2 * PI)
ans1 -= 2 * PI;
ans1 *= dis(s, c);
/* 精度不够的做法
double r = dis(s, c);
double ans1 = 2.0 * r * asin(dis(s, t) / (2.0 * r));
if(cross(s, t) < 0.0)
ans1 = 2.0 * PI * r - ans1;
*/
//按路走
double ans2 = dis(s, a[0]);
for(int i = 1; i < n; i++)
ans2 += dis(a[i - 1], a[i]);
ans2 += dis(a[n - 1], t);
printf("Case #%d: ", T);
if(ans1 > ans2)
{
printf("Watch out for squirrels!\n\n");
}
else
{
printf("Stick to the Circle.\n\n");
}
}
return 0;
}
J-Lowest Common Ancestor
题意
给定两个16进制表示下的数,算出他们在二进制表示下的最长公共前缀。
思路
题的本意是找两个数在完美二叉树层序编号下的最近公共祖先,但其实层序编号下的数的二进制表示就是树按照左0右1的情况走下来的二进制表示,也就是离散里的哈夫曼编码。因此就是找二进制下的公共前缀。
公共前缀让我梦回计网课算子网掩码和IP网段,我期末没90一定是错这题了(误)
代码
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define PI acos(-1)
#define pb push_back
using namespace std;
typedef pair<int, int> P;
typedef long long ll;
const int N = 1000 + 19;
const int M = 100000;
const ll mod = 1e9 + 7;
const double eps = 1e-12;
void change(int x, string& a)
{
while(x)
{
if(x & 1)
{
a.append(1, '1');
}
else
{
a.append(1, '0');
}
x >>= 1;
}
reverse(a.begin(), a.end());
}
int trans(char a)
{
if (a <= '9')
return a - '0';
return a - 'a' + 10;
}
inline string to_binary(char c)
{
return bitset<4>(trans(c)).to_string();
}
inline string to_binary_string(string& a)
{
string res;
for (int i = 0; i < a.length(); ++i)
{
res += to_binary(a[i]);
}
int i = 0;
while (i < res.length() && res[i] == '0')
{
++i;
}
return res.substr(i);
}
char to_hex(int n) {
if (n <= 9) return n + '0';
else return n + 'a' - 10;
}
string to_hex_string(string& a) {
int tmp;
string res;
for (int i = a.length() - 1; i >= 0; i -= 4) {
tmp = 0;
for (int j = -3; j <= 0; ++j) {
if (i + j < 0) {
continue;
}
tmp = tmp * 2 + (a[i + j] - '0');
}
res = to_hex(tmp) + res;
}
return res;
}
int main()
{
int n;
cin >> n;
for(int j = 1; j <= n; j++)
{
string a, b;
cin >> a >> b;
a = to_binary_string(a);
b = to_binary_string(b);
int k = min(a.length(), b.length());
int cnt = 0;
for(int i = 0; i < k; i++)
{
cnt = i;
if(a[i] != b[i])
{
cnt--;
break;
}
}
a = a.substr(0, cnt + 1);
a = to_hex_string(a);
cout << "Case #" << j << ": " << a << "\n\n";
}
return 0;
}
总结
第一次AK,虽然靠队友的比较多qwq
第一道开的题就是G题,因为题意不清晰以及最开始做的时候不仔细没特判所以WA了很多次,但是说实话要是最开始细心一点说不定之后都不需要纠结题意。
之后开的是I题,没错也卡了,卡到最后一题才出这个。而且是因为精度而不是做的不对,怪就怪我没有计算几何板子,,,下周就校赛了,计算几何的板子是肯定要理了,其他很需要板子的估计也得理,要加油了