The 2021 ICPC Asia Shenyang Regional Contest
B - Bitwise Exclusive-OR Sequence
题意
\(n\)个数,\(m\)个关系,每个关系形如 \(a_u⊕a_v=w\),表示第\(u\)个数与第\(v\)数的异或运算结果为\(w\)。求是否有这样的\(n\)个数满足所有关系要求,如果没有输出\(-1\),如果有输出所有满足要求的方案中,所有数字的和的最小值。
思路
建图,处理每个连通块,选取源点赋值为\(0\)(因为要求最小),途中出现矛盾直接输出\(-1\),否则从低位到高位统计每一位上\(0\)和\(1\)的出现次数,最后相加计算最小值。
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mxn = 1e6 + 5;
int n, m, idx;
int to[mxn], nxt[mxn], head[mxn], edge[mxn], point[mxn], pre[mxn], sz[mxn];
void add(int u, int v, int w)
{
to[++idx] = v;
edge[idx] = w;
nxt[idx] = head[u];
head[u] = idx;
}
void init()
{
fill(point, point + n, -1);
fill(sz, sz + n, 1);
iota(pre, pre + n, 0);
}
int find(int x)
{
return (x == pre[x] ? x : pre[x] = find(pre[x]));
}
void join(int u, int v)
{
int fu = find(u);
int fv = find(v);
if (fu != fv)
{
pre[fu] = fv;
sz[fv] += sz[fu];
}
}
void solve()
{
cin >> n >> m;
init();
if (!m)
{
cout << 0 << endl;
return;
}
for (int i = 0; i < m; i++)
{
int u, v, w;
cin >> u >> v >> w;
u--, v--;
add(u, v, w);
add(v, u, w);
join(u, v);
}
int ans = 0;
for (int i = 0; i < n; i++)
{
if (find(i) != i)
{
continue;
}
vector<int> cnt(30);
queue<int> q;
q.push(i);
point[i] = 0;
while (q.size())
{
int u = q.front();
q.pop();
for (int j = head[u]; j; j = nxt[j])
{
int v = to[j];
if (point[v] == -1)
{
point[v] = (point[u] ^ edge[j]);
q.push(v);
for (int k = 0; k < 30; k++)
{
cnt[k] += ((point[v] >> k) & 1); // 统计1的个数
}
}
else
{
if ((point[u] ^ point[v]) != edge[j]) // 前后矛盾
{
cout << -1 << endl;
return;
}
}
}
}
for (int j = 0; j < 30; j++)
{
ans += (1LL << j) * min(cnt[j], sz[i] - cnt[j]); // 二进制加法,cnt是1的个数,sz-cnt是0的个数,min(cnt[j], sz[i] - cnt[j])其实是当前连通快中第j位的贡献
}
}
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
//cin >> T;
while (T--)
{
solve();
}
return 0;
}
E - Edward Gaming, the Champion
题意
给个字符串,找有几个\(edgnb\)。
思路
模拟。
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
void solve()
{
string s;
cin >> s;
if (s.size() < 5)
{
cout << 0 << endl;
return;
}
int ans = 0;
for (int i = 0; i <= s.size() - 5; i++)
{
if (s[i] == 'e' && s[i + 1] == 'd' && s[i + 2] == 'g' && s[i + 3] == 'n' && s[i + 4] == 'b')
{
ans++;
}
}
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
//cin >> T;
while (T--)
{
solve();
}
return 0;
}
F - Encoded Strings I
题意
给定一个长度为\(n(n≤1000)\)的字符串\(s\),定义函数\(Fs:Fs(c)=chr(G(c, S))\),表示将\(S\)中的每个字符\(c\)转换为\(chr(G(c, S))\),其中\(G(c, S)\)表示\(S\)中最后一次出现\(c\)之后的后缀中不同的字符个数,\(chr(i)\)表示第\(i\)个字符(第个\(0\)字符是 \(a\),第\(1\)个是\(b\)…)。要求你对\(s\)的每个非空前缀代入到函数中,得到\(n\)个字符串,输出按照字典序排序的最大字符串。
思路
模拟。
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mxn = 1e3 + 5;
unordered_map<char, int> cnt[mxn];
void solve()
{
int n;
string s;
cin >> n >> s;
for (int i = n; i >= 1; i--)
{
int cntt = 0;
unordered_set<char> vis;
for (int j = i - 1; j >= 0; j--)
{
if (!vis.count(s[j]))
{
vis.insert(s[j]);
cnt[i][s[j]] = cntt++;
}
}
}
set<string, greater<string>> ans;
for (int i = n; i >= 1; i--)
{
string t = s.substr(0, i);
for (int j = i - 1; j >= 0; j--)
{
t[j] = cnt[i][t[j]] + 'a';
}
ans.insert(t);
}
cout << *ans.begin() << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
//cin >> T;
while (T--)
{
solve();
}
return 0;
}
J - Luggage Lock
题意
四位密码锁,每次能把一位或连续的几位向上或向下转动一个单位。现给两个密码\(A\)和\(B\),求从\(A\)到\(B\)的最少操作数。
思路
\(T\)挺大,一个个来铁超时,直接从\(0000\)开始用\(bfs\)打表,查的时候就可以等价于查\(0000\)\(A\)和\(B\)之间的差值,即查\(1234\)到\(4689\)相当于查\(0000\)到\(3455\)。
代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
typedef pair<string, int> pii;
map<string, int> ans;
set<string> vis;
string cmd[] = { "1000","0100", "0010", "0001", "1100", "0110", "0011", "1110","0111", "1111", "2000","0200", "0020", "0002", "2200", "0220", "0022", "2220", "0222", "2222" }; // 1代表+,2代表-
string change(string s, int idx)
{
string res = s;
for (int i = 0; i < 4; i++)
{
if (cmd[idx][i] == '1')
{
res[i]++;
if (res[i] > '9')
{
res[i] = '0';
}
}
else if (cmd[idx][i] == '2')
{
res[i]--;
if (res[i] < '0')
{
res[i] = '9';
}
}
}
return res;
}
void bfs()
{
queue<pii> q;
q.push({ "0000", 0 });
vis.insert("0000");
while (q.size())
{
string cur = q.front().first;
int cnt = q.front().second;
q.pop();
for (int i = 0; i < 20; i++) // 把cur的全部情况都存了
{
string t = change(cur, i);
if (!vis.count(t))
{
vis.insert(t);
ans[t] = cnt + 1;
q.push({ t, cnt + 1 });
}
}
}
}
void solve()
{
string a, b;
cin >> a >> b;
if (a == b)
{
cout << 0 << endl;
return;
}
string t = "";
for (int i = 0; i < 4; i++)
{
t.push_back((b[i] - a[i] + 10) % 10 + '0');
}
cout << ans[t] << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
bfs();
while (T--)
{
solve();
}
return 0;
}
H - Line Graph Matching
题意
给定简单无向加权连通图\(G\),计算其线图\(L(G)\)的最大加权匹配中所有边的权重之和。
线图\(L(G)\):每个顶点代表\(G\)的一条边,\(L(G)\)中的边表示\(G\)中两条边共享一个端点的情况,其权重为这两条边权重的和。
最大加权匹配:一种边的集合,其中任意两条边没有共同的顶点,且该集合中边的权重之和最大化。
思路
对于每个连通块,如果边数为偶数,一定能匹配完;否则,要删一条边,如果删的是割边,要保证删完两边连通块边数都是偶数,删的不是割边则没有限制。
一种思路是用\(tarjan\)求割边,如果要删就删最小边(戳这里看参考博客),还有一种思路是贪心,每次选权最大的边加入(戳这里看参考博客)。
这就是🥈题,\(What\ can\ I\ say\)。
代码一
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mxn = 1e6 + 5;
int n, m, idx, tot, ans, minn, ttot;
int to[mxn], nxt[mxn], head[mxn], edge[mxn], dfn[mxn], low[mxn], sz[mxn], deg[mxn];
void add(int u, int v, int w)
{
to[++idx] = v;
edge[idx] = w;
nxt[idx] = head[u];
head[u] = idx;
}
void tarjan(int u, int pre)
{
dfn[u] = low[u] = ++tot;
sz[u] = deg[u];
for (int i = head[u]; i; i = nxt[i])
{
int v = to[i];
if (!dfn[v])
{
tarjan(v, u);
sz[u] += sz[v];
low[u] = min(low[u], low[v]);
if (low[v] > dfn[u]) // 割边
{
if ((sz[v] - 1) / 2 % 2 == 0) // 删边后边数是偶数
{
minn = min(minn, edge[i]);
}
}
else
{
minn = min(minn, edge[i]);
}
}
else if (v != pre)
{
low[u] = min(low[u], dfn[v]);
minn = min(minn, edge[i]);
}
if (dfn[u] < dfn[v]) // 该连通块内的边数
{
ttot++;
}
}
}
void solve()
{
cin >> n >> m;
for (int i = 1; i <= m; i++)
{
int u, v, w;
cin >> u >> v >> w;
add(u, v, w);
add(v, u, w);
deg[u]++;
deg[v]++;
ans += w;
}
for (int i = 1; i <= n; i++)
{
if (!dfn[i])
{
ttot = 0;
minn = LLONG_MAX;
tarjan(i, -1);
if (ttot & 1) // 奇数要删一条最小边
{
ans -= minn;
}
}
}
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
//cin >> T;
while (T--)
{
solve();
}
return 0;
}
代码二
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mxn = 1e6 + 5;
struct edge
{
int u, v, w;
bool operator < (const edge& a) const
{
return w > a.w;
}
};
int n, m, ans;
vector<edge> e;
int pre[mxn], w[mxn];
int find(int x)
{
return (x == pre[x] ? x : pre[x] = find(pre[x]));
}
void solve()
{
cin >> n >> m;
iota(pre + 1, pre + 1 + n, 1);
e.resize(m + 1);
for (int i = 1; i <= m; i++)
{
cin >> e[i].u >> e[i].v >> e[i].w;
}
sort(e.begin() + 1, e.end()); // 按边权降序排序
for (int i = 1; i <= m; i++)
{
int fu = find(e[i].u);
int fv = find(e[i].v);
if (fu == fv)
{
if (!w[fu])
{
w[fu] = e[i].w;
}
else
{
ans += e[i].w + w[fu];
w[fu] = 0;
}
}
else
{
pre[fv] = fu;
if (!w[fu] && !w[fv])
{
w[fu] = e[i].w;
}
else if (!w[fu] || !w[fv])
{
ans += e[i].w + w[fu] + w[fv];
w[fu] = 0;
}
else
{
ans += e[i].w + max(w[fu], w[fv]);
w[fu] = min(w[fu], w[fv]);
}
}
}
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
int T = 1;
//cin >> T;
while (T--)
{
solve();
}
return 0;
}