2020省赛第八次训练赛题解
文章目录
- 2020省赛第八次训练赛题解
- 前面的碎碎念
- [A - Team Formation](https://vjudge.net/problem/ZOJ-3870)
- [B - Convex Hull](https://vjudge.net/problem/ZOJ-3871)
- [C - Beauty of Array](https://vjudge.net/problem/ZOJ-3872)
- [D - Lunch Time](https://vjudge.net/problem/ZOJ-3875)
- [E - May Day Holiday](https://vjudge.net/problem/ZOJ-3876)
- [F - Convert QWERTY to Dvorak](https://vjudge.net/problem/ZOJ-3878)
- [G - Capture the Flag](https://vjudge.net/problem/ZOJ-3879)
- [H - Demacia of the Ancients](https://vjudge.net/problem/ZOJ-3880)
- [I - Ace of Aces](https://vjudge.net/problem/ZOJ-3869)
- [J - Earthstone Keeper](https://vjudge.net/problem/ZOJ-3877)
前面的碎碎念
这场比赛的题目不是很难,签到题占了快一半了,同样的记录本场比赛的原因是因为本场比赛的思维题占了一半,思维题可遇不可求,每一次遇到都大开眼界,吐槽一下hdu和fzu竟然在国庆期间不开放,导致前面几场比赛没机会写题解,(其实也是没时间,每天一场训练赛对于我这种慢性子是需要很长很长很长的时间来补题的)你看看人家poj和zoj假期依然坚守岗位,在这里为两大oj点赞,当然牛客和洛谷也是坚守岗位。冲冲冲!!
A - Team Formation
思维题
题目分析:题目的意思就是有一些人,两两组队,每个人都有能力值,如果两个人能力值的异或值大于每一个人单独的能力值,那么这两个人组的队可以认为是一个出色的团队。
首先这个题目暴力是不会出奇迹的,暴力是可以过样例的但是显然也会超时,那么我们可以从异或出发,两个数的异或就是二进制的每一位异或,我们遵循相同为0,相异为1,也就是说如果两个数对应位置上的数字不同那么结果就是1,回到题目中,题目要求的是异或两个数之后变大,那么我们以其中一个数为基准,如果这个数二进制的某一位上为0,而对应另一个数相应位置并且这个位置是最高位二进制上为1,那么两个数异或就会变大,不用去考虑其他位,因为就算其他位置异或变小了但是我们保证高位异或变大整体还是变大的。
对于任意的a(以下用二进制来表示)
如a
1011001
对应的b可以是
1* * * * *
1* *
1*
对于b
若b的最高位 对于 a来说 在a的当前位 为0 则符合上述条件
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e6 + 10;
int vis[maxn], a[maxn];
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
int n;
memset(vis, 0, sizeof(vis));
scanf("%d", &n);
ll ans = 0;
for (int i = 1; i <= n; ++i)
{
scanf("%d", &a[i]);
int x = a[i], tot = 0;
while (x)
{
++tot;
x >>= 1;
}
vis[tot]++; //因为首位一定为1,那么就记录一下这个数最高位为1的位置
}
for (int i = 1; i <= n; ++i)
{
int x = a[i], tot = 0;
while (x)
{
++tot;
if (!(x & 1)) //如果这个数当前位置为0,看看能找到几个数当前位置也是这个数二进制最高位为1
//并且这种过滤已经排除掉本身了,所以不用判断是不是异或本身
ans += vis[tot];
x >>= 1;
}
}
printf("%lld\n", ans);
}
}
B - Convex Hull
计算几何,凸包
题意:给n个点,|x[i]|,|y[i]| <= 1e9。求在所有情况下的子集下(子集点数>=3),凸包的面积和。
这题主要有几个方面,一个是凸包的面积,可以直接用线段的有向面积和求得,这个当时没想到。还有就是,在180度以内的点数。
所以实际上是枚举2个点作为某个凸包的一条边,看有多少个这样的凸包。那个2^num - 1是保证除了2个点外至少还需1个点才能构成凸包。
时间复杂度O(n * n * logn)
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cmath>
using namespace std;
const double pi = acos(-1.0);
#define ll long long
#define maxn 1010
#define mod 998244353
ll area2(ll ax, ll ay, ll bx, ll by)
{
return ((ax * by % mod - ay * bx % mod) % mod + mod) % mod;
}
struct node
{
ll x, y;
double ang;
bool operator<(const node &b) const
{
return ang < b.ang;
}
} vec[maxn << 1];
ll p2[maxn];
int main()
{
p2[0] = 1;
for (ll i = 1; i <= 1000; ++i)
p2[i] = (p2[i - 1] << 1) % mod;
ll t;
scanf("%lld", &t);
while (t--)
{
ll n;
scanf("%lld", &n);
ll x[maxn], y[maxn];
for (ll i = 0; i < n; ++i)
scanf("%lld%lld", x + i, y + i);
ll ans = 0;
for (ll i = 0; i < n; ++i)
{
for (ll j = 0; j < n; ++j)
{
vec[j].x = x[j];
vec[j].y = y[j];
vec[j].ang = atan2(x[j] - x[i], y[j] - y[i]);
}
vec[i] = vec[n - 1];
for (ll j = 0; j < n - 1; ++j)
{
vec[j + n - 1] = vec[j];
vec[j + n - 1].ang += pi * 2;
}
ll nn = n - 1 + n - 1;
sort(vec, vec + nn);
ll l = 0, r = 0;
while (l < n - 1)
{
while (r < nn && vec[r].ang - vec[l].ang < pi)
r++;
ll area = area2(x[i], y[i], vec[l].x, vec[l].y);
ll cnt = (p2[r - l - 1] - 1 + mod) % mod;
ans = (ans + 1ll * area * cnt % mod) % mod;
++l;
}
}
printf("%lld\n", (mod - ans) % mod);
}
return 0;
}
C - Beauty of Array
dp
参考链接
题目分析:这个题目很巧妙,题目要求的是所有连续序列中不同数字之和,出看可能不好写,也不知道怎么去写,暴力肯定会超时的,所以不用考虑。那么我们每添加一个元素进来,求当前序列包含a的所有序列之和,我们用两个变量dp,sum,dp去储存当前所有子序列的和,sum储存整个序列的和,然后用一个变量去定义每个数字最终出现的位置。
说是这么说,当我第一次看题解的时候,我没看懂,然后就自己手动模拟了一下代码的过程,大致模拟完之后,也就知道代码的意思了
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100005;
typedef long long ll;
int a[maxn];
int main()
{
int t, m, x;
scanf("%d", &t);
while (t--)
{
memset(a, 0, sizeof(a));
scanf("%d", &m);
ll ans = 0, dp = 0;
for (int i = 1; i <= m; i++)
{
scanf("%d", &x);
dp = (i - a[x]) * x + dp;
ans += dp;
a[x] = i;//记住这个数的位置,如果下一个数和这个数相同的话,那么加上的就是下一个数字,就不会加上前面的和
}
printf("%lld\n", ans);
}
return 0;
}
D - Lunch Time
结构体的简单排序
题目分析:题目的意思就是在每一样菜品中选出中位数,如果一个菜有偶数个那么取最大的中位数
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100005;
typedef long long ll;
struct node
{
string s;
int num;
bool operator<(const node x) const
{
return num < x.num;
}
} w[maxn];
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
int a, b, c;
int ans = 0;
string s = "";
scanf("%d%d%d", &a, &b, &c);
for (int i = 1; i <= a; ++i)
cin >> w[i].s >> w[i].num;
sort(w + 1, w + a + 1);
ans += w[(a / 2) + 1].num;
s += w[(a / 2) + 1].s;
s += " ";
for (int i = 1; i <= b; ++i)
cin >> w[i].s >> w[i].num;
sort(w + 1, w + b + 1);
ans += w[(b / 2) + 1].num;
s += w[(b / 2) + 1].s;
s += " ";
for (int i = 1; i <= c; ++i)
cin >> w[i].s >> w[i].num;
sort(w + 1, w + c + 1);
ans += w[(c / 2) + 1].num;
s += w[(c / 2) + 1].s;
// s += " ";
cout << ans << ' ' << s << endl;
}
return 0;
}
E - May Day Holiday
模拟题
https://blog.csdn.net/weixin_33700350/article/details/93304560
题目分析:这种题目之前遇见过,有点小恶心,需要我们手动计算一下(偷偷查看电脑日历也是可以的)根据题意已知,2015年5月1日是星期五,据此推算。结果是1928年5月1日是星期二。用0-6分别表示星期天到星期六。,我们在计算的时候,只需要计算一下相差的年份,然后加一下,如果是闰年的话就多加一个1,然后对每一次的和都模一下7就行。
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <set>
#include <map>
#include <vector>
#include <queue>
#include <string.h>
typedef long long ll;
using namespace std;
#define pi acos(-1.0)
const int maxn = 1e5 + 10;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
int ans[10] = {6, 9, 6, 5, 5, 5, 5};
int judge(int x)
{
if (x % 4 == 0 && x % 100 != 0 || x % 400 == 0)
return 1;
return 0;
}
int main()
{
int t, y;
scanf("%d", &t);
while (t--)
{
int d = 2, a = 1928;
scanf("%d", &y);
while (y != a)
{
++a;
d = (d + 365 + judge(a)) % 7;
}
printf("%d\n", ans[d]);
}
}
F - Convert QWERTY to Dvorak
模拟题
题目分析:这是一个比较恶心的模拟题
#include <bits/stdc++.h>
using namespace std;
int main()
{
char a[128],x;
for (int i = 0; i < 128; i++)
a[i] = i;
a['-'] = '[';
a['='] = ']';
a['['] = '/';
a[']'] = '=';
a['p'] = 'l';
a['o'] = 'r';
a['i'] = 'c';
a['u'] = 'g';
a['y'] = 'f';
a['t'] = 'y';
a['r'] = 'p';
a['e'] = '.';
a['w'] = ',';
a['q'] = '\'';
a['a'] = 'a';
a['s'] = 'o';
a['d'] = 'e';
a['f'] = 'u';
a['g'] = 'i';
a['h'] = 'd';
a['j'] = 'h';
a['k'] = 't';
a['l'] = 'n';
a[';'] = 's';
a['\''] = '-';
a['.'] = 'v';
a['/'] = 'z';
a[','] = 'w';
a['n'] = 'b';
a['b'] = 'x';
a['v'] = 'k';
a['c'] = 'j';
a['x'] = 'q';
a['z'] = ';';
a['Q'] = '"';
a['W'] = '<';
a['E'] = '>';
a['{'] = '?';
a['}'] = '+';
a['_'] = '{';
a['+'] = '}';
a[':'] = 'S';
a['"'] = '_';
a['Z'] = ':';
a['<'] = 'W';
a['>'] = 'V';
a['?'] = 'Z';
while (~scanf("%c", &x))
{
if (x >= 'A' && x <= 'Z' && !(x == 'Q' || x == 'W' || x == 'E' || x == 'Z'))
printf("%c", a[x + 32] - 32);
else
printf("%c", a[x]);
}
return 0;
}
G - Capture the Flag
模拟题
这是一个较为复杂的模拟题,复杂在题目意思不是很好的理解,代码中也有很多细节地方需要处理
题目分析:首先输入4个数,n,q,p,c
代表有n个队伍,q个服务器,每支队伍的初始分数p,还有c次操作
对于每次操作,首先输入一个k,代表k次攻击
每次攻击有三个数,a,b,c,代表a通过c服务器成功的攻击了b
对于每次成功的攻击,b会失去n-1分,这n-1分会平分给通过同一服务器攻击b的几支队伍
然后是q行,每行n个数,分别代表1~n是否成功维护了,1为成功,0为不成功
不成功的队伍会失去n-1分,这失去的分会平分给成功维护的那些队伍
然后输入k个数,询问这k支队伍的分数
#include <bits/stdc++.h>
using namespace std;
#define exp 1e-5
const int inf = 0x3f3f3f3f;
struct node
{
int id, rank;
double score;
} a[105];
int n, q, c, t;
double p;
bool vis[105][105][15], hsh[105];
int cmp1(node a, node b)
{
return a.score > b.score;
}
int cmp2(node a, node b)
{
return a.id < b.id;
}
int main()
{
scanf("%d", &t);
while (t--)
{
scanf("%d%d%lf%d", &n, &q, &p, &c);
for (int i = 1; i <= n; ++i)
{
a[i].id = i;
a[i].score = p;
}
while (c--)
{
int k;
scanf("%d", &k);
memset(vis, false, sizeof(vis));
while (k--)
{
int x, y, z;
scanf("%d%d%d", &x, &y, &z);
if (vis[x][y][z])
continue;
vis[x][y][z] = true;
}
for (int i = 1; i <= q; ++i) //服务器
{
for (int j = 1; j <= n; ++j) //防守方
{
int cnt = 0;
for (int k = 1; k <= n; ++k) //攻击方
if (vis[k][j][i]) //统计攻击j的队伍有几支
cnt++;
if (!cnt)
continue;
double ss = 1.0 * (n - 1) / cnt; //平分
a[j].score -= (n - 1); //防守方失去n-1
for (int k = 1; k <= n; ++k)
if (vis[k][j][i]) //攻击方得到分数
a[k].score += ss;
}
}
for (int i = 1; i <= q; ++i) //服务器
{
memset(hsh, false, sizeof(hsh));
int cnt = 0;
for (int j = 1; j <= n; ++j)
{
int x;
scanf("%d", &x);
if (x) //成功维护的队伍数
{
hsh[j] = true;
cnt++;
}
else
{
hsh[j] = false;
a[j].score -= (n - 1);
}
}
if (cnt == n)
continue;
double ss = 1.0 * (n - 1) / cnt;
ss = ss * (n - cnt);
for (int j = 1; j <= n; ++j)
if (hsh[j])
a[j].score += ss;
}
sort(a + 1, a + n + 1, cmp1);
for (int i = 1; i <= n; ++i) //更新排名
{
if (i != 1)
{
if (fabs(a[i].score - a[i - 1].score) < exp)
a[i].rank = a[i - 1].rank;
else
a[i].rank = i;
}
else
a[i].rank = i;
}
sort(a + 1, a + n + 1, cmp2);
scanf("%d", &k);
while (k--)
{
int x;
scanf("%d", &x);
printf("%f %d\n", a[x].score, a[x].rank);
}
}
}
return 0;
}
H - Demacia of the Ancients
水题
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+10;
int a[maxn];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
int n;
int ans=0;
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
if(a[i]>6000)
++ans;
}
printf("%d\n",ans);
}
}
I - Ace of Aces
模拟题
题目分析:首先用一个map去储存每一个数出现的次数,然后记录维护一个最大值也就是出现最多的次数,之后我们就去遍历map数组。如果有一个数出现的次数是最大次数,那么就符合题意,否则不满足输出Nobody
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <set>
#include <map>
#include <vector>
#include <queue>
#include <string.h>
typedef long long ll;
using namespace std;
#define pi acos(-1.0)
const int maxn = 1e5 + 10;
const int inf = 0x3f3f3f3f;
const int mod = 1e9 + 7;
int a[maxn];
map<int, int> mp;
map<int, int>::iterator it;
int main()
{
int t;
scanf("%d", &t);
while (t--)
{
mp.clear();
int n, ans = 0;
scanf("%d", &n);
for (int i = 1; i <= n; ++i)
{
scanf("%d", &a[i]);
mp[a[i]]++;
ans = max(ans, mp[a[i]]);
}
int cnt = 0, tot;
for (it = mp.begin(); it != mp.end(); it++)
{
if (it->second == ans)
{
tot = it->first;
++cnt;
}
}
// cout << cnt << ' ' << 1 << endl;
if (cnt == 1)
printf("%d\n", tot);
else
puts("Nobody");
}
}
J - Earthstone Keeper
最短路
题目分析:这应该是目前我见过最难的最短路。所以鸽了鸽了
本题如果去掉怪物选项就是双关键字最短路。
问题是现在多了一个怪物选项,因为这个怪物选项被杀死后他不会复生,因此我们不能重复计算这个值的答案。
所以对于每条过来的路,前面一个点遇到的怪物的后面的点就不用计算了,也就是去重。
根据这个思路,我们可以得到我们想要干的是当枚举到当前点,我们希望计算出他的cost然后减去前一个点由怪物造成的cost。
所以怪物这个点是特殊的点,普通点和普通点就是按规则连边,因为他们是相邻的,去重比较容易。
我们多考虑一种连边,因为题目告诉我们怪物是不可能相邻的,这个信息显然是有自己的道理,也给我们带来启发。
当我们枚举到怪物点的时,我们将他上下左右这四个点分别连单向边,时间为2,cost就是终点的cost-起点的cost
这样就成功跳过了怪物类的点
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> pll;
const int N = 2e6 + 10;
const int mod = 1e9 + 7;
int n, m;
char g[550][550];
int sx, sy, ex, ey;
int dx[] = {-1, 0, 1, 0};
int dy[] = {0, 1, 0, -1};
vector<pll> num;
int h[N], ne[N], e[N], idx;
int cost[N], w[N];
int st[N];
int dis[N][2];
struct node
{
int x, y;
int id;
bool operator<(const node &t) const
{
if (x == t.x)
return y > t.y;
return x > t.x;
}
};
void add(int a, int b, int c, int d)
{
e[idx] = b, ne[idx] = h[a], cost[idx] = c, w[idx] = d, h[a] = idx++;
}
bool check(int x, int y)
{
if (x >= 0 && x < n && y >= 0 && y < m)
{
if (g[x][y] != '#')
return true;
}
return false;
}
int solve(int x, int y)
{
int res = 0;
if (g[x][y] >= 'a' && g[x][y] <= 'z')
res += (g[x][y] - 'a' + 1);
int i;
for (i = 0; i < 4; i++)
{
int a = x + dx[i];
int b = y + dy[i];
if (!check(a, b))
continue;
if (g[a][b] >= 'A' && g[a][b] <= 'Z')
res += (g[a][b] - 'A' + 1);
}
return res;
}
int get(int a, int b, int c, int d)
{
int res = solve(c, d);
int i, j;
for (i = 0; i < 4; i++)
{
int x = dx[i] + a;
int y = dy[i] + b;
if (check(x, y))
{
if (g[x][y] >= 'A' && g[x][y] <= 'Z')
{
for (int k = 0; k < 4; k++)
{
int tmp1 = dx[k] + x;
int tmp2 = dy[k] + y;
if (check(tmp1, tmp2))
{
if (tmp1 == c && tmp2 == d)
{
res -= (g[x][y] - 'A' + 1);
}
}
}
}
}
}
return res;
}
void dij()
{
int ans1, ans2;
priority_queue<node> q;
q.push({0, 0, sx * m + sy});
int i;
for (i = 0; i <= n * m; i++)
{
st[i] = 0;
dis[i][1] = 0x3f3f3f3f;
dis[i][0] = 0x3f3f3f3f;
}
dis[sx * m + sy][0] = dis[sx * m + sy][1] = 0;
while (q.size())
{
auto t = q.top();
q.pop();
if (st[t.id])
continue;
st[t.id] = 1;
if (t.id == ex * m + ey)
{
ans1 = t.x;
ans2 = t.y;
break;
}
for (i = h[t.id]; i != -1; i = ne[i])
{
int j = e[i];
if (dis[j][0] > dis[t.id][0] + cost[i])
{
dis[j][0] = dis[t.id][0] + cost[i];
dis[j][1] = dis[t.id][1] + w[i];
q.push({dis[j][0], dis[j][1], j});
}
else if (dis[j][0] == dis[t.id][0] + cost[i] && dis[j][1] > dis[t.id][1] + w[i])
{
dis[j][1] = dis[t.id][1] + w[i];
q.push({dis[j][0], dis[j][1], j});
}
}
}
printf("%d %d\n", ans1, ans2);
}
int main()
{
//ios::sync_with_stdio(false);
int t;
cin >> t;
while (t--)
{
scanf("%d%d", &n, &m);
int i, j;
idx = 0;
for (i = 0; i <= n * m; i++)
{
h[i] = -1;
}
scanf("%d%d%d%d", &sx, &sy, &ex, &ey);
sx--, sy--, ex--, ey--;
for (i = 0; i < n; i++)
scanf("%s", g[i]);
for (i = 0; i < n; i++)
{
for (j = 0; j < m; j++)
{
int k;
if (g[i][j] == '#')
continue;
if (g[i][j] >= 'A' && g[i][j] <= 'Z')
{
num.clear();
for (k = 0; k < 4; k++)
{
int x = i + dx[k];
int y = j + dy[k];
if (check(x, y))
{
num.push_back({x, y});
}
}
for (k = 0; k < (int)num.size(); k++)
{
for (int l = 0; l < (int)num.size(); l++)
{
if (k == l)
continue;
int tmp1 = num[k].first * m + num[k].second;
int tmp2 = num[l].first * m + num[l].second;
add(tmp1, tmp2, get(num[k].first, num[k].second, num[l].first, num[l].second), 2); //计算到下个点的代价
}
}
}
else
{
for (int k = 0; k < 4; k++)
{
int x = i + dx[k];
int y = j + dy[k];
if (!check(x, y))
continue;
if (g[x][y] >= 'A' && g[x][y] <= 'Z')
continue;
add(i * m + j, x * m + y, solve(x, y), 1);
}
}
}
}
dij();
}
return 0;
}
一夜岂可成名,百炼方能成钢 |
---|
2020.10.12湖人在总决赛以比分4:2一举击败热火,夺得了今年的总冠军,is for kobe ,is also for lakers,叫一个赛季的湖人总冠军,终于实现了!!!千言万语,终究化为“湖人总冠军!!!”