ABC-282解题报告

比赛传送门

C. String Delimiter

题意:有一个包含字母、双引号(保证有偶数个,相邻两个匹配)和逗号的字符串,将在双引号外的逗号改为句号。

维护当前在双引号里还是外,遇到双引号更改即可。

By SSRS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <bits/stdc++.h>
using namespace std;
int main(){
int N;
cin >> N;
string S;
cin >> S;
bool c = false;
for (int i = 0; i < N; i++){
if (S[i] == '"'){
c = !c;
}
if (S[i] == ',' && !c){
S[i] = '.';
}
}
cout << S << endl;
}

D. Make Bipartite 2

题意:给你一个无向图,判断有多少无序点对 (x,y)(x,y) 满足之间没有边,且连接之后为二分图。

对于一个连通块,生成二分图的方式是唯一的,且加边不会导致一个非二分图变为二分图。于是只要有一个连通块不是二分图,答案一定为 00

对于不为 00 的情况,可以预处理每个连通块两边的点数,对于连通块内部的边,一定是左边连向右边,方案数为两边点数之积。对于连通块之间的边,任意连边都可以。在第一种情况中,有 (x,y)(x,y) 之间有边的情况,所以最后减去 mm 即可。正确性容易证明。

By wygz

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
const int maxn=(2e5)+10;
int n,m,cnt[2];
int col[maxn]; vector<int> g[maxn];
ll C2(ll x) { return x*(x-1)/2; }
ll ans;
void dfs(int u) {
cnt[col[u]-1]++;
for (int &v : g[u]) {
if (col[v]&&col[v]==col[u]) { puts("0"); exit(0); }
if (col[v]) continue;
col[v]=3-col[u];
dfs(v);
}
}
int main() {
read(n),read(m);
int x,y; for (int i=1;i<=m;i++) {
read(x),read(y);
g[x].push_back(y),g[y].push_back(x);
}
for (int i=1;i<=n;i++) if (!col[i]) {
cnt[0]=cnt[1]=0;
col[i]=1,dfs(i);
ans-=C2(cnt[0]),ans-=C2(cnt[1]);
}
ans-=m;
ans+=C2(n);
printf("%lld\n",ans);

return 0;
}

还有一种维护二分图的方法,将一个点 ii 拆成两个点 i,ii,i'(实现上可以用 i+ni+n),原图中连一条边 u,vu,v 时,我们连接 u,vu,v'v,uv,u'。此时维护的图中一条边 x,yx,y 表示 x,yx,y 异色。如下图:

左边的图为原图,右边为维护的图。容易发现,左边的图为二分图,而右边的图可以拆成红黑两部分。左边红色的点 1,41,4 为原二分图中的一边,左边黑色的点 2,32,3 为原二分图中的另一边。

显然,如果两个点 u,vu,v 在二分图的两侧,那么 u,vu,v 一定不连通,u,vu,v' 一定连通。于是有点 uuuu' 一定不连通。

什么时候不是二分图呢?如下图,连接 2,32,3

加上这条蓝色边后,可以发现维护的图合并成了一部分。此时点 uu 和点 uu' 变得连通,就无法产生二分图。这种二分图的表示可以很容易用并查集维护,加完边最后判断每个 uuuu' 是否连通即可。二分图两边点的个数也可以很容易地用并查集统计(红点之间相连,黑点之间相连)。

By drogskol

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
#include <bits/stdc++.h>
using namespace std;

#include <atcoder/dsu>
using namespace atcoder;

using ll=long long;

int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);

ll n,m;cin>>n>>m;
ll ans=n*(n-1)/2-m;

dsu uf(n*2);
while(m--){
int u,v;cin>>u>>v;u--;v--;
uf.merge(u,v+n);
uf.merge(v,u+n);
}

map<int,int> mp;
for(int i=0;i<n;i++){
if(uf.same(i,i+n)){
cout<<0<<endl;
return 0;
}
ans-=mp[uf.leader(i)]++;
}

cout<<ans<<endl;
}

E. Choose Two and Eat One

题意:有 nn 个有权值的球,每次可以选两个球 ai,aja_i,a_j,造成 (aiaj+ajai)modm({a_i}^{a_j}+{a_j}^{a_i})\bmod m 的贡献,并选择一个球删掉。问剩一个球时的最大贡献。

由于贡献的式子没有规律,所以不能从 aia_i 的角度贪心,而思考一下会发现 DP 状态也设不了,于是考虑其他思路。

我们将每个球 ii 被删掉的时候与别的球 jj 造成的贡献看为一条从 ii 连向 jj 的边,显然每个 ii(除了最后一个点)有一条出边,但可能有多个入边。容易发现此为一个树形结构。而两个球的贡献同样可以在图上表示出来,为一个完全图。所以,答案即为图上最大生成树的权值。将最小生成树算法反向操作即可。

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <bits/stdc++.h>
using namespace std;
long long modpow(long long x, long long y, long long M){
long long ans = 1;
while (y > 0){
if (y % 2 == 1){
ans *= x;
ans %= M;
}
x *= x;
x %= M;
y /= 2;
}
return ans;
}
struct unionfind{
vector<int> p;
unionfind(int N){
p = vector<int>(N, -1);
}
int root(int x){
if (p[x] < 0){
return x;
} else {
p[x] = root(p[x]);
return p[x];
}
}
bool same(int x, int y){
return root(x) == root(y);
}
void unite(int x, int y){
x = root(x);
y = root(y);
if (x != y){
if (p[x] < p[y]){
swap(x, y);
}
p[y] += p[x];
p[x] = y;
}
}
};
int main(){
int N, M;
cin >> N >> M;
vector<int> A(N);
for (int i = 0; i < N; i++){
cin >> A[i];
}
vector<tuple<int, int, int>> E;
for (int i = 0; i < N; i++){
for (int j = i + 1; j < N; j++){
E.push_back(make_tuple((modpow(A[i], A[j], M) + modpow(A[j], A[i], M)) % M, i, j));
}
}
sort(E.begin(), E.end(), greater<tuple<int, int, int>>());
unionfind UF(N);
long long ans = 0;
for (int i = 0; i < N * (N - 1) / 2; i++){
int c = get<0>(E[i]);
int u = get<1>(E[i]);
int v = get<2>(E[i]);
if (!UF.same(u, v)){
UF.unite(u, v);
ans += c;
}
}
cout << ans << endl;
}