浙工商 2026 暑期内部排位赛 2 部分题解
F 昵称
标签
组合数学,\(dp\).
题意
给定长度分别为 \(n,m\) 的两字符串 \(s,t\),其中 \(n\le m\),需要计算合法的 \((s',t')\) 数量,模 \(998244353\),约束如下:
-
\(s'\) 是 \(s\) 的一个排列.
-
\(t'\) 是 \(t\) 删掉 \(m-n\) 个字符后的排列,且 \(t'_i = s'_i\) 或 \(t'_i = s'_i+1\).
\(1\le n \le m \le 2\times 10^5\).
思路
令 \(a_c,b_c\) 分别为 \(s,t\) 中字符 \(c\) 的数量,\(x_c\) 为 字符 \(c\) 中匹配 \(c+1\) 的数量.
先不考虑 \(x\),方案数是:
从 \(a_c\) 个 \(c\) 中替换 \(x_c\) 个为 \(c+1\):
统计每组合法的 \(x\) 的贡献,即计算:
接下来分析 \(x\) 的约束,对于 \(t\) 中字符 \(c\),需要匹配 \(a_c-x_c\) 个 \(c\) 和 \(x_{c-1}\) 个 \(c-1\),因此需要满足 \(b_c \ge a_c-x_c+x_{c-1}\),即 \(x_c \ge x_{c-1}+a_c-b_c\),那么可以考虑 \(dp\).
记 \(dp[c][x]\) 为处理完 \(0\cdots c\) 的字符,\(x_c=x\) 的贡献,\(x_{c-1}\) 可以转移到 \([x_c+b_c-a_c,n]\),这个后缀形式统一转移到 \([x_c+b_c-a_c]\),之后求前缀和即可,最终答案是 \(dp[25][0]\),因为 \(x_{25}\) 必须是 \(0\).
时间复杂度 \(\mathcal{O}(26n)\).
代码
//author:kzssCCC
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
template<int MOD>
struct modint {
int val;
modint() : val(0) {}
modint(long long v) {
val = v % MOD;
if (val < 0) val += MOD;
}
modint& operator++() { val = (val + 1 == MOD ? 0 : val + 1); return *this; }
modint& operator--() { val = (val == 0 ? MOD - 1 : val - 1); return *this; }
modint operator++(int) { modint res = *this; ++*this; return res; }
modint operator--(int) { modint res = *this; --*this; return res; }
modint& operator+=(const modint& o) { val += o.val; if (val >= MOD) val -= MOD; return *this; }
modint& operator-=(const modint& o) { val -= o.val; if (val < 0) val += MOD; return *this; }
modint& operator*=(const modint& o) { val = 1LL * val * o.val % MOD; return *this; }
modint& operator/=(const modint& o) { return *this *= o.inv(); }
friend modint operator+(modint a, const modint& b) { return a += b; }
friend modint operator-(modint a, const modint& b) { return a -= b; }
friend modint operator*(modint a, const modint& b) { return a *= b; }
friend modint operator/(modint a, const modint& b) { return a /= b; }
friend bool operator==(const modint& a, const modint& b) { return a.val == b.val; }
friend bool operator!=(const modint& a, const modint& b) { return a.val != b.val; }
modint operator-() const { modint res = *this; res.val = (res.val == 0 ? 0 : MOD - res.val); return res;};
modint operator+() const { return *this; };
modint qpow(long long p) const {
modint res = 1, a = *this;
while (p > 0) {
if (p & 1) res *= a;
a *= a;
p >>= 1;
}
return res;
}
modint inv() const {
return qpow(MOD - 2);
}
friend std::ostream& operator<<(std::ostream& os, const modint& m) { return os << m.val; }
friend std::istream& operator>>(std::istream& is, modint& m) { long long v; is >> v; m = modint(v); return is; }
};
using Z = modint<998244353>;
struct binom{
int n;
vector<Z> fac,inv;
binom(int _n){
n = _n;
fac.assign(n+1,0);
inv.assign(n+1,0);
fac[0] = 1;
for (int i=1;i<=n;i++){
fac[i] = fac[i-1]*i;
}
inv[n] = fac[n].inv();
for (int i=n-1;i>=0;i--){
inv[i] = inv[i+1]*(i+1);
}
}
Z C(int a,int b){
if (a<0 || a>n || b<0 || b>n || a<b) return 0;
return fac[a]*inv[b]*inv[a-b];
}
Z A(int a,int b){
if (a<0 || a>n || b<0 || b>n || a<b) return 0;
return fac[a]*inv[a-b];
}
};
binom bn(2e5);
void solve(){
int n,m;
cin >> n >> m;
string s,t;
cin >> s >> t;
s = ' '+s;
t = ' '+t;
vector<int> a(26),b(26);
for (int i=1;i<=n;i++){
a[s[i]-'A']++;
}
for (int i=1;i<=m;i++){
b[t[i]-'A']++;
}
Z res = bn.fac[n];
for (int c=0;c<26;c++){
res /= bn.fac[a[c]];
}
vector<vector<Z>> dp(27,vector<Z>(n+1));
dp[0][0] = 1;
for (int c=0;c<26;c++){
for (int x=0;x<=n;x++){
if (dp[c][x]==0) continue;
if (x+a[c]-b[c]<=a[c]){
dp[c+1][max(0,x+a[c]-b[c])] += dp[c][x];
}
}
for (int x=1;x<=a[c];x++){
dp[c+1][x] += dp[c+1][x-1];
}
for (int x=0;x<=a[c];x++){
dp[c+1][x] *= bn.C(a[c],x);
}
}
res *= dp[26][0];
cout << res << '\n';
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}
H 方格游戏
标签
博弈论,欧几里得算法.
题意
一张 \([1,10^9] \times [1,10^9]\) 网格,给定 \(n\) 个黑点,其他全白.
每次操作选择一个黑点 \((r,c)\),对所有满足 \(0\le x,y \le \min(r,c)\) 的格子 \((r-x,c-y)\) 翻转颜色,无法操作者输,问胜者是先手还是后手.
\(1\le n \le 2\times 10^5\).
思路
难以分析每次操作的影响,考虑寻找每次操作的不变量.
博弈问题中,可以尝试寻找一个量 \(S\),使得每次操作 \(S := S \oplus 1\).
具体地,给初始每个黑点钦定一个值 \(0/1\),其余白点均为 \(0\),定义 \(S\) 为整张表格的异或值,因此要让一次操作覆盖的网格内异或值为 \(1\).
令 \(f[i][j]\) 为网格的权值,\(pre[i][j]\) 为 \(f[i][j]\) 的二维前缀异或和,对一次操作讨论:
-
若 \(r\ge c\),要求 \(pre[r][c]\oplus pre[r-c][c]=1\).
-
若 \(r\lt c\),要求 \(pre[r][c]\oplus pre[r][c-r]=1\).
可以发现,这是辗转相减的形式,可以递推求得所有 \(pre[i][j]\) 的值,需要用欧几里得算法加速这个过程.
时间复杂度 \(\mathcal{O}(n\log V)\),\(V\) 是值域.
代码
//author:kzssCCC
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int euclid(int a,int b){
if (b==0) return 0;
return ((a/b)&1)^euclid(b,a%b);
}
int cal(int x,int y){
return euclid(x,y)^euclid(x-1,y)^euclid(x,y-1)^euclid(x-1,y-1);
}
void solve(){
int n;
cin >> n;
int res = 0;
for (int i=1;i<=n;i++){
int x,y;
cin >> x >> y;
res ^= cal(x,y);
}
if (res&1){
cout << "FIRST" << '\n';
}
else{
cout << "SECOND" << '\n';
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}
I 增加线缆
标签
网络流,最小路径覆盖.
题意
给定 \(n-1\) 条有向边,保证忽略方向后构成图为树,要求添加最少的边使得图的拓扑序唯一,给出构造方案.
\(2\le n \le 10^5\).
思路
拓扑序唯一意味着拓扑序相邻两点必有边,否则可以交换位置.
要求添加边最少,需要尽可能多地保留原图的边,分析可知,这相当于在原图中保留一些路径,因此可以总结为最小路径覆盖问题,可以用二分图匹配解决.
跑完二分图匹配,需要连接所有路径,将保留路径缩成单点并记录起点和终点,对缩点图拓扑排序后,按拓扑序依次连接路径起点和终点即可.
时间复杂度 \(\mathcal{O}(n\sqrt{n})\).
代码
//author:kzssCCC
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
class dinic{
public:
const ll INF = 9e18;
int n,s,t;
vector<vector<array<ll,4>>> adj;
ll mxf;
vector<int> cur,depth;
dinic(int _n,int _s,int _t){
n = _n;
s = _s;
t = _t;
adj = vector<vector<array<ll,4>>>(n+1);
}
void add(int u,int v,ll w){
adj[u].push_back({w,1,(int)adj[v].size(),v});
adj[v].push_back({0,0,(int)adj[u].size()-1,u});
}
bool bfs(){
depth = vector<int>(n+1,-1);
queue<int> q;
depth[s] = 0;
q.push(s);
while (!q.empty()){
int u = q.front();
q.pop();
for (auto& [w,flag,rev,v]:adj[u]){
if (w>0 && depth[v]==-1){
depth[v] = depth[u]+1;
q.push(v);
}
}
}
return depth[t]!=-1;
}
ll dfs(int u,ll mf){
if (u==t) return mf;
ll sum = 0;
int len = adj[u].size();
for (int& i=cur[u];i<len;i++){
auto& [w,flag,rev,v] = adj[u][i];
if (w>0 && depth[v]==depth[u]+1){
ll f = dfs(v,min(mf,w));
sum += f;
mf -= f;
w -= f;
adj[v][rev][0] += f;
if (mf==0) break;
}
}
return sum;
}
void work(){
mxf = 0;
while (bfs()){
cur = vector<int>(n+1);
mxf += dfs(s,INF);
}
}
};
void solve(){
int n;
cin >> n;
vector<vector<int>> adj(n+1);
for (int i=0;i<n-1;i++){
int u,v;
cin >> u >> v;
adj[u].push_back(v);
}
int s = 2*n+1;
int t = s+1;
dinic dn(2*n+2,s,t);
for (int u=1;u<=n;u++){
for (auto& v:adj[u]){
dn.add(u,v+n,1);
}
dn.add(s,u,1);
dn.add(u+n,t,1);
}
dn.work();
cout << n-1-dn.mxf << '\n';
vector<int> pre(n+1,-1),next(n+1,-1);
for (int u=1;u<=n;u++){
for (auto& [w,op,rev,v]:dn.adj[u]){
if (op && w==0){
next[u] = v-n;
pre[v-n] = u;
}
}
}
vector<pair<int,int>> wait{{-1,-1}};
vector<int> belong(n+1);
for (int i=1;i<=n;i++){
if (pre[i]==-1){
int u = i;
int id = wait.size();
while (next[u]!=-1){
belong[u] = id;
u = next[u];
}
belong[u] = id;
wait.emplace_back(i,u);
}
}
int m = wait.size()-1;
vector<vector<int>> g(m+1);
for (int u=1;u<=n;u++){
for (auto& v:adj[u]){
if (belong[u]!=belong[v]){
g[belong[u]].push_back(belong[v]);
}
}
}
vector<int> ing(m+1);
for (int i=1;i<=m;i++){
sort(g[i].begin(),g[i].end());
g[i].erase(unique(g[i].begin(),g[i].end()),g[i].end());
for (auto& v:g[i]){
ing[v]++;
}
}
queue<int> q;
for (int i=1;i<=m;i++){
if (ing[i]==0){
q.push(i);
}
}
vector<int> topo{-1};
while (!q.empty()){
int u = q.front();
q.pop();
topo.push_back(u);
for (auto& v:g[u]){
if (--ing[v]==0){
q.push(v);
}
}
}
for (int i=2;i<=m;i++){
cout << wait[topo[i-1]].second << ' ' << wait[topo[i]].first << '\n';
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}
J 还原词典
标签
\(sa\),\(st\) 表,\(dp\).
题意
给定长度为 \(n\) 字符串 \(s\),将 \(s\) 划分成若干连续子串且满足子串严格递增,求最大划分数量并给出方案.
\(1\le n \le 5000\).
思路
考虑 \(dp\),令 \(dp[i][j]\) 为以 \(i\) 为起点长度为 \(j\) 的子串作为最后一个子串能达到的最大划分数量.
令 \(r\) 为第一个满足 \(s[i\cdots i+j-1] \lt s[i+j,i+j+r-1]\) 的长度,那么 \(dp[i+j][R]\leftarrow dp[i][j]+1\),\(R\ge r\),转移到 \(dp[i+j][r]\) 后求前缀最大值即可,问题转化为求 \(r\).
发现起点是固定的,考虑求 \(lcp\),可以用 \(st\) 表维护 \(sa\) 的 \(height\) 数组快速解决,分类讨论转移即可.
时间复杂度 \(\mathcal{O}(n^2)\).
代码
//author:kzssCCC
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int INF = 1e9;
void solve(){
string s;
cin >> s;
int n = s.size();
s = ' '+s;
vector<int> sa(n+1),rk(n+1),height(n+1);
{
int m = 255;
vector<int> ord(n+1),cnt(max(n,m)+1);
for (int i=1;i<=n;i++){
rk[i] = (unsigned char)s[i];
cnt[rk[i]]++;
}
for (int i=1;i<=m;i++){
cnt[i] += cnt[i-1];
}
for (int i=n;i>=1;i--){
sa[cnt[rk[i]]--] = i;
}
for (int k=1;;k<<=1){
int p = 1;
for (int i=n-k+1;i<=n;i++){
ord[p++] = i;
}
for (int i=1;i<=n;i++){
if (sa[i]>k){
ord[p++] = sa[i]-k;
}
}
fill(cnt.begin(),cnt.begin()+m+1,0);
for (int i=1;i<=n;i++){
cnt[rk[ord[i]]]++;
}
for (int i=1;i<=m;i++){
cnt[i] += cnt[i-1];
}
for (int i=n;i>=1;i--){
sa[cnt[rk[ord[i]]]--] = ord[i];
}
swap(ord,rk);
rk[sa[1]] = 1;
for (int i=2;i<=n;i++){
pair<int,int> p1 = {ord[sa[i-1]],sa[i-1]+k<=n?ord[sa[i-1]+k]:-1};
pair<int,int> p2 = {ord[sa[i]],sa[i]+k<=n?ord[sa[i]+k]:-1};
rk[sa[i]] = rk[sa[i-1]]+(p1<p2);
}
m = rk[sa[n]];
if (m==n) break;
}
int k = 0;
for (int i=1;i<=n;i++){
if (rk[i]==1) continue;
int j = sa[rk[i]-1];
while (i+k<=n && j+k<=n && s[i+k]==s[j+k]){
k++;
}
height[rk[i]] = k;
k = max(0,k-1);
}
}
vector<int> lg(n+1);
for (int i=2;i<=n;i++){
lg[i] = lg[i>>1]+1;
}
int K = lg[n];
vector<vector<int>> st(n+1,vector<int>(K+1));
for (int i=1;i<=n;i++){
st[i][0] = height[i];
}
for (int k=1;k<=K;k++){
int len = 1<<k;
int half = len>>1;
for (int i=1;i+len-1<=n;i++){
st[i][k] = min(st[i][k-1],st[i+half][k-1]);
}
}
auto query = [&](int l,int r){
int len = r-l+1;
int k = lg[len];
return min(st[l][k],st[r-(1<<k)+1][k]);
};
vector<vector<int>> dp(n+1,vector<int>(n+1,-INF));
dp[1][1] = 1;
for (int i=1;i<=n;i++){
for (int j=1;j<=n;j++){
dp[i][j] = max(dp[i][j],dp[i][j-1]);
if (i+j<=n && dp[i][j]!=-INF){
int r = query(min(rk[i],rk[i+j])+1,max(rk[i],rk[i+j]));
if (r>=j){
if (i+2*j<=n){
dp[i+j][j+1] = max(dp[i+j][j+1],dp[i][j]+1);
}
}
else{
if (i+j+r<=n && s[i+r]<s[i+j+r]){
dp[i+j][r+1] = max(dp[i+j][r+1],dp[i][j]+1);
}
}
}
}
}
int mx = 1;
for (int i=1;i<=n;i++){
mx = max(mx,dp[i][n-i+1]);
}
cout << mx << '\n';
vector<string> res{string(n+5,'Z')};
int R = n;
mx++;
while (R>=1){
int pos = -1;
for (int i=1;i<=R;i++){
if (dp[i][R-i+1]==mx-1 && s.substr(i,R-i+1)<res.back()){
pos = i;
break;
}
}
res.push_back(s.substr(pos,R-pos+1));
mx--;
R = pos-1;
}
reverse(res.begin(),res.end());
res.pop_back();
for (auto& v:res){
cout << v << '\n';
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
int t = 1;
// cin >> t;
while (t--) solve();
return 0;
}

浙公网安备 33010602011771号