AtCoder Beginner Contest 303
B - Discord
题目大意
给定m行数字串, 每行数字串都是由1~n的数字组成, 问有多少对数字一直没有相邻过
解题思路
很容易想到从1~n里面任意挑选两个数字一共有C(n 2)种可能, 即n*(n-1)/2; (注意数字对是无序的) 所以我们可以遍历所有数字串, 用set储存相邻过的数字对 (为避免同时储存{a, b}和{b, a}, 我们可以在储存时先比较一下, 储存时小数在前, 大数在后) ;
神秘代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=60;
int f[N][N];
set<pair<int,int>> s;
signed main(){
int n,m;
cin>>n>>m;
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
cin>>f[i][j];
}
}
for(int i=1;i<=m;i++){
for(int j=1;j<n;j++){
int x=min(f[i][j],f[i][j+1]);
int y=max(f[i][j],f[i][j+1]);
s.insert({x,y});
}
}
int num=((n-1)*n)/2;
num-=s.size();
cout<<num;
return 0;
}
C - Dash
题目大意
在一个二维平面上, 小莫以(0,0)为起点, 给出小莫要走的步数和每一步的方向; 小莫初始血量为h, 每走一步血量就会-1; 在平面上有m个补给包, 并给出它们的坐标, 当小莫到达补给包坐标时, 如果此时血量小于k, 那么就会消耗掉这个补给包并把血量回复到k; 问当小莫走完给定的路线时血量是否大于等于0;
注意: 对于每一次行动, 都是先扣血再看有没有补给包; 如果走到某个补给包坐标时血量已经小于0了, 那么无法使用该补给包, 游戏直接结束;
解题思路
只需要简单模拟一下就行; 注意一个坑点, 每个补给包只能使用一次, 当时读题没读出来, 因为这个原因wa了3次...
神秘代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10;
int f[N][2];
signed main() {
int x, y, z;
cin >> x >> y >> z;
string s;
cin >> s;
f[0][1] = z;
f[0][0] = 0;
int len = s.size();
for (int i = 0; i < len; i++) {
if (s[i] == 'a') {
f[i + 1][0] = min(f[i][0] + x, min(f[i][1] + z + x, f[i][1] + y + z));
f[i + 1][1] = min(f[i][1] + y, min(f[i][0] + z + y, f[i][0] + x + z));
}
else {
f[i + 1][0] = min(f[i][0] + y, min(f[i][1] + z + y, f[i][1] + x + z));
f[i + 1][1] = min(f[i][1] + x, min(f[i][0] + z + x, f[i][0] + y + z));
}
}
cout << min(f[len][1], f[len][0]);
return 0;
}
D - Shift vs. CapsLock
题目大意
给定一个只由a和A组成的字符串, 并且给出了三种操作和他们所需的时间; 你需要计算出最少用多少时间可以打出该字符串; 三种操作如下:
一: 消耗x秒按下字符a的按键, 如果此时不是大写状态则输入字符a, 否则输入A;
二: 消耗y秒同时按下字符a和shift的按键, 如果此时不是大写状态则输入字符A, 否则输入a;
三: 消耗z秒按下CapsLock的按键, 如果此时不是大写状态则开启大写状态, 否则就关闭;
解题思路
当时读完题就感觉应该是个dp问题, 于是试着敲一下没想到直接过了, 哇库哇库;
状态表示: f(i, j), j只有1和0两种情况表示当输完第i个字符后, 此时大写状态是否开启; 所以最后只需要输出f[n][1]和f[n][0]的最小值即可;
状态计算首先要根据第i个字符是a还是A分成两种情况讨论, 下面讨论a的情况;
状态计算: 这里我们只简单说说f[i+1][0]的情况, j=1的情况同理;
f[i+1][0] 由三种状态转移而来, 如果是f[i][0], 那么只需要消耗x输入a即可; 如果是f[i][1], 那么就可以再分两种情况, 一是先消耗y秒通过第二种操作输入a, 然后再消耗z秒关闭大写状态; 二是先消耗z秒关闭大写状态, 然后再消耗x秒输入a;
注意: 别忘了初始化f[0][1]=z
神秘代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10;
int f[N][2];
signed main() {
int x, y, z;
cin >> x >> y >> z;
string s;
cin >> s;
f[0][1] = z;
f[0][0] = 0;
int len = s.size();
for (int i = 0; i < len; i++) {
if (s[i] == 'a') {
f[i + 1][0] = min(f[i][0] + x, min(f[i][1] + z + x, f[i][1] + y + z));
f[i + 1][1] = min(f[i][1] + y, min(f[i][0] + z + y, f[i][0] + x + z));
}
else {
f[i + 1][0] = min(f[i][0] + y, min(f[i][1] + z + y, f[i][1] + x + z));
f[i + 1][1] = min(f[i][1] + x, min(f[i][0] + z + x, f[i][0] + y + z));
}
}
cout << min(f[len][1], f[len][0]);
return 0;
}
E - A Gift From the Stars
题目大意
如果一个图形有(k+1)个顶点和k条边组成, 那么我们称之为k边星 (k >= 2);
注意在k边星的顶点里面有一个是中心点, 中心点会与其他顶点通过一条边相连, 并且没有多余的边; (即k边星是一个无向图, 除了中心点以外的顶点的度数都为1);
给定一个无向图, 顶点由1~n进行编号, 该图是由多个星星相连组成的, 问当把将各星星之间相连的边删去后, 剩余每个星星的大小是多少, 即边数;
解题思路
读完题之后就感觉这题的难点就在于怎么找到当前星星用于和其他星星相连的顶点,我们暂时称其为边界点;
我们先用邻接矩阵创建一个无向图, 并且记录每个点的入度; 然后找出一个入度为1的点作为无向图的入口, 因为入度为1的点一定与中心点相连, 所以我们可以找到一个中心点, 然后遍历该中心点的子节点, 如果子节点的入度不为1, 说明它就是边界点; 边界点不仅与当前星星的中心点相连, 还与其他相邻星星的边界点相连, 所以我们可以通过边界点来作为其他星星的入口;
思路有了之后就可以考虑算法, 本题显然用dfs更合适, 深搜时我们要记录当前点的父节点是谁, 防止死循环 (中心点没有父节点, 所以可以用-1进行代替); 还要有一个变量记录当前状态, 因为边界点和中心点的处理方式不同
//来自未来时间线: 现在我反而感觉用bfs来解决会更 好...类似于拓扑序列去处理入度; 但是正解其实可以不用搜索, 毕竟中心点的入度一定大于等于2, 连接点的入度一定等于2, 叶子节点的入度一定等于1, 这都是固定的; 具体结论可以去洛谷上看看;
神秘代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10;
int h[N], ne[N], e[N];
int idx;
int d[N];
vector<int> v;
void add(int a, int b) {
ne[idx] = h[a], e[idx] = b, h[a] = idx++;
}
void dfs(int t,int fa,int sm) {
int num = 0;
for (int i = h[t]; i != -1; i = ne[i]) {
int j = e[i];
if (sm == 0) {
if (j != fa) {
d[j] = 1;
d[t] = 1;
dfs(j, t, 1);
}
}
else {
if (d[t] == 1 && j != fa) {
dfs(j, -1, 1);
}
else {
if (d[j] != 1 && j != fa) {
num++;
dfs(j, t, 0);
}
else if (d[j] == 1)num++;
}
}
}
if (num >= 2)v.push_back(num);
}
signed main() {
memset(h, -1, sizeof h);
int n;
cin >> n;
for (int i = 1; i < n; i++) {
int a, b;
cin >> a >> b;
add(a, b);
add(b, a);
d[a]++;
d[b]++;
}
int root;
for (int i = 1; i <= n; i++) {
if (d[i] == 1) {
root = i;
break;
}
}
dfs(root,-1,1);
sort(v.begin(), v.end());
for (int x : v) {
if (x != 1) {
cout << x << ' ';
}
}
return 0;
}
F - Damage over Time
难度: ⭐⭐⭐⭐⭐
题目大意
现在有一只血量为m的怪兽, 小莫有n个技能, 每个技能有两个属性, 伤害d和持续时间t, 意思是该技能的伤害会持续作用t回合(包括当前回合); 问小莫最少用多少回合可以把怪兽的血量打到小于等于0;
解题思路
本题的难点在于我们不知道每个技能到底可以作用多少回合, 所以也就无法选择; 所以我们可以用二分来确定一共要打多少回合; 因为我们需要知道还剩x个回合时哪些技能可以打完, 哪些不行; 所以我们要预处理把所有技能按照持续时间t从小到大排序; 设当前回合为now, 二分回合为mid:
当now + ti -1 <= mid时, 这个技能可以打完, 那我们就从这些技能中找出max(di * ti);
当now + ti -1 > mid时, 这个技能打不完, 其持续时间就是mid - now + 1; 既然持续时间固定, 我们就只需要从这些技能中找出max(di)即可;
因为我们的技能是按持续时间从小到大排序, 所以对于每个二分回合mid, 一定存在一个界限k, k左边的技能可以打完, 而右边的技能打不完; 所以我们可以预处理前缀最大的(di * ti), 设为hur[i] 和后缀最大的(di)(在check函数中循环得到就行, 正好顺便找到界限k);
在check函数中, 设当前回合为l, 二分回合为u; 我们先按照界限k找出右边最大的di, 我们设为maxd; 之后k接着后前遍历, 每次遍历时, 我们可以得到最大的打完伤害hur[i]; 如果想每回合使用都可以打完伤害, 那么该技能可以用(-spl[i].t + 1)回合, 我们设为r; 在l~r这些回合中我们有两种技能选择: hur[i]和maxd, 根据不同情况我们有三种方案: 只用hur[i]; 只用maxd; 先用maxd再用hur[i]; (这里可以想一下为什么不能先用hur[i]再用maxd)
一: 因为maxd造成的总伤害是随着回合数的减少而减小; 所以在l~r这些回合中, 在第r回合maxd造成的总伤害是最小的; 如果这个伤害大于hur[i], 那我们就可以只用maxd; 其伤害变化和回合数之间的关于满足等差数列, 所以可以用等差求和公式在进行求解;
二: 和第一种情况相反, 在第l回合maxd造成的总伤害是最大的; 如果这个伤害小于hur[i], 那我们就可以只用hur[i];
三: 在第l回合时maxd造成的总伤害要比hur[i]大, 但是随着剩下的回合数越来越小, maxd造成的伤害逐渐变小知道小于hur[i]; 这种情况就需要计算出在第第几回合二者的大小关系发生了变化, 然后分两段计算;
选完之后更新造成的总伤害, 然后更新当前回合l, 把k接着往前推找到在剩下回合中可以打完伤害的技能, 同时更新maxd; 最后如果没有能打完的技能, 那么就用此时的maxd打完剩下的回合;
最重要的是数据很大, 会爆longlong, 换成__int128;
好难啊.....
神秘代码
#include<bits/stdc++.h>
#define int __int128
#define IOS ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
#define endl '\n'
using namespace std;
const int N = 1e6 + 10, mod = 998244353;
typedef pair<int, int> PII;
int n, m;
int hur[N];
int read() { char ch = getchar(); int cnt = 0; while (ch < '0' || ch>'9')ch = getchar(); while (ch >= '0' && ch <= '9')cnt = (cnt << 3) + (cnt << 1) + ch - '0', ch = getchar(); return cnt; }
void write(int x) { if (!x)return; write(x / 10), putchar(x % 10 + '0'); }
struct stu {
int t, d;
}spl[N];
bool cmp(stu a, stu b) {
return a.t < b.t;
}
bool check(int u) {
int maxd = 0, res = 0, l = 1;
int i = n;
for (; i >= 1; i--) {
if (spl[i].t > u) maxd = max(maxd, spl[i].d);
else break;
}
for (; i >= 1; i--) {
int r = u - spl[i].t + 1;
int d1 = u - l + 1, d2 = u - r + 1;
if (maxd == 0) {
res += (r - l + 1) * hur[i];
}
else {
if (hur[i] <= maxd * d2) {
res += maxd * (r - l + 1) * (d1 + d2) / 2;
}
else if (hur[i] > maxd * d1) {
res += hur[i] * (r - l + 1);
}
else {
int x = hur[i] / maxd + (hur[i] % maxd != 0);
res += maxd * (d1 - x + 1) * (d1 + x) / 2;
res += hur[i] * (x - d2);
}
}
l = r + 1;
maxd = max(maxd, spl[i].d);
while (i > 1 && spl[i-1].t > u-l+1) {
maxd = max(maxd, spl[i-1].d);
i--;
}
}
res += maxd * (u - l + 2) * (u - l + 1) / 2;
return res >= m;
}
signed main() {
n = read(), m = read();
for (int i = 1; i <= n; i++) {
spl[i].t = read(), spl[i].d = read();
}
sort(spl + 1, spl + 1 + n, cmp);
for (int i = 1; i <= n; i++) {
hur[i] = max(hur[i - 1], spl[i].t * spl[i].d);
}
int l = 0, r = m;
while (l < r) {
int mid = (l + r) >> 1;
if (check(mid)) r = mid;
else l = mid + 1;
}
write(l);
return 0;
}