The 2021 ICPC Asia Shenyang Regional Contest(EFBJHLI)
2025.8.24
单挑,5题,Cu
E. Edward Gaming, the Champion
签到。
F. Encoded Strings I
签到,模拟。
但是我糖丸了,做了很久,还wa了一发。
B. Bitwise Exclusive-OR Sequence
根据样例2,发现只有01会很好找到无解的情况,而注意到每位是独立的,于是考虑按位做。
#include <bits/stdc++.h>
using namespace std;
// #define int long long
using i64 = long long;
using i128 = __int128;
using u64 = unsigned long long;
const int mod = 998244353;
const int N = 1e5 + 9;
const int iinf = 1e9;
const i64 linf = 1e18;int fa[N];
int find(int x) {if (x==fa[x]) return x;return fa[x]=find(fa[x]);
}
void merge(int x,int y) {int fx=find(x),fy=find(y);if (fx==fy) return ;fa[fx]=fy;
}void solve() {int n,m;cin>>n>>m;vector<vector<array<int,2>>> G(n+1);for (int i=1;i<=n;++i) fa[i]=i;for (int i=1;i<=m;++i) {int u,v,w;cin>>u>>v>>w;G[u].push_back({v,w});G[v].push_back({u,w});merge(u,v);}bool ok=true;vector<bool> vis(n+1),a(n+1);int cnt1=0,cnt0=0,w;function<void(int,int)> dfs=[&](int x,int val) {if (!ok) return ;a[x]=val;vis[x]=1;if (val==0) cnt0++;else cnt1++;for (auto [y,z]:G[x]) {int nv=(z>>w)&1;if (vis[y]) {if ((val^nv)!=a[y]) ok=false;}else {dfs(y,val^nv);}}};i64 ans=0;for (w=0;w<30;++w) {for (int i=1;i<=n;++i) vis[i]=false;for (int i=1;i<=n;++i) {if (i!=find(i)) continue;cnt1=0,cnt0=0;dfs(i,0);if (ok==false) {cout<<"-1\n";return ;}else {ans+=(1ll<<w)*min<i64>(cnt1,cnt0);}}}cout<<ans<<"\n";
}signed main() {ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);
#ifndef ONLINE_JUDGEfreopen("C:\\ACM\\code\\test.in", "r", stdin);// freopen("C:\\ACM\\code\\test2.in", "r", stdin);
#endifint Case = 1;// cin >> Case;while (Case--) solve();return 0;
}
J. Luggage Lock
~铜牌题
容易想到根据差值处理,发现状态只有\(10000\),直接暴力预处理即可。
又糖了,导致后面没时间写L了。
#include <bits/stdc++.h>
using namespace std;
// #define int long long
using i64 = long long;
using i128 = __int128;
using u64 = unsigned long long;
const int mod = 998244353;
const int N = 1e6 + 9;
const int iinf = 1e9;
const i64 linf = 1e18;map<array<int,4>,int> f;void init() {array<int,4> now={0,0,0,0};f[now]=0;queue<array<int,4>> q;q.push((now));while (q.size()) {auto ar=q.front();q.pop();for (int l=0;l<4;++l) for (int r=l;r<4;++r) {auto ar0=ar,ar1=ar;for (int i=l;i<=r;++i) ar0[i]=(ar0[i]+1)%10;for (int i=l;i<=r;++i) ar1[i]=(ar1[i]+9)%10;if (!f.count(ar0)) q.push(ar0),f[ar0]=f[ar]+1;if (!f.count(ar1)) q.push(ar1),f[ar1]=f[ar]+1;}}
}void solve() {string a,b;cin>>a>>b;array<int,4> ar;for (int i=0;i<4;++i) {int x=b[i]-a[i];if (x<0) x+=10;ar[i]=x;}cout<<f[ar]<<"\n";
}signed main() {ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);
#ifndef ONLINE_JUDGEfreopen("C:\\ACM\\code\\test.in", "r", stdin);// freopen("C:\\ACM\\code\\test2.in", "r", stdin);
#endifint Case = 1;init();cin >> Case;while (Case--) solve();return 0;
}
H. Line Graph Matching
铜~银牌题,图论。
容易发现就是每次可以获得同一个点的两条边的权值和,然后去掉,问最大权值和。
一开始以为贪心,写出来发现有问题,然后看了看样例,猜到一个结论就是一个联通块的边数是偶数的话,一定可以获得全部边。奇数就要去掉一些边(假设一定是去掉一条边),正着不好做。我们考虑按照边权递减的顺序连边。记录每个连通块边数,和去掉了的权值,用并查集维护。有以下情况。
- 联通块内部加边,如果原来是奇数,那么加上后,去掉的权值为0,否则为这条边的权值。
- 联通两个不同奇偶性的块,这个时候合并后为偶数,为0。
- 偶数和偶数,为这条边的权值。
- 奇数和奇数,首先若原来的奇数块去掉边没有分开,那么权值就为原来的值,若分成了两个块,根据归纳假设,由于只去掉一条边,那么两个块一定为偶数,因此还是可以删除这一条边。
这里补充上面那个性质的证明,即无向联通图,相邻边两两匹配,若边为偶数,则一定有完美匹配。
找出所有的奇度数点,连向一个新的节点,根据握手引理,点数量为偶数,此时新图所有点度数为偶数,那么存在一条欧拉回路,对于这条路径,将进入某个点和离开某个点的边配对即可。
#include <bits/stdc++.h>
using namespace std;
// #define int long long
using i64 = long long;
using i128 = __int128;
using u64 = unsigned long long;
const int mod = 998244353;
const int N = 1e5 + 9;
const int iinf = 1e9;
const i64 linf = 1e15;int fa[N],e[N],los[N];
int find(int x) {if (x==fa[x]) return x;return fa[x]=find(fa[x]);
}void solve() {int n,m;cin>>n>>m;vector<array<int,3>> E;i64 ans=0;for (int i=1;i<=m;++i) {int u,v,w;cin>>u>>v>>w;ans+=w;E.push_back({w,u,v});}sort(E.begin(),E.end(),greater<array<int,3>>());for (int i=1;i<=n;++i) fa[i]=i,e[i]=0,los[i]=0;for (auto [w,x,y]:E) {int fx=find(x),fy=find(y);if (fx==fy) {if (e[fx]&1) {los[fx]=0;}else {los[fx]=w;}e[fx]++;}else {if ((e[fx]&1)^(e[fy]&1)) {los[fy]=0;}else {if (e[fx]&1) {los[fy]=min(los[fx],los[fy]);}else {los[fy]=w;}}fa[fx]=fy;e[fy]+=1+e[fx];}}for (int i=1;i<=n;++i) {if (i==find(i))ans-=los[i];}cout<<ans<<"\n";
}signed main() {ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);
#ifndef ONLINE_JUDGEfreopen("C:\\ACM\\code\\test.in", "r", stdin);// freopen("C:\\ACM\\code\\test2.in", "r", stdin);
#endifint Case = 1;// cin >> Case;while (Case--) solve();return 0;
}
比赛结束后发现,图是联通的,那就只需要找到奇数边时删哪条边即可。显然可以枚举边,如果该边是割边,就要判断两边联通块边数:若不是偶数,就跳过,否则继续判断。可以用tarjan找桥,边数可根据dfs处理出度数和计算。
#include <bits/stdc++.h>
using namespace std;
// #define int long long
using i64 = long long;
using i128 = __int128;
using u64 = unsigned long long;
const int mod = 998244353;
const int N = 1e5 + 9;
const int iinf = 1e9;
const i64 linf = 1e15;struct Q {int v, w,in;
};
multiset<int> st;
vector<vector<int>> ans;
vector<int> cur;
vector<Q> e[N];
int dfn[N], low[N], ed[N],sz[N];
int id;
void tarjan(int u, int fin) {dfn[u]=low[u]=++id;for (auto [v,w,in]:e[u]) {if (in!=fin) {if (!dfn[v]) {tarjan(v,in);sz[u]+=sz[v];low[u]=min(low[u],low[v]);if (low[v]>dfn[u] and ((sz[v]-1)/2)&1) st.erase(w);}else{low[u]=min(low[u],dfn[v]);}}}
}void solve() {int n, m;cin >> n >> m;i64 ans = 0;id=0;for (int i = 1; i <= m; ++i) {int u, v, w;cin >> u >> v >> w;e[u].push_back({v,w,i});e[v].push_back({u,w,i});sz[u]++,sz[v]++;st.insert(w);ans+=w;}if (m&1) tarjan(1,0),ans-=*st.begin();cout<<ans<<"\n";
}signed main() {ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);
#ifndef ONLINE_JUDGEfreopen("C:\\ACM\\code\\test.in", "r", stdin);// freopen("C:\\ACM\\code\\test2.in", "r", stdin);
#endifint Case = 1;// cin >> Case;while (Case--) solve();return 0;
}
L. Perfect Matchings
银牌题。dp
赛时没看到会形成一颗树。最眼瞎的一集
用集合去容斥时间复杂度过高,但发现只用考虑两类边,因此只用考虑树上使用边(点)的数量。
一个显然的思路是维护边不含交点,对禁止的边进行容斥,因为边全在一颗树上,那么我们就很方便进行dp。
而去掉选定的点后,还是是一个完全图,\(2n\)个点完全图的完美匹配数是
然后就是计算一颗树上,选出k条边的方案数。
\(f_{i,j,0/1}\) 表示以\(i\)为根,选出来\(j\)条边,根节点是否被选的方案数量,然后就是树上背包问题。
void dfs(int x,int pre) {siz[x]=1;for (auto y:ver[x]) {if (y==pre) continue;dfs(y,x);for (int i=siz[x]/2;i>=0;--i) for (int j=siz[y]/2;j>=0;--j) {if (j) {f[x][i+j][0]=(1ll*f[x][i+j][0]+1ll*f[x][i][0]*(1ll*f[y][j][0]+f[y][j][1])%mod)%mod;f[x][i+j][1]=(1ll*f[x][i+j][1]+1ll*f[x][i][1]*(1ll*f[y][j][0]+f[y][j][1])%mod)%mod;}f[x][i+j+1][1]=(1ll*f[x][i+j+1][1]+1ll*f[x][i][0]*f[y][j][0]%mod)%mod;}siz[x]+=siz[y];}
}
实际上这是\(O(n^2)\)的,不太会算树上背包的复杂度,感觉做树上背包问题多注意调整一下边界,大胆写就行了。
I. Linear Fractional Transformation
金牌题。数学
3个方程,4个未知数,并且是齐次的,因此我们考虑先定一个数。
如果\(c=0\),那么\(f(z)=\frac{a}{d} z+\frac{c}{d}\),再设\(d=1\),然后解出\(a,c\),再验证第3个,若满足,就可以直接带入计算即可。
不满足就设\(c=1\),解出\(a,b,d\)即可。
#include <bits/stdc++.h>
using namespace std;
// #define int long long
using i64 = long long;
using i128 = __int128;
using u64 = unsigned long long;
const int mod = 998244353;
const int N = 1e6 + 9;
const int iinf = 1e9;
const i64 linf = 1e18;using cd=complex<long double>;
cd ar[10][10];
const double eps = 1e-8;int gauss(int n) {int c, r;for (c = 0, r = 0; c < n; ++c) {int t = r;for (int i = r; i < n; ++i) {if (std::abs(ar[i][c]) > std::abs(ar[t][c])) t = i;}if (std::abs(ar[t][c]) < eps) continue;for (int j = c; j < n + 1; ++j) std::swap(ar[t][j], ar[r][j]);for (int j = n; j >= c; --j) ar[r][j] /= ar[r][c];for (int i = r + 1; i < n; ++i) {if (std::abs(ar[i][c]) > eps) {for (int j = n; j >= c; --j) {ar[i][j] -= ar[r][j] * ar[i][c];}}}r++;}if (r < n) {for (int i = r; i < n; ++i) {if (std::abs(ar[i][n])>eps) return 2;}return 1;}for (int i = n - 1; i >= 0; --i) {for (int j = i + 1; j < n; ++j) {ar[i][n] -= ar[i][j] * ar[j][n];}}return 0;
}bool myequal(cd z1,cd z2) {return std::abs(z1-z2)<eps;
}void solve() {cd z0,a,b,d;array<cd,3> z,w;for (int i=0;i<3;++i) {int x,y;cin>>x>>y;z[i]=cd(x,y);cin>>x>>y;w[i]=cd(x,y);}int x,y;cin>>x>>y;z0=cd(x,y);for (int i=0;i<2;++i) ar[i][0]=z[i],ar[i][0]=z[i],ar[i][1]=1,ar[i][2]=w[i];gauss(2);a=ar[0][2],b=ar[1][2];if (myequal(a*z[2]+b,w[2])) {cd ans=a*z0+b;cout<<fixed<<setprecision(12)<<ans.real()<<" "<<ans.imag()<<"\n";return ;}for (int i=0;i<3;++i) ar[i][0]=z[i],ar[i][0]=z[i],ar[i][1]=1,ar[i][2]=-w[i],ar[i][3]=w[i]*z[i];gauss(3);a=ar[0][3],b=ar[1][3],d=ar[2][3];cd ans=(a*z0+b)/(z0+d);cout<<fixed<<setprecision(12)<<ans.real()<<" "<<ans.imag()<<"\n";
}signed main() {ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);
#ifndef ONLINE_JUDGEfreopen("C:\\ACM\\code\\test.in", "r", stdin);// freopen("C:\\ACM\\code\\test2.in", "r", stdin);
#endifint Case = 1;cin >> Case;while (Case--) solve();return 0;
}
或者利用线性分式变换的保交比性
分式线性变换
交比是四个复数 z, z1, z2, z3
的一种组合,定义为:
而
从而
void solve() {cd z0,a,b,d;array<cd,4> z,w;for (int i=1;i<=3;++i) {int x,y;cin>>x>>y;z[i]=cd(x,y);cin>>x>>y;w[i]=cd(x,y);}int x,y;cin>>x>>y;z[0]=cd(x,y);cd A=(z[0]-z[1])*(z[2]-z[3])*(w[2]-w[1]),B=(z[0]-z[3])*(z[2]-z[1])*(w[2]-w[3]);cd ans=(w[3]*A-w[1]*B)/(A-B);cout<<fixed<<setprecision(12)<<ans.real()<<" "<<ans.imag()<<"\n";
}