ABC-277解题报告

比赛传送门

B. Playing Cards Validation

题意:有 nn 个长度为 22 的字符串,判断是否满足以下条件:

  1. 第一个字符为 HDCS 之一。
  2. 第二个字符为 A23456789TJQK 之一。
  3. 字符串两两不同。

一个模拟题。可以将两个字符可能的选择分别记录下来,循环一遍判断是否为其中之一或调用 count 函数;也可以直接写 if 判断。判断两两不同可以 n2n^2 枚举,也可以用 set 或排序加 unique 去重。

By cxm1024

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<bits/stdc++.h>
using namespace std;
string a="HDCS",b="A23456789TJQK";
void NO() {puts("No");exit(0);}
set<string> st;
signed main() {
int n;
cin>>n;
for(int i=1;i<=n;i++) {
string s;
cin>>s;
bool flag=0;
for(char c:a) if(s[0]==c) flag=1;
if(!flag) NO();
flag=0;
for(char c:b) if(s[1]==c) flag=1;
if(!flag) NO();
st.insert(s);
}
if(st.size()!=n) NO();
puts("Yes");
return 0;
}

By yuiop

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<stdio.h>
int main(){
int n,h=1,i,j,u[550]={};
char s[]={"HDCS"},t[]={"A23456789TJQK"};
scanf("%d",&n);
while(n--){
scanf(" %c%c",&s[4],&t[13]);
for(i=0;s[i]!=s[4 ];i++);
for(j=0;t[j]!=t[13];j++);
if(i==4||j==13)h=0;
if(u[i*100+j])h=0;
u[i*100+j]=1;
//printf("%d\n",h);
}
printf("%s\n",h?"Yes":"No");
return 0;
}

这份代码是使用一个指针,一直扫到匹配。然而将待判断字符串与合法字符集混在一起的写法容易产生混淆,并不利于代码可读性和调试难度。写代码时每个部分的用途要尽量明确,不要有一个结构存多个意义的情况。

修改后可以为:

fixed

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<bits/stdc++.h>
using namespace std;
void NO() {puts("No");exit(0);}
string a="HDCS",b="A23456789TJQK";
set<string> st;
signed main() {
int n;
cin>>n;
for(int i=1;i<=n;i++) {
string s;
cin>>s;
int x=0,y=0;
while(s[0]!=a[x]&&x<a.size()) x++;
while(s[1]!=b[y]&&y<b.size()) y++;
if(x==a.size()||y==b.size()) NO();
st.insert(s);
}
if(st.size()!=n) NO();
puts("Yes");
return 0;
}

By hitonanode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void bad() {
puts("No");
exit(0);
}

int main() {
int N;
cin >> N;
set<string> se;
REP(_, N) {
string s;
cin >> s;
const string suit = "HDCS";
const string num = "A23456789TJQK";
if (!count(ALL(suit), s.front())) bad();
if (!count(ALL(num), s.back())) bad();
se.insert(s);
}
if (se.size() < N) bad();
puts("Yes");
}

这份代码使用 count 函数进一步简化过程。

C. Ladder Takahashi

题意:给你一张 nn 个点 mm 条边的无向图,问点 11 能到达的点中编号最大的。n109,m2×105n\le 10^9,m\le 2\times 10^5

很简单的判断连通性问题,可以 DFS 也可以 BFS。主要需要处理的问题在于,点的编号可以很大,常规存图方式不可行。有以下两种方法处理:

离散化

By SSRS

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
#include <bits/stdc++.h>
using namespace std;
int main(){
int N;
cin >> N;
vector<int> A(N), B(N);
for (int i = 0; i < N; i++){
cin >> A[i] >> B[i];
}
vector<int> X;
X.push_back(1);
for (int i = 0; i < N; i++){
X.push_back(A[i]);
X.push_back(B[i]);
}
sort(X.begin(), X.end());
X.erase(unique(X.begin(), X.end()), X.end());
int M = X.size();
for (int i = 0; i < N; i++){
A[i] = lower_bound(X.begin(), X.end(), A[i]) - X.begin();
B[i] = lower_bound(X.begin(), X.end(), B[i]) - X.begin();
}
vector<vector<int>> E(M);
for (int i = 0; i < N; i++){
E[A[i]].push_back(B[i]);
E[B[i]].push_back(A[i]);
}
vector<bool> used(M, false);
used[0] = true;
queue<int> Q;
Q.push(0);
while (!Q.empty()){
int v = Q.front();
Q.pop();
for (int w : E[v]){
if (!used[w]){
used[w] = true;
Q.push(w);
}
}
}
int ans = 0;
for (int i = 0; i < M; i++){
if (used[i]){
ans = max(ans, X[i]);
}
}
cout << ans << endl;
}

使用 map 处理

By cxm1024

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<bits/stdc++.h>
using namespace std;
map<int,vector<int> > a;
map<int,bool> vis;
int ans=0;
void dfs(int now) {
if(vis[now]) return;
vis[now]=1,ans=max(ans,now);
auto &to=a[now];
for(int v:to) dfs(v);
}
signed main() {
int n;
cin>>n;
for(int i=1;i<=n;i++) {
int x,y;
cin>>x>>y;
a[x].push_back(y);
a[y].push_back(x);
}
dfs(1);
cout<<ans<<endl;
return 0;
}

D. Takahashi’s Solitaire

题意:有 nn 张卡片,卡片上数字的值域为 [0,m)[0,m),你需要选择若干张卡片放到桌子上,要求放的卡片的数字等于上一次放的数字,或等于上一次放的数字 +1+1modm\bmod m。最小化剩余的卡片的数字和。

转化为最大化放的卡片的数字和。首先将卡片排序。一个显然的结论是,每次选一个数字时必然要全部选完。实现上有以下几种处理方法:

断环为链

由于值域上 mm00 连续,可能形成环,所以将排好序的数组复制两遍,断环为链。循环时维护当前尽可能长的连续段和即可。连续段的数字和计算可以使用前缀和加速。无需特判长度是否超过 nn,因为此时数字和也会超过总数字和,只需要将答案和 00max\max 即可。

By cxm1024

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<bits/stdc++.h>
using namespace std;
#define int long long
int a[400010];
signed main() {
int n,m,sum=0;
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>a[i];
sort(a+1,a+n+1);
for(int i=n+1;i<=n+n;i++)
a[i]=a[i-n],sum+=a[i];
int now=a[1],ans=0;
for(int i=2;i<=n+n;i++) {
if(a[i-1]!=a[i]&&(a[i-1]+1)%m!=a[i]) {
ans=max(ans,now);
now=a[i];
}
else now+=a[i];
}
ans=max(ans,now);
cout<<max(0ll,sum-ans)<<endl;
return 0;
}

环上记录断点

直接在环上处理,记录每一个不连续的位置,扫一遍统计答案。相邻两个断点之间即为连续段。

By SSRS

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
#include <bits/stdc++.h>
using namespace std;
int main(){
int N, M;
cin >> N >> M;
vector<int> A(N);
for (int i = 0; i < N; i++){
cin >> A[i];
}
sort(A.begin(), A.end());
long long sum = 0;
for (int i = 0; i < N; i++){
sum += A[i];
}
vector<int> p;
for (int i = 0; i < N; i++){
int j = (i + N - 1) % N;
if (A[j] != (A[i] + M - 1) % M && A[j] != A[i]){
p.push_back(i);
}
}
if (p.empty()){
cout << 0 << endl;
} else {
int cnt = p.size();
long long mx = 0;
for (int i = 0; i < cnt; i++){
int r = p[(i + 1) % cnt];
if (i == cnt - 1){
r += N;
}
long long tmp = 0;
for (int j = p[i]; j < r; j++){
tmp += A[j % N];
}
mx = max(mx, tmp);
}
cout << sum - mx << endl;
}
}

循环移位

jm0ij\to m\to 0\to i 的一整个连续段放到最后,从 i+1i+1 开始处理到 ii 为止。

By kotatsugame

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
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int N,M;
int A[2<<17];
main()
{
cin>>N>>M;
for(int i=0;i<N;i++)cin>>A[i];
sort(A,A+N);
int id;
if((A[N-1]+1)%M!=A[0])id=0;
else
{
id=1;
while(id<N&&A[id-1]+1>=A[id])id++;
if(id==N)
{
cout<<0<<endl;
return 0;
}
}
long ans=0;
for(int i=0;i<N;)
{
int j=i+1;
long now=A[(id+i)%N];
while(j<N)
{
int y=A[(id+j)%N];
int x=A[(id+j-1)%N];
if(y==x||y==(x+1)%M)
{
now+=y;
j++;
}
else break;
}
i=j;
ans=max(ans,now);
}
for(int i=0;i<N;i++)ans-=A[i];
cout<<-ans<<endl;
}

并查集

用并查集维护连通的值域,维护每个块的数字和,最后找最大的块即可。

By kzyKT

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
ll p[200001],r[200001],c[200001];
void init(ll n){rep(i,n)p[i]=i,r[i]=0,c[i]=0;}
int find(int x){return (p[x]==x)?x:(p[x]=find(p[x]));}
void unite(int x,int y) {
x=find(x),y=find(y);
if(x==y)return;
ll cx=c[x],cy=c[y];
if(r[x]<r[y])p[x]=y;
else{p[y]=x;if(r[x]==r[y])r[x]++;}
c[find(x)]=cx+cy;
}
bool same(int x,int y){return find(x)==find(y);}

void Main() {
ll n,m;
cin >> n >> m;
ll a[n];
rep(i,n) R a[i];
init(n);
map<ll,vector<ll>> ma;
rep(i,n) {
ma[a[i]].pb(i);
}
ll sum=0;
rep(i,n) {
c[i]=a[i];
sum+=a[i];
}
tr(it,ma) {
REP(i,1,it->S.size()) unite(it->S[0],it->S[i]);
if(ma.count((it->F+1)%m)) unite(it->S[0],ma[(it->F+1)%m][0]);
}
ll ans=sum;
rep(i,n) {
if(find(i)==i) ans=min(ans,sum-c[i]);
}
pr(ans);
}

int main(){ios::sync_with_stdio(0);cin.tie(0);Main();return 0;}

遍历 map

用 map 存下每个数字及出现次数,在 map 上遍历,遇到断点就扫到下一个断点,没有断点特判以下即可。

By waltz

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
55
using namespace std;
map<int, long long> ans;
int main()
{
#ifdef absi2011
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
#endif
int n,m;
scanf("%d%d",&n,&m);
int i;
long long sum_ans = 0;
for (i=0;i<n;i++)
{
int x;
scanf("%d",&x);
ans[x % m] += x;
sum_ans += x;
}
long long max_dec = 0;
map<int, long long>::iterator ii;
for (ii = ans.begin(); ii != ans.end(); ii++)
{
int x = (*ii).first;
if (ans.find((x-1+m)%m) == ans.end())
{
long long dec = (*ii).second;
map<int, long long>::iterator jj = ii;
for (;;)
{
jj ++;
if (jj == ans.end())
{
jj = ans.begin();
}
if ((*jj).first == ((x + 1) % m))
{
dec += (*jj).second;
x = (*jj).first;
}
else
{
break;
}
}
max_dec = max(max_dec, dec);
}
}
if (max_dec == 0)
{
max_dec = sum_ans;
}
printf("%lld\n", sum_ans - max_dec);
return 0;
}

E. Crystal Switches

题意:有一张图,边有 01 两种颜色,只能走颜色 1 的边。有 kk 个特殊点,当你到达特殊点时,可以选择将图中每条边的颜色取反(也可以不取反)。问从 11 号点走到 nn 号点的最短路(取反不算一步)。

将图中每条边的颜色取反,可以看成将当前能走的颜色取反,于是在普通最短路的基础上修改一下即可。记录状态的时候不仅要记录当前的点和距离,还要记录当前能走的颜色。转移时,若当前点不为特殊点,则只能通过可走的边转移到下一个点,否则能通过所有边转移到下一个点。用 Dijkstra 和 BFS 均可。

Dijkstra

Dijkstra 的做法比较显然但较难写:

By cxm1024

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
#include<bits/stdc++.h>
using namespace std;
vector<pair<int,bool> > a[200010];
bool swt[200010];
struct node {
int x,val,flag;
bool operator<(const node &o)const {
return val>o.val;
}
};
priority_queue<node> q;
bool done[200010][2];
int ans[200010][2];
void dijkstra() {
q.push({1,0,0});
while(!q.empty()) {
auto [u,w,flag]=q.top();q.pop();
if(done[u][flag]) continue;
done[u][flag]=1,ans[u][flag]=w;
for(auto v:a[u]) {
if(done[v.first][1-v.second]) continue;
if(v.second^flag) q.push({v.first,w+1,flag});
else if(swt[u]) q.push({v.first,w+1,1-flag});
}
}
}
signed main() {
int n,m,k;
cin>>n>>m>>k;
for(int i=1;i<=m;i++) {
int u,v,x;
cin>>u>>v>>x;
a[u].push_back({v,x});
a[v].push_back({u,x});
}
for(int i=1;i<=k;i++) {
int x;
cin>>x;
swt[x]=1;
}
dijkstra();
if(!done[n][0]&&!done[n][1]) cout<<-1<<endl;
else if(done[n][0]&&done[n][1]) cout<<min(ans[n][0],ans[n][1])<<endl;
else cout<<ans[n][0]*done[n][0]+ans[n][1]*done[n][1]<<endl;
return 0;
}

01BFS

用 01BFS 来处理最短路。对于特殊点,我们可以看做该点向自己连一条权值为 00 的边,作用为翻转当前能走的颜色。在实现中即为 push_front “翻转颜色后的自己”到队列首。

By kotatsugame

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
#include<iostream>
#include<vector>
#include<deque>
using namespace std;
int N,M,K;
bool S[2<<17];
int D[2<<17][2];
vector<int>G[2<<17][2];
main()
{
cin>>N>>M>>K;
for(int i=0;i<M;i++)
{
int u,v,a;cin>>u>>v>>a;
u--,v--;
G[u][a].push_back(v);
G[v][a].push_back(u);
}
for(int i=0;i<K;i++)
{
int s;cin>>s;
S[s-1]=true;
}
for(int i=0;i<N;i++)D[i][0]=D[i][1]=1e9;
D[0][1]=0;
deque<pair<int,int> >Q;
Q.push_back(make_pair(0,1));
while(!Q.empty())
{
int u=Q.front().first,f=Q.front().second;
Q.pop_front();
if(S[u])
{
if(D[u][!f]>D[u][f])
{
D[u][!f]=D[u][f];
Q.push_front(make_pair(u,!f));
}
}
for(int v:G[u][f])if(D[v][f]>D[u][f]+1)
{
D[v][f]=D[u][f]+1;
Q.push_back(make_pair(v,f));
}
}
int ans=min(D[N-1][0],D[N-1][1]);
if(ans==(int)1e9)ans=-1;
cout<<ans<<endl;
}

Dijkstra+虚点

我们对于每个点 ii 建立一个对应点 i+ni+n,为翻转当前颜色后的点。对于颜色为 1 的边,只能在初始颜色的情况下走,所以连接两个点 i,ji,j。对于颜色为 0 的边,只能在翻转颜色后的图上走,所以连接 i+n,j+ni+n,j+n。于是我们得到了两个完全独立的图。而特殊点的作用就是连接这两个图。对于每个特殊点 ii,连接 i,i+ni,i+n,权值为 0 即可。

By hitonanode

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
int main() {
int N, M, K;
cin >> N >> M >> K;

shortest_path<int> graph(N * 2);
REP(e, M) {
int u, v, a;
cin >> u >> v >> a;
--u, --v;
if (a) {
graph.add_bi_edge(u, v, 1);
} else {
graph.add_bi_edge(u + N, v + N, 1);
}
}
while (K--) {
int s;
cin >> s;
--s;
graph.add_bi_edge(s, N + s, 0);
}

graph.solve(0);
int ret = min(graph.dist[N - 1], graph.dist[N * 2 - 1]);
if (ret > 1 << 25) ret = -1;
cout << ret << endl;
}