2021 ICPC 沈阳区域赛 BEFHJL
B - Bitwise Exclusive-OR Sequence
种类并查集。
根据每一对的异或关系,可以得到二进制中每一位是否互斥关系,涉及到两种关系的处理用种类并查集更好维护;另外再维护两个点之间是否有关系,之后可能形成多个关系的集合,每个集合又分成了互斥的两个部分,对少的那一堆赋值为 \(1\) 即可。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using i64 = long long;
struct DSU {
std::vector<int> f, siz;
DSU() {}
DSU(int n) {
init(n);
}
void init(int n) {
f.resize(n);
std::iota(f.begin(), f.end(), 0);
siz.assign(n, 1);
for (int i = n / 2 + 1; i < n; i += 1) {
siz[i] = 0;
}
}
int find(int x) {
while (x != f[x]) {
x = f[x] = f[f[x]];
}
return x;
}
bool same(int x, int y) {
return find(x) == find(y);
}
bool merge(int x, int y) {
x = find(x);
y = find(y);
if (x == y) {
return false;
}
siz[x] += siz[y];
f[y] = x;
return true;
}
int size(int x) {
return siz[find(x)];
}
};
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n, m;
cin >> n >> m;
vector<DSU> dsu(30, DSU(2 * n + 1));
vector<array<int,3>> e(m);
vector g(n + 1, vector<int>());
for (auto &[u, v, w] : e) {
cin >> u >> v >> w;
for (int i = 29; i >= 0; i -= 1) {
if (w >> i & 1) {
if (dsu[i].same(u, v) || dsu[i].same(u + n, v + n)) {
cout << "-1\n";
return 0;
}
dsu[i].merge(u + n, v);
dsu[i].merge(u, v + n);
}
else {
if (dsu[i].same(u, v + n) || dsu[i].same(u + n, v)) {
cout << "-1\n";
return 0;
}
dsu[i].merge(u, v);
dsu[i].merge(u + n, v + n);
}
}
g[u].push_back(v);
g[v].push_back(u);
}
i64 ans = 0;
for (int i = 29; i >= 0; i -= 1) {
i64 k = 0;
vector<int> vis(2 * n + 1), has(2 * n + 1);
auto bfs = [&](int x)->int{
queue<int> Q;
Q.push(x);
int res = n, cnt = 0;
while (Q.size()) {
auto u = Q.front();
Q.pop();
if (vis[u]) {
continue;
}
vis[u] = 1;
if (!has[dsu[i].find(u)]) {
cnt += 1;
has[dsu[i].find(u)] = 1;
res = min(res, dsu[i].size(u));
}
for (auto &v : g[u]) {
if (!vis[v]) {
Q.push(v);
}
}
}
return (cnt > 1 ? res : 0);
};
for (int j = 1; j <= n; j += 1) {
if (vis[j]) {
continue;
}
k += bfs(j);
}
ans += (1LL << i) * k;
}
cout << ans << "\n";
return 0;
}
E - Edward Gaming, the Champion
模拟。
签到题,一个典型的错法是:
string s; cin >> s;
for (int i = 0; i < s.size() - 4; i++) { ... }
\(\text{str.size()\ <\ 4}\) 会导致 \(\text{size\_t}\) 类型的下溢出,并且在 \(\text{for}\) 循环判断退出条件时 \(\text{int}\) 会被 \(\text{static\_cast}\) 成 \(\text{size\_t}\) 类型进行比较从而导致访问越界。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define int long long
void solve() {
string s;
cin>>s;
int ans=0;
for (int i = 0; i <s.size() ; ++i) {
string g=s.substr(i,5);
if(g=="edgnb")ans++;
}
cout<<ans<<endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
int t=1;
// cin >> t;
while (t--) {
solve();
}
}
F - Encoded Strings I
模拟。
没读,队友写的,看起来直接模拟就行。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define int long long
const int mod = 998244353;
#define endl '\n'
void solve() {
int n;
cin >> n;
string g;
cin >> g;
string s;
string res = "";
s = "";
for (int i = 0; i < g.size(); ++i) {
// s += g[i];
string s=g.substr(0,i+1);
vector<int>vis(26,-1);
int dq=-1;
std::reverse(s.begin(), s.end());
for(auto &x:s) {
if (vis[x - 'a'] == -1) {
vis[x - 'a'] = ++dq;
// dq++;
x = 'a' + dq;
} else {
x = 'a' + vis[x-'a'];
}
}
std::reverse(s.begin(), s.end());
res= max(res,s);
}
cout << res << endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
int t=1;
// cin >> t;
while (t--) {
solve();
}
}
H - Line Graph Matching
边双联通分量。
容易发现偶数条边总是可以通过贪心匹配完的,奇数条边会有一条边不能被匹配;再进一步分析,如果奇数条边中删的边是割边,那么分成的两个联通块中边数有奇数的就会缺少匹配,所以要保证割掉这条边后两个联通块都是偶数边才会更优。
具体地,用边双联通分量缩点后,对于每个子树内是偶数边数时,可以删掉割边计算答案;非割边的情况可以预处理即可。
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define int long long
const int mod = 998244353;
#define endl '\n'
const int N=5e5+3;
#define endl '\n'
int cnt;
int dfn[N], low[N];
vector<array<int,3>> g[N];
vector<int>ans[N];
stack<int> st;
int dcc;
///c[x]表示x所属的边双联通分量编号
int c[N];
int s = 0;
void tarjan(int x, int las) {
low[x] = dfn[x] = ++cnt;
st.push(x);
for (auto f: g[x]) {
pair<int,int>i={f[0],f[1]};
if (i.second == (las ^ 1)) continue;
if (!dfn[i.first]) {
tarjan(i.first, i.second);
low[x] = min(low[x], low[i.first]);
} else low[x] = min(low[x], dfn[i.first]);
}
if (dfn[x] == low[x]) {
vector<int> vec;
dcc++;
ans[dcc].push_back(x);
c[x]=dcc;
while (st.top() != x) {
ans[dcc].push_back(st.top());
c[st.top()] = dcc;
st.pop();
}
st.pop();
}
}
void init(int n) {
cnt = 0;
dcc = 0;
while (st.size())st.pop();
for (int i = 0; i <= n; ++i) {
g[i].clear();
ans[i].clear();
low[i] = dfn[i] = 0;
c[i] = 0;
}
}
vector<array<int,2>>mp[N];
int a[N];
int res=0;
int f[N];
void dfs(int u,int fa) {
f[u] = a[u];
for (auto [v, w]: mp[u]) {
if (v == fa)continue;
dfs(v, u);
f[u] += f[v]+1;
if (f[v] % 2 == 0) {
res = max(res, s - w);
}
}
}
void solve() {
int n, m;
cin >> n >> m;
vector<int> du(n + 1);
vector<array<int, 3>> edge;
for (int i = 1; i <= m; ++i) {
int u, v, w;
cin >> u >> v >> w;
s += w;
g[u].push_back({v, i << 1, w});
g[v].push_back({u, i << 1 | 1, w});
}
if (m % 2 == 0) {
cout << s << endl;
return;
}
for (int i = 1; i <= n; ++i) {
if (!dfn[i]) tarjan(i, 0);
}
for (int i = 1; i <= n; ++i) {
for (auto [v, id, w]: g[i]) {
if (c[v] != c[i]) {
mp[c[i]].push_back({c[v], w});
}
}
}
for (int i = 1; i <= dcc; ++i) {
std::sort(mp[i].begin(), mp[i].end());
mp[i].erase(unique(mp[i].begin(), mp[i].end()), mp[i].end());
}
vector<int> vis(n + 1);
for (int i = 1; i <= n; ++i) {
if (vis[i] == 0) {
int sum = 0;
queue<int> q;
q.push(i);
while (q.size()) {
auto u = q.front();
q.pop();
vis[u] = 1;
for (auto [v, idx, w]: g[u]) {
if (c[v] == c[u]) {
res= max(res,s-w);
sum++;
if (vis[v] == 0) {
vis[v] = 1;
q.push(v);
}
}
}
}
a[c[i]] = sum;
}
}
// cout<<res<<endl;
// for (int i = 1; i <= dcc; ++i) {
// for (auto x: ans[i]) {
// cout << x << ' ';
// }
// cout << endl;
// }
// cout << endl;
for (int i = 1; i <= dcc; ++i) {
a[i] /= 2;
// cout << a[i] << ' ';
}
dfs(1, 0);
cout << res << endl;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
int t=1;
// cin >> t;
while (t--) {
solve();
}
}
J - Luggage Lock
最短路。
发现给定的两个字符串可能有 \(1\times 10^4\) 种状态,又是多源,复杂度是难以承受的;但是两个不同状态之间有相对关系,将一个状态调整到全 \(0\) 状态后,另外一个状态也调整相应步数,这时候它们的最短路程和未调整前是一样的,即对于 \(u\rightarrow v\) 来说,将 \(u\) 都调整到全 \(0\) 的状态,\(v\) 调整相应的步数,那么在一开始用全 \(0\) 跑完 \(\text{dijkstra}\) 就可以 \(O(1)\) 回答。
点击查看代码
#include <bits/stdc++.h>
#define PII pair<int,int>
#define endl '\n'
using namespace std;
using i64 = long long;
const int N=1e4+10,M=1e5+10;
string a, b;
vector<PII>g[N];
int vis[N],mn[N];
int ans[M];
void solve() {
int m;
cin>>a>>b;
int s=0;
for(int i=0;i<4;i++){
int p=(b[i]-a[i]+10)%10;
s*=10;
s+=p;
}
// cout<<s<<endl;
cout<<mn[s]<<endl;
}
int main() {
// freopen("in.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(nullptr);
queue<PII>q;
for(int l=0;l<1;l++){
int pp=l;
q.push({0,pp});
for(int i=0;i<1e4;i++){
vis[i]=0;
mn[i]=1e9;
}
mn[pp]=0;
while(q.size()){
auto [s,x]=q.front();
q.pop();
if(vis[x]){
continue;
}
vis[x]=1;
for (int i = 1; i <= 4; i += 1) {
for (int j = 0; j + i - 1 < 4; j += 1) {
int nxt = x;
for (int k = j; k <= j + i - 1; k += 1) {
int p=pow(10,3-k);
if (nxt/p%10 == 9) {
nxt+=p;
nxt-=p*10;
}
else {
nxt+=p;
}
}
if(s+1<mn[nxt]){
mn[nxt]=s+1;
q.push({s+1,nxt});
}
nxt = x;
for (int k = j; k <= j + i - 1; k += 1) {
int p=pow(10,3-k);
if (nxt/p%10 == 0) {
nxt-=p;
nxt+=p*10;
}
else {
nxt-=p;
}
}
if(s+1<mn[nxt]){
mn[nxt]=s+1;
q.push({s+1,nxt});
}
}
}
}
}
int t=1;
cin >> t;
while (t--) {
solve();
}
return 0;
}
L - Perfect Matchings
容斥,树形 \(dp\)。
设 \(dp_{u,i,0/1}\) 表示第 \(u\) 个点的子树内匹配了 \(i\) 对且 不包含 / 包含 点 \(i\) 的方案数。
定义 \(v\in Sub_u\),那么有转移方程:
得到 \(dp_{u,i,0/1}\) 之后,我们还要计算剩下 \(n-i\) 对点的匹配数,定义 \(x = n - i\),首先要选出一半的点 \(\binom{2x}{x}\),然后另外一半的点可以任意匹配,有全排列数 \(x!\),每一边还可以交换再算,会算重 \(2^x\) 次,所以另一半的方案数即 \(\frac{\binom{2x}{x}x!}{2^x}\),最后根据容斥原理判断系数为 \((-1)^i\)。
代码中 Z 类型为取模类。
参考[1]。
点击查看代码
using Z = ModInt<MOD[0]>;
struct Comb {
int n;
std::vector<Z> _fac; //阶乘
std::vector<Z> _invfac; //阶乘的逆元
std::vector<Z> _inv; //数字的逆元
Comb() : n{0}, _fac{1}, _invfac{1}, _inv{0} {}
Comb(int n) : Comb() {
init(n);
}
void init(int m) {
if (m <= n) return;
_fac.resize(m + 1);
_invfac.resize(m + 1);
_inv.resize(m + 1);
for (int i = n + 1; i <= m; i++) {
_fac[i] = _fac[i - 1] * i;
}
_invfac[m] = _fac[m].inv();
for (int i = m; i > n; i--) {
_invfac[i - 1] = _invfac[i] * i;
_inv[i] = _invfac[i] * _fac[i - 1];
}
n = m;
}
Z fac(int m) {
if (m > n) init(2 * m);
return _fac[m];
}
Z invfac(int m) {
if (m > n) init(2 * m);
return _invfac[m];
}
Z inv(int m) {
if (m > n) init(2 * m);
return _inv[m];
}
Z C(int n, int m) {
if (n < m || m < 0) return 0;
return fac(n) * invfac(m) * invfac(n - m);
}
Z A(int n, int m) {
if (n < m || m < 0 ) return 0;
return fac(n) * invfac(n - m);
}
} comb;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int n;
cin >> n;
n <<= 1;
vector e(n + 1, vector<int>());
for(int i = 1; i < n; i += 1) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
vector<int> siz(n + 1);
vector dp(n + 1, vector<array<Z,2>>(n / 2 + 2));
vector<array<Z,2>> ndp;
auto dfs = [&](auto &&self, int u, int fa)->void{
siz[u] = 1;
dp[u][0][0] = 1;
for(auto &v : e[u]) {
if(v == fa) continue;
self(self, v, u);
ndp.assign((siz[u] + siz[v]) / 2 + 2, {0, 0});
for(int i = siz[u] / 2; i >= 0; i -= 1) {
for(int j = siz[v] / 2; j >= 0; j -= 1) {
ndp[i + j][0] += dp[u][i][0] * (dp[v][j][0] + dp[v][j][1]);
ndp[i + j][1] += dp[u][i][1] * (dp[v][j][0] + dp[v][j][1]);
ndp[i + j + 1][1] += dp[u][i][0] * dp[v][j][0];
}
}
siz[u] += siz[v];
for(int i = 0;i <= siz[u] / 2 + 1;i += 1){
dp[u][i] = ndp[i];
}
}
};
dfs(dfs, 1, 0);
Z ans = 0;
for(int i = 0; i <= n / 2; i += 1) {
Z w = dp[1][i][0] + dp[1][i][1];
int x = n / 2 - i;
w *= comb.C(x * 2, x) * comb.fac(x) / power<Z>(2, x);
ans += (i & 1 ? -1 : 1) * w;
}
cout << ans << "\n";
return 0;
}

浙公网安备 33010602011771号