AtCoder Beginner Contest 319

【题解】 AtCoder Beginner Contest 319

没有写 F,不确定我的做法对不对。
评价:什么牛逼场次,代码大赛是嘛,从 A 开始就感觉到不对了,而且题面写的真答辩。

A.Legendary Players

题目分析:

直接按题目模拟即可。

代码:

点击查看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<bits/stdc++.h>
using namespace std;
int main(){
string s;
cin>>s;
if(s == "tourist") printf("3858\n");
if(s == "ksun48") printf("3679\n");
if(s == "Benq") printf("3658");
if(s == "Um_nik") printf("3648");
if(s == "apiad") printf("3638");
if(s == "Stonefeang") printf("3630");
if(s == "ecnerwala") printf("3613");
if(s == "mnbvmar") printf("3555");
if(s == "newbiedmy") printf("3516");
if(s == "semiexp") printf("3481");
return 0;
}

B.Measure

题目分析:

直接枚举 i,ji,j 然后判断一下就好.

代码:

点击查看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;scanf("%d",&n);
for(int i=0; i<=n; i++){
char s = '-';
for(int j=1; j<=9; j++){
if(n % j != 0) continue;
if(i % (n/j)== 0){
s = '1' + j - 1;
break;
}
}
cout<<s;
}
return 0;
}

C.False Hope

题目分析:

一个比较好写的写法就是:直接枚举看到的顺序。
然后枚举 i<j<ki < j < k,若 pi,pj,pkp_i,p_j,p_k 这三个位于同一条线然后就判断一下是不是值满足条件即可。
对于判断同一条线,行和列是简单的,同一条对角线就是 x+yx+yxyx-y 为定值。

代码:

点击查看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include<bits/stdc++.h>
#define PII pair<int,int>
using namespace std;
PII pos[100];
int a[100],p[100],tot,ans;
bool vis[100];
void chk(){
++tot;
for(int i=1; i<=9; i++){
for(int j=i+1; j<=9; j++){
for(int k=j+1; k<=9; k++){
if(a[p[i]] == a[p[j]] && a[p[j]] != a[p[k]]){
PII b[4] = {pos[p[i]],pos[p[j]],pos[p[k]]};
bool flag = false;
if(b[0].first + b[0].second == b[1].first + b[1].second && b[0].first + b[0].second == b[2].first + b[2].second) flag = true;
if(b[0].first - b[0].second == b[1].first - b[1].second && b[0].first - b[0].second == b[2].first - b[2].second) flag = true;
if(b[0].first == b[1].first && b[0].first == b[2].first) flag = true;
if(b[0].second == b[1].second && b[0].second == b[2].second) flag = true;
if(!flag) continue;
ans += 1;
return;
}
}
}
}
}
void dfs(int now){
if(now == 10){
chk();
return;
}
for(int i=1; i<=9; i++){
if(vis[i]) continue;
p[now] = i; vis[i] = true;
dfs(now+1);
vis[i] = false;
}
}
int main(){
freopen("in.txt","r",stdin);
freopen("out.txt","w",stdout);
for(int i=1; i<=3; i++){
for(int j=1; j<=3; j++){
scanf("%d",&a[(i-1)*3 + j]);
pos[(i-1)*3 + j] = {i,j};
}
}
dfs(1);
double tmp = tot;
tmp = 1.0 * ans / tmp;
printf("%.9f",1 - tmp);
return 0;
}

D.Minimum Width

题目分析:

所占的行数显然随着宽度的增加而单调不增,所以可以直接二分宽度。
判断占了多少行就是根据题目进行模拟。

代码:

点击查看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6+5;
int n,m,l[N];
int get(int len){
int now = len;
int cnt = 1;
for(int i=1; i<=n; i++){
if(l[i] > len) return m + 1;
if(l[i] > now) now = len,++cnt;
if(l[i] <= now) now = now - l[i] - 1;
}
return cnt;
}
signed main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
scanf("%lld%lld",&n,&m);
for(int i=1; i<=n; i++) scanf("%lld",&l[i]);
int L = 1,R = 1e15,ans = 0;
while(L <= R){
int mid = (L + R) >> 1;
if(get(mid) <= m){
ans = mid;
R = mid - 1;
}
else L = mid + 1;
}
printf("%lld\n",ans);
return 0;
}

E.Bus Stops

题目分析:

一个想法就是建分层图,对于每一个点拆成模 pip_i0,1,2,pi10,1,2,p_i-1pip_i 个点,但是不同车站之间无法连边。
所以考虑将每一个点拆成模 lcm(p1,p2,p3,)lcm(p_1,p_2,p_3,\cdots) 下的几个点,这样不同车站之间的边就很好连了。
但是其实建分层图是个很傻的行为,因为车站之间只能通过公交车相互到达,所以可以直接枚举 lcm(p1,p2,p3,)lcm(p_1,p_2,p_3,\cdots) 个起始时间,然后贪心地得到答案即可。
这里的 lcmlcm 经过计算不会超过 840840

代码:

点击查看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5+5;
int n,f[850],p[N],t[N];
int solve(int now){
int ans = 0;
for(int i=1; i<n; i++){
int tmp = p[i] - now % p[i];
if(tmp == p[i]) tmp = 0;
now = (now + tmp + t[i])%840;
ans = ans + tmp + t[i];
}
return ans;
}
signed main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
int x,y;scanf("%lld%lld%lld",&n,&x,&y);
for(int i=1; i<n; i++) scanf("%lld%lld",&p[i],&t[i]);
for(int i=0; i<=839; i++) f[i] = solve(i);
int q;scanf("%lld",&q);
while(q--){
int k;scanf("%lld",&k);
printf("%lld\n",f[(k+x)%840] + x + y + k);
}
return 0;
}

G.Counting Shortest Paths

题目分析:

注意到我们只需要在所有的边都被删完了,再进行询问。(我一开始以为每一次删边都需要询问,然后就被卡卡卡)

所以可以考虑直接从小到大枚举最短路长度,也就是从 11 开始一步步扩展直到扩展到 nn

所以一个直观的想法就是一步步预处理出 f[i][j]f[i][j] 表示 11ii 路径长度为 jj 的方案数,O(n)O(n) 转移显然。

考虑优化,f[i][j]f[i][j] 的第二维显然只有当 jj11ii 的最短路时才有意义,所以可以将其它的所有忽略,总状态数变成了 O(n)O(n)

对于转移,因为是现在的点对其余所有的点产生贡献,所以可以直接一起转移,只有 O(m)O(m) 次转移才是需要特殊注意的,所以只需要管这 O(m)O(m) 次转移,转移的总复杂度为 O(m)O(m)

nmnm 同阶,则总时间复杂度为 O(n)O(n)

代码中为了好写,使用了 set 复杂度会变成 O(nlogn)O(n \log n)

代码实现细节的话就是考虑我们使用 set 只维护有用的状态(就是代码里记录的 cur),也就是最短路长度等于枚举值的点,那么考虑这些有效的状态可以转移到哪些点呢。
这些有效的状态可以转移到的点就是之前没有被作为有用状态的点(就是代码里记录的 lst),因为如果一个点被当作有效状态了那么就意味着它的最短路长度一定不会低于我们当前枚举的值,那么再用当前枚举的值去更新这些点更新出来的就一定不是最短路。
然后最后更新 cur 和 lst 数组的时候,因为边操作边遍历在 set 上是很困难的,所以考虑先用 tmp 数组记录下 lst,因为我们可能转移到的点只有 lst 里的点,然后将 lst 与 cur 清空,之后直接遍历 tmp 然后对于一个点 ii 我们记录了一个 totitot_i 代表有多少个 cur 内的点通过 mm 条给定边转移到 ii,显然如果 toti=res2tot_i = res2,也就是所有转移到 ii 的点都是通过给定的 mm 条边转移过去的,也就是没有一个点是真实转移到它的,也就是其依然没有被更新,应放到 lst;反之则应放到 cur。
代码里还有的一点细节就是我们的 E[u]E[u] 记录的就是与 uu 相连的 mm 条边的另一个端点,这里要求另一个端点必须没有被更新到过,因为如果被更新到了那么显然这两者之间的转移就没有任何意义。

代码:

点击查看代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5+5;
const int MOD = 998244353;
set<int> E[N],cur,lst,tmp;
int cnt[N],a[N],tot[N];
signed main(){
int n,m;scanf("%lld%lld",&n,&m);
for(int i=1; i<=m; i++){
int u,v;scanf("%lld%lld",&u,&v);
E[u].insert(v);
E[v].insert(u);
}
cnt[n] = 1;
cur.insert(n);
for(int i=1; i<n; i++) lst.insert(i);
while(!cur.empty()){
int res = 0,res2 = 0;
for(auto now : cur) res = (res + cnt[now])%MOD,res2++;
//因为每个点 now 转移到以后的点的贡献都是 cnt[now],所以 res 记录的就是转移造成的总贡献,当前是不考虑 m 条边影响下的
//res2 就是为了方便判断一个点是否被转移到,记录的总共有多少个点可以转移到以后的点
for(auto to : lst) cnt[to] = (cnt[to] + res)%MOD,tot[to] = 0; //这里就是不考虑 m 条边情况下的直接转移
for(auto now : cur){ //枚举每一条给定边,然后将通过这一条边的转移造成的贡献删掉
for(auto to : E[now]){
if(lst.find(to) != lst.end()) //这个 if 是为了防止 to 也在 cur 中直接减就会有影响
cnt[to] = (cnt[to] - cnt[now] + MOD)%MOD,tot[to]++;
}
}
for(auto now : cur){ //因为此时 cur 内的点就都被更新到了,所以就相当于要将 cur 这些点删除
int tot = 0;
for(auto to : E[now]) a[++tot] = to;
for(int i=1; i<=tot; i++){ //这里就是为了 E 内只维护有用的点
E[a[i]].erase(now);
E[now].erase(a[i]);
}
}
swap(tmp,lst); //这里就是更新下一轮的 cur 和 lst,这里本质就是进行了一个枚举的最短路长度加 1,只不过不需要记录长度的值
cur.clear(),lst.clear();
for(auto i : tmp){
if(tot[i] != res2){
cur.insert(i);
if(i == 1){
printf("%lld\n",cnt[1]);
return 0;
}
}
else lst.insert(i);
}
tmp.clear();
}
printf("-1\n");
return 0;
}
作者

linyihdfj

发布于

2023-09-10

更新于

2023-09-14

许可协议