The 2021 ICPC Asia Shenyang Regional Contest
我们按难易程度来,E,F<B,J<H,I,L,M
E. Edward Gaming, the Champion
直接输出edgnb
子字符串数量。
F. Encoded Strings I
分析
对每一个前缀进行一次判断:
从后往前更新,用一个set来维护已经出现的字符。如果这个字符是第一次出现,那么就更新它的映射 \(mp[c] = char('a' + st.size())\),并把它插入set。
AC_code
#include <bits/stdc++.h>
using namespace std;
string fuc(string s){
int n = s.size();
set<char> st;
map<char, char> mp;
for(int i = n - 1; i >= 0; i --){
if(!st.count(s[i])){
mp[s[i]] = char('a' + st.size());
st.insert(s[i]);
}
}
for(char& c : s){
c = mp[c];
}
return s;
}
void solve(){
int n; string s;
cin >> n >> s;
vector<string> st;
for(int i = 1; i <= n; i ++){
st.push_back(fuc(s.substr(0, i)));
}
sort(st.begin(), st.end());
cout << st[n - 1];
}
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
solve();
return 0;
}
B. Bitwise Exclusive-OR Sequence
题目大意
有一个数组a
,长度为n
,给出m
个限制,每个限制为\(a_i \oplus a_j = w\),求满足条件的最小的数组的所有的数的和。
分析
由于涉及位运算,我们考虑对每一位单独操作。我们假设当前考虑的是bit
位。
我们来思考一下限制有什么作用呢?我们分两种情况。
- \((a_i \oplus a_j)>>bit\ \&\ 1=1\),则代表\(a_i\)与\(a_j\)在
bit
位一定不同。 - \((a_i \oplus a_j)>>bit\ \&\ 1=0\),则代表\(a_i\)与\(a_j\)在
bit
位一定相同。
这让我们想到了什么,不同的划分到一个集合去,相同的划分到一个集合去。利用扩展域并查集就可以实现该操作了。
AC_code
#include <bits/stdc++.h>
#define fi first
#define se second
#define endl '\n'
#define ios ios::sync_with_stdio(false); cin.tie(0), cout.tie(0)
using ll = long long;
using namespace std;
const int N = 2e5 + 10,M = N*2;
struct Node
{
int u,v,w;
}edges[N];
int p[N],sz[N];
bool vis[N];
int find(int x)
{
if(p[x]!=x) p[x] = find(p[x]);
return p[x];
}
void solve() {
int n,m;cin>>n>>m;
for(int i=0;i<m;i++)
{
int u,v,w;cin>>u>>v>>w;
edges[i] = {u,v,w};
}
ll ans = 0;
for(int i=0;i<30;i++)
{
for(int j=1;j<=2*n;j++) p[j] = j,vis[j] = 0;
for(int j=1;j<=n;j++) sz[j] = 1;
for(int j=n+1;j<=2*n;j++) sz[j] = 0;
for(int j=0;j<m;j++)
{
auto [u,v,w] = edges[j];
int pa = find(u),paa = find(u+n);
int pb = find(v),pbb = find(v+n);
if(w>>i&1)
{
if(pa==pb||paa==pbb)
{
cout<<"-1\n";
return ;
}
if(pa==pbb||paa==pb) continue;
p[pa] = pbb;sz[pbb] += sz[pa];
p[paa] = pb;sz[pb] += sz[paa];
}
else
{
if(pa==pbb||pb==paa)
{
cout<<"-1\n";
return ;
}
if(pa==pb||paa==pbb) continue;
p[pa] = pb;sz[pb] += sz[pa];
p[paa] = pbb;sz[pbb] += sz[paa];
}
}
for(int j=1;j<=n;j++)
{
int pa = find(j),pb = find(j+n);
if(vis[pa]&&vis[pb]) continue;
vis[pb] = vis[pa] = 1;
ans += (1ll<<i)*min(sz[pa],sz[pb]);
}
}
cout<<ans<<'\n';
}
int main()
{
ios;
int T=1;
// cin>>T;
while(T -- ) {
solve();
}
return 0;
}
J. Luggage Lock
题目大意
给两个字符串st
,ed
,都有四位。其中都为数字,从st
到ed
最少需要多少步。每一步,可以同时旋转相邻的几位,向上旋转或向下旋转。这里的向上旋转指的是同时+1,向下旋转同时-1。范围要限制在[0,9]
中。
分析
直接说做法吧。
我们直接暴力求出从0000
到所有的情况最短路。
然后T组样例时,我们做一个映射。
将st
映射到0000
每一位需要调整的,同步调整到ed
中。
AC_code
#include <bits/stdc++.h>
#define fi first
#define se second
#define endl '\n'
#define ios ios::sync_with_stdio(false); cin.tie(0), cout.tie(0)
using ll = long long;
using namespace std;
const int N = 15,M = N*2;
int dis[10010];
int get(int s,int x,int len,int ty)
{
int a[4] = {s/1000%10,s/100%10,s/10%10,s%10};
for(int i=x;i<x+len;i++)
a[i] = (a[i] + ty + 10)%10;
s = 0;
for(int i=0;i<4;i++) s = a[i] + s*10;
return s;
}
int find(string st,string ed)
{
int res = 0;
for(int i=0;i<4;i++) res = res*10 + (ed[i] - st[i] + 10)%10;
return res;
}
void solve() {
string st,ed;
cin>>st>>ed;
cout<<dis[find(st,ed)]-1<<'\n';
}
int main()
{
ios;
int T=1;
queue<int> q;
q.push(0);
dis[0] = 1;
while(q.size())
{
auto t = q.front();q.pop();
for(int len=1;len<=4;len++)
for(int i=0;i<4-len+1;i++)
{
int res = get(t,i,len,1);
if(!dis[res])
{
dis[res] = dis[t] + 1;
q.push(res);
}
res = get(t,i,len,-1);
if(!dis[res])
{
dis[res] = dis[t] + 1;
q.push(res);
}
}
}
cin>>T;
while(T -- ) {
solve();
}
return 0;
}
H. Line Graph Matching
题目大意
给出一个图,对一个点的两条边匹配,匹配得到的价值为两条边的权值和,这样能得到的最大的价值。
分析
我们分析后可以发现,如果图是偶数条边,那么一定可以全取到。
如果是奇数边,我们考虑将最小的一条边删除。
但是这里有个问题,如果最小的一条边是桥的话。我们删除后,会出现两个新的连通块。
其中包含两种情况。
- 奇数-桥-奇数,那我们肯定是考虑,删除除桥外的最小的一条边,这样结果就是偶数-桥-奇数,剩下的我们都可以匹配成功。
- 偶数-桥-偶数,那我们正常删桥就好了。
总结来说。
偶数的话,就直接输出所有边的权值和就好。
而奇数的话,如果最小值不是桥,那么我们直接删最小值,如果是桥,只有这个桥两端的连通块是偶数的时候我们才删该桥。
我们用tarjan
求桥
AC_code
#include<bits/stdc++.h>
#define ios ios::sync_with_stdio(false); cin.tie(0), cout.tie(0)
using namespace std;
using ll = long long;
const int N = 1e5 + 10,M = 4e5 + 10,inf = 0x3f3f3f3f;
int n,m,mi = inf,totedg;
int h[N],e[M],ne[M],w[M],idx;
int dfn[N],low[N],timestamp;
int sz[N],edge[N];
void add(int a,int b,int c)
{
e[idx]=b,ne[idx]=h[a],w[idx] = c,h[a]=idx++;
}
void tarjan(int u,int fa)
{
dfn[u]=low[u]=++timestamp;
sz[u] = edge[u];
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
if(!dfn[j])
{
tarjan(j,u);
sz[u] += sz[j];
low[u]=min(low[u],low[j]);
if(dfn[u]<low[j])
{
if((sz[j]-1)/2%2==0)
mi = min(w[i],mi);
}
else mi = min(w[i],mi);
}
else if(j!=fa) low[u]=min(low[u],dfn[j]),mi = min(w[i],mi);//同时,没有标记数组后,我们每次都需要对low[u]进行更新。
if(dfn[u]<dfn[j]) totedg++;
}
}
int main()
{
ios;cin>>n>>m;
memset(h,-1,sizeof h);
ll ans = 0;
for(int i=0;i<m;i++)
{
int u,v,w;cin>>u>>v>>w;
add(u,v,w),add(v,u,w);
edge[u]++;edge[v]++;
ans += w;
}
for(int i=1;i<=n;i++)
if(!dfn[i])
{
mi = inf;totedg = 0;
tarjan(i,-1);
if(totedg&1) ans -= mi;
}
cout<<ans<<'\n';
return 0;
}
I. Linear Fractional Transformation
题目大意
定义\(f(z)=\frac{az+b}{cz+d},(a,b,c,d\in C),C为复数\)。
T
组样例
每组样例给你\(z_1,f(z_1),z_2,f(z_2),z_3,f(z_3),z_4\),让你去计算\(f(z_4)\)的答案,保证\(f(z_4)\)的答案,保证\(f(z_4)\)有唯一解。
分析
由于有四个未知数,但是我们只有三个等式。但是本题保证一定有唯一解。因此,我们考虑将c
当做已知。
分类讨论c
-
c
为{0,0}
,带入看是否能使\(z_1,z_2,z_3\)同时成立。由于
\[az_1+b = f(z_1)\\ az_2+b = f(z_12) \]d
对判断成立并不影响,因此我们直接设d={1,0}
,带入\(z_1,z_2\),我们可以得到。解出来可以得到
\[a=\frac{w_1-w_2}{z_1-z_2}\\ b=w_1-az_1 \]接下来,我们用\(f(z_3)\)去验证
a,b
是否正确,正确的话,则\(f(z_4)\)也可以求出来。 -
c
为{1,0}
我们当前有三个方程,有三个未知数
\[az_1+b-f(z_1)*d=f(z_1)*z_1\\ az_2+b-f(z_2)*d=f(z_2)*z_2\\ az_3+b-f(z_3)*d=f(z_3)*z_3 \]a,b,d
直接高斯消元解出来。
将\(z_4\)带入得到答案。
AC_code
#include<bits/stdc++.h>
using namespace std;
const double eps = 1e-8;
struct Complex{
double x, y;
Complex operator+(const Complex &b) const {
return Complex({x+b.x, y+b.y});
}
Complex operator-(const Complex &b) const {
return Complex({x-b.x, y-b.y});
}
Complex operator*(const Complex &b) const {
return Complex({x*b.x - y*b.y, x*b.y + y*b.x});
}
Complex operator/(const Complex &b) const {
return Complex({(x*b.x + y*b.y)/(b.x*b.x + b.y*b.y), (y*b.x - x*b.y)/(b.x*b.x + b.y*b.y)});
}
}A[5][5], w[5], z[5];
double mo(Complex x){
return x.x*x.x + x.y*x.y;
}
void Gauss() {
//化成上三角矩阵
int n = 3;
for (int r = 1, c = 1; r <= n; ++ r, ++ c) {
//找到主元
int t = r;
for (int i = r + 1; i <= n; ++ i)
if (mo(A[i][c]) > mo(A[t][c]))
t = i;
//交换第r行和第t行的元素
for (int i = c; i <= n + 1; ++ i) swap(A[r][i], A[t][i]);
//主元归一(第r行除以主元系数)
for (int i = n + 1; i >= c; -- i) A[r][i] = A[r][i] / A[r][c];
//消元(用该行把下面所有行的第c列消为0)
for (int i = r + 1; i <= n; ++ i) {
for (int j = n + 1; j >= c; -- j) {
A[i][j] = A[i][j] - A[r][j] * A[i][c];
}
}
}
//化成行最简阶梯型矩阵(本题唯一解,故同样也是对角矩阵)
for (int i = n; i > 1; -- i) {
for (int j = i - 1; j >= 1; -- j) {
A[j][n + 1] = A[j][n+1] - A[i][n + 1] * A[j][i];
A[j][i] = {0, 0};
}
}
}
int main()
{
int T;scanf("%d",&T);
while(T--)
{
for(int i=1;i<=3;i++)
{
double x,y;
scanf("%lf%lf",&x,&y);z[i] = {x,y};
scanf("%lf%lf",&x,&y);w[i] = {x,y};
}
Complex z4;double x,y;
scanf("%lf%lf",&x,&y);
z4 = {x, y};
Complex a = (w[1] - w[2]) / (z[1] - z[2]);
Complex b = w[1] - a*z[1];
Complex res = {-5, -5};
if(mo(a*z[3] + b - w[3])<eps)
{
res = a*z4 + b;
}
else
{
for (int i = 1; i <= 3; i ++ ) {
A[i][1] = z[i];
A[i][2] = {1, 0};
A[i][3] = {-w[i].x, -w[i].y};
A[i][4] = w[i]*z[i];
}
Gauss();
res = (A[1][4]*z4 + A[2][4]) / (z4 + A[3][4]);
}
printf("%.8lf %.8lf\n",res.x,res.y);
}
return 0;
}
L. Perfect Matchings
题目大意
给一个2n大小的完全图,删除其中2n-1条边,其中2n-1条边形成一棵树。问完美匹配数量。
完美匹配,一个边集,这个边集中,任意两条边的端点不重合。
分析
我们没什么下手的地方,先想想如果不删我们怎么算呢?
这就是一个比较简单的组合问题了,我们可以将任意两个端点分成一组,然后就有n
组,这n
组的组成方案数就是完美匹配的方案数了。
我想的是,我们直接将2n
个端点进行全排列\(A_{2n}^{2n}\),每相邻两个为一组,则有重复,一个重复是组内前后没区别,则除以\(2^n\),接下来n
组的相对关系也是没区别,再除个\(A_{n}^{n}\)。则最后式子就是,\(\frac{A_{2n}^{2n}}{2^nA_{n}^{n}}\)。
我们的思路逐渐清晰了,我们从总体减去利用2n-1条边的不同方案。
那这些方案怎么算呢?这里有一个一般思路,我们暴力枚举一般都是不太行的,我们考虑用DP
计算。
因为减去的2n-1
条边是一棵树。则我们考虑定义状态为,f[i][j][0/1]
:以i
为根节点的子树,用了j
条边,0表示根节点所在的边未被选中,1相反。所有的完美匹配方案数。
我们考虑最后统计时,枚举0-n
。f[1][x][0]+f[1][x][1]
则表示,选中其中至少x
条树边,其余的树边我们无法确定是否选择。因此这里使用了容斥原理。
这表示了已经匹配了2i
个点,其余的2n-2i
个点未匹配。剩余2n-2i
的所有方案,我们已经知道了,即为\(y=\frac{A_{2n-2i}^{2n-2i}}{2^{n-i}A_{n-i}^{n-i}}\),则这种情况最终方案即为x*y。
容斥原理,用了x
条边的系数即为\((-1)^x\)。
这里为什么想到DP呢?
因为如果我们直接使用暴力枚举的话,则\(2^n\)种情况,但是其实很多情况是可以合并的。比如虽然选中的是不同的边,但是最后乘的y是相同的,因此可以直接合并,则时间复杂度直接就被降到\(O(n)\)。这里其实利用的就是加法原理。所以说虽然组合数学的加减乘除四个原理很基础,但是是我们解决后面很多问题的基石。
AC_code
#include<bits/stdc++.h>
#define ios ios::sync_with_stdio(false); cin.tie(0), cout.tie(0)
using namespace std;
using ll = long long;
const int N = 4010,mod = 998244353;
int h[N],ne[N<<1],e[N<<1],idx;
int f[N][N>>1][2],g[N>>1][2],sz[N];
int fact[N],infact[N],qpow[N];
int n;
void add(int a,int b)
{
e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}
int ksm(int a,int b)
{
int res = 1;
while(b)
{
if(b&1) res = 1ll*res*a%mod;
a = 1ll*a*a%mod;
b >>= 1;
}
return res;
}
void init(int x)
{
qpow[0] = fact[0] = infact[0] = 1;ll inv = ksm(2,mod-2);
for(int i=1;i<=x;i++) fact[i] = 1ll*fact[i-1]*i%mod,qpow[i] = inv*qpow[i-1]%mod;
infact[x] = ksm(fact[x],mod-2);
for(int i=x-1;i;i--) infact[i] = 1ll*infact[i+1]*(i+1)%mod;
}
int C(int a,int b)
{
if(b>a) return 0;
return 1ll*fact[a]*infact[b]%mod*infact[a-b]%mod;
}
void dfs(int u,int fa)
{
sz[u] = f[u][0][0] = 1;
for(int i=h[u];~i;i=ne[i])
{
int j = e[i];
if(j==fa) continue;
dfs(j,u);
memset(g,0,sizeof g);
for(int i=0;i<=sz[u]/2;i++)
for(int k=0;k<=sz[j]/2;k++)
{
g[i+k][0] = (g[i+k][0]+1ll*f[u][i][0]*(f[j][k][0]+f[j][k][1])%mod)%mod;
g[i+k][1] = (g[i+k][1]+1ll*f[u][i][1]*(f[j][k][0]+f[j][k][1])%mod)%mod;
g[i+k+1][1] = (g[i+k+1][1]+1ll*f[u][i][0]*f[j][k][0]%mod)%mod;
}
for(int i=0;i<=sz[u]/2+sz[j]/2+1;i++) f[u][i][0] = g[i][0],f[u][i][1] = g[i][1];
sz[u] += sz[j];
}
}
int main()
{
ios;
int n;cin>>n;
memset(h,-1,sizeof h);
init(2*n);
for(int i=0;i<2*n-1;i++)
{
int u,v;cin>>u>>v;
add(u,v),add(v,u);
}
dfs(1,-1);
ll ans = 0;
for(int i=0;i<=n;i++)
{
ll x = (f[1][i][0]+f[1][i][1])%mod;
int t = n - i;
ll y = 1ll*fact[2*t]*infact[t]%mod*qpow[t]%mod;
if(i&1) ans = (ans-x*y%mod+mod)%mod;
else ans = (ans + x*y%mod)%mod;
}
cout<<ans<<'\n';
return 0;
}
M. String Problem
分析
队友写的,听说不难。
后缀数组&&LCP
一个字符串的字典序最大的子串是这个字符串字典序最大的后缀。
从后往前更新,用 \(now\) 来记录已经更新过的答案
对于排名第 \(i\) 位的后缀,往前找到第一个 \(j < i,sa[j] < sa[i]\) 的排名为 \(j\) 后缀 , 他对答案的贡献是 \([sa[i] + lcp(i, j), now]\)
AC_code
#include <bits/stdc++.h>
using namespace std;
const int N = 1000010;
int n, sa[N], rk[N], oldrk[N << 1];
int id[N], px[N], cnt[N], height[N];
int ans[N];
bool cmp(int x, int y, int w) {
return oldrk[x] == oldrk[y] && oldrk[x + w] == oldrk[y + w];
}
void solve() {
int i, m = 300, p, w;
string s; cin >> s; n = s.size();
s = " " + s;
for(int i = 0; i <= m; i ++) cnt[i] = 0;
for (i = 1; i <= n; ++i) ++cnt[rk[i] = s[i]];
for (i = 1; i <= m; ++i) cnt[i] += cnt[i - 1];
for (i = n; i >= 1; --i) sa[cnt[rk[i]]--] = i;
for (w = 1;; w <<= 1, m = p) {
for (p = 0, i = n; i > n - w; --i) id[++p] = i;
for (i = 1; i <= n; ++i)
if (sa[i] > w) id[++p] = sa[i] - w;
memset(cnt, 0, (m + 3) * sizeof(int));
for (i = 1; i <= n; ++i) ++cnt[px[i] = rk[id[i]]];
for (i = 1; i <= m; ++i) cnt[i] += cnt[i - 1];
for (i = n; i >= 1; --i) sa[cnt[px[i]]--] = id[i];
memcpy(oldrk, rk, (n + 3) * sizeof(int));
for (p = 0, i = 1; i <= n; ++i)
rk[sa[i]] = cmp(sa[i], sa[i - 1], w) ? p : ++p;
if (p == n) {
for (int i = 1; i <= n; ++i) sa[rk[i]] = i;
break;
}
}
for (int i = 1, k = 0; i <= n; ++i) {
if (k) --k;
while (s[i + k] == s[sa[rk[i] - 1] + k]) ++k;
height[rk[i]] = k;
}
for(int i = 1; i <= n; i ++){
cout << sa[i] << ' ';
}
cout << "\n";
int now = n;
for(int i = n; i; i --){
int j = i - 1;
int h = height[i];
while(j && sa[i] < sa[j]){
h = min(h, height[j]);
j --;
}
if(j <= 0){
for(int k = now; k; k --) ans[k] = sa[i];
break;
}
for(int j = now; j >= sa[i] + h; j --) ans[j] = sa[i];
now = sa[i] + h - 1;
if(now <= 0) break;
i = j + 1;
}
for(int i = 1; i <= n; i ++){
cout << ans[i] << ' ' << i << "\n";
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr); cout.tie(nullptr);
int T = 1; //cin >> T;
while (T --) {
solve();
}
return 0;
}
标签:Regional,const,Contest,int,Shenyang,--,Complex,return,2n
From: https://www.cnblogs.com/aitejiu/p/16845695.html