一、前言
第六次 AK,记录一下。
二、正文
第 A 题 Secret Numbers
对新手很有意义的题目。
关键点在于如何判断一个字符是否为数字,我们发现数字的 ASCII 编码都是连续的,所以说 ASCII 码在 0 0 0 的 ASCII 码与 9 9 9 的 ASCII 码之间的字符都为数字。
接下来就是枚举每个字符了,本代码使用 C++14 的 auto 枚举方式。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
signed main(){
string s; cin>>s;
for (auto x:s){
if (x>='0'&&x<='9') cout<<x;
}
}
第 B 题 Gift
简单的题目,考察 vector 的使用。
维护 n n n 个 vector,对于某个人 i i i,他给 j j j 礼物就相当于将 i i i 插入第 j j j 个 vector。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
vector <int> vc[2000010];
signed main(){
int n; cin>>n;
for (int i=1; i<=n; i++){
int x; cin>>x;
for (int j=1; j<=x; j++){
int v; cin>>v;
vc[v].push_back(i);
}
}
for (int i=1; i<=n; i++){
cout<<vc[i].size()<<" ";
for (auto x:vc[i]) cout<<x<<" ";
cout<<"\n";
}
}
第 C 题 Not Covered Points
按照横坐标从小到大排序。
如果对于当前点,如果前面有点的纵坐标小于这个点的纵坐标,那么这个点就不是合法的。
维护前缀纵坐标最小值,答案就是这个最小值的更新次数。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
pair <int,int> pr[300010];
signed main(){
int n; cin>>n;
for (int i=1; i<=n; i++){
cin>>pr[i].first>>pr[i].second;
}
sort(pr+1,pr+n+1);
int mn=1000000000,ans=0;
for (int i=1; i<=n; i++){
if (pr[i].second<=mn) ans++,mn=pr[i].second;
}
cout<<ans;
}
第 D 题 Accomplice
枚举案发时间 i i i。
可能参与这起案件的凶手要满足 l ≤ i l\le i l≤i 且 r ≥ i + m − 1 r\ge i+m-1 r≥i+m−1。
按照 l l l 排序,使用树状数组就可以查询这一时间的可能的凶手人数 c c c。
那么时间 i i i 对于答案的贡献为 C c 2 C_{c}^2 Cc2。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
pair <int,int> pr[300010];
int C[2000010];
void insert(int id){
while (id<=2000000) C[id]++,id+=(id&-id);
}
int query(int id){
int ans=0;
while (id) ans+=C[id],id-=(id&-id);
return ans;
}
signed main(){
int n,m; cin>>n>>m;
for (int i=1; i<=n; i++){
cin>>pr[i].first>>pr[i].second;
}
sort(pr+1,pr+n+1);
int id=1,ans=0;
for (int i=1; i<=1000000; i++){
while (id<=n&&pr[id].first<=i){
insert(pr[id].second); id++;
}
int val=query(2000000)-query(i+m-1);
ans+=val*(val-1)/2;
}
cout<<ans;
}
第 E 题 Alternating Costs
假设横坐标所需移动距离为 x x x,纵坐标所需移动距离为 y y y。
显然我们可以进行 2 × min ( x , y ) 2\times \min(x,y) 2×min(x,y) 次操作,每次操作都使用 m i n ( a , b ) min(a,b) min(a,b) 的代价。
对于下面的操作来说,我们因为只能朝着一个方向移动,所以无法避免使用 a , b a,b a,b 中较大数。
当然我们的 a , b a,b a,b 如果相差很大,我们也可以绕路,用 3 × min ( a , b ) 3\times \min(a,b) 3×min(a,b) 代替 max ( a , b ) \max(a,b) max(a,b)。
对于 min ( a , b ) , max ( a , b ) \min(a,b),\max(a,b) min(a,b),max(a,b) 的出现次数,我们分讨四种情况即可。
代码中对情况进行了简化,大家可以对着代码进行理解。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main(){
int t; cin>>t;
while (t--){
int a,b,x,y; cin>>a>>b>>x>>y;
x=abs(x); y=abs(y);
int ans=min(x,y)*min(a,b)*2;
int val=min(x,y); x-=val; y-=val;
if (x>0){
ans+=(x+1)/2*min(a,3*b)+x/2*min(b,3*a);
}
else{
ans+=(y+1)/2*min(b,3*a)+y/2*min(a,3*b);
}
cout<<ans<<"\n";
}
}
第 F 题 More ABC
大炮打蚊子抢到首 A。
本题存在原题:原题链接,将 MOO 改为 ABC 即可。
对于题解,去看那道题吧,大致来说就是分治+闵可夫斯基和。
这里给出代码,以供参考:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
typedef vector <int> minkowski;
inline minkowski operator+(minkowski a,minkowski b){
vector <int> ans;
int ida=0,idb=0;
while (ida!=a.size()||idb!=b.size()){
if (ida!=a.size()&&(idb==b.size()||a[ida]>b[idb])) ans.push_back(a[ida]),ida++;
else ans.push_back(b[idb]),idb++;
}
return ans;
}
inline minkowski max(minkowski a,minkowski b){
for (int i=1; i<a.size(); i++) a[i]+=a[i-1];
for (int i=1; i<b.size(); i++) b[i]+=b[i-1];
int sz=max(a.size(),b.size());
vector <int> ans;
for (int i=0; i<sz; i++) ans.push_back(max((i<a.size()?a[i]:(int)-1e18),(i<b.size()?b[i]:(int)-1e18)));
for (int i=sz-1; i>0; i--) ans[i]=ans[i]-ans[i-1];
return ans;
}
#define ls (id<<1)
#define rs (id<<1|1)
#define mid (l+r>>1)
minkowski dp[1200010][3][3];
int a[300010];
int n,k;
void solve(int id,int l,int r){
if (l>=r){
for (int i=0; i<k; i++) for (int j=0; j<k; j++) dp[id][i][j].clear();
if (k<=r-l+1) dp[id][0][0].push_back(a[l]);
return ;
}
solve(ls,l,mid); solve(rs,mid+1,r);
for (int i=0; i<k; i++){
for (int j=0; j<k; j++){
dp[id][i][j].clear();
if (i+j>r-l+1||i>mid-l+1||j>r-mid) continue;
dp[id][i][j]=dp[ls][i][0]+dp[rs][0][j];
for (int p=1; p<k; p++){
if (i+p>mid-l+1||k-p+j>r-mid) continue;
dp[id][i][j]=max(dp[id][i][j],dp[ls][i][p]+dp[rs][k-p][j]+(minkowski){a[mid+k-p]});
}
}
}
}
int b[300010];
signed main(){
int t; cin>>t;
while (t--){
string s; cin>>s;
int n=s.size();
int c; cin>>c;
for (int i=0; i+2<s.size(); i++){
if (s.substr(i,3)=="ABC") c++;
}
k=3;
for (int i=1; i<=n; i++) b[i]=1;
for (int i=k; i<=n; i++){
a[i]=0;
for (int j=i-k+1; j<=i; j++){
a[i]-=b[j]*(j==i-k+1?(s[j-1]!='A'):(j==i-k+2?(s[j-1]!='B'):(s[j-1]!='C')));
}
}
solve(1,1,n);
if (dp[1][0][0].size()<c){
cout<<"-1\n";
continue;
}
int ans=0;
for (int i=0; i<c; i++){
ans+=dp[1][0][0][i];
}
cout<<-ans<<"\n";
}
}
第 G 题 Completely Wrong
容斥。
假设数字 i i i 在 c c c 数组中出现 a i a_i ai 次,在 k k k 数组中出现 b i b_i bi 次。
那么如果颜色 i i i 对了 j j j 次的贡献为 C ( b i , j ) × ( a i − j ) ! − 1 C(b_i,j)\times (a_i-j)!^{-1} C(bi,j)×(ai−j)!−1,最后如果 j j j 之和为 s s s,那么对答案的贡献再乘一个 ( − 1 ) s × ( n − s ) ! (-1)^s\times (n-s)! (−1)s×(n−s)!。
理解起来也不难,前半部分表示选择对的位置,后半部分应用的是一个常见公式:将 n n n 长数组染成 a 1 a_1 a1个颜色 1 1 1, a 2 a_2 a2个颜色 2 2 2......, a m a_m am 个颜色 m m m 的方案数为 n ! × ( a 1 ) ! − 1 × ( a 2 ) ! − 1 ⋯ × ( a n ) ! − 1 n!\times (a_1)!^{-1}\times (a_2)!^{-1}\dots\times (a_n)!^{-1} n!×(a1)!−1×(a2)!−1⋯×(an)!−1。
我们可以使用多项式乘法解决这个问题,把一个颜色理解为一个多项式,把它们乘起来时,只需要类似石子合并的方式乘即可,时间复杂度两只老哥,常数还可以。
代码(取到主函数部分为多项式乘法(NTT)、多项式各类函数、FWT(FMT)的全部模板,可供大家参考):
cpp
//Timmy's poly
//writen by @Timmylyx
//update to 7-3
//last update:2026/4/24
//too interesting
//I love poly!
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define poly vector<int>
const int M=998244353,ima=86583718,G1=3,G2=(M+1)/3,inv2=(M+1)/2;
vector <int> inv;
//it is time to prepare
inline int Pow(int a,int n){
//faster pow
int ans=1;
while (n){
if (n&1) ans=ans*a%M;
a=a*a%M; n>>=1;
}
return ans;
}
void init(int len){
inv.resize(len+1);
for (int i=0; i<=len; i++){
inv[i]=Pow(i,M-2);
}
}
//these are ins ans outs
void readin(poly &p,int n){
while (n--){
int x; cin>>x;
p.push_back(x);
}
}
void writeout(poly p){
for (auto x:p) cout<<x<<" ";
}
//start from easy-FFT
void FFT(poly &x,int type){
int n=x.size();
poly r; r.resize(n);
//resize done
for (int i=0; i<n; i++){
r[i]=((r[i>>1]>>1)+((i&1)*(n>>1)));
}
for (int i=0; i<n; i++){
if (i<r[i]) swap(x[i],x[r[i]]);
}
//exchange done
for (int now=1; now<n; now<<=1){
int wn=Pow((type==1?G1:G2),(M-1)/(now<<1));
for (int tmp=(now<<1),j=0; j<n; j+=tmp){
for (int k=j,w=1; k<j+now; k++,w=w*wn%M){
int X=x[k],Y=w*x[k+now]%M;
x[k]=(X+Y)%M;
x[k+now]=(X-Y+M)%M;
}
}
}
}
//four operators
poly operator +(poly a,poly b){
int len=max(a.size(),b.size());
a.resize(len); b.resize(len);
for (int i=0; i<len; i++) a[i]=(a[i]+b[i])%M;
return a;
}
poly operator -(poly a,poly b){
int len=max(a.size(),b.size());
a.resize(len); b.resize(len);
for (int i=0; i<len; i++) a[i]=(a[i]-b[i]+M)%M;
return a;
}
poly operator *(poly a,poly b){
int n=a.size(),m=b.size();
int len=1; while (len<n+m) len*=2;
a.resize(len); b.resize(len);
FFT(a,1); FFT(b,1);
a.resize(max(a.size(),b.size()));
b.resize(max(a.size(),b.size()));
for (int i=0; i<a.size(); i++) a[i]=a[i]*b[i]%M;
FFT(a,-1);
for (int i=0; i<a.size(); i++) a[i]=a[i]*inv[a.size()]%M;
a.resize(n+m-1);
return a;
}
poly get_inv(poly x){
int m=1; poly ans;
ans.push_back(Pow(x[0],M-2));
while (m<x.size()){
m*=2;
auto tmp=x;
tmp.resize(m);
ans=ans*((poly){2}-tmp*ans);
ans.resize(m);
}
ans.resize(x.size());
return ans;
}
poly operator /(poly a,poly b){
return a*get_inv(b);
}
//some calculus
poly get_der(poly p){
int n=p.size();
poly ans;
for (int i=1; i<n; i++) ans.push_back(i*p[i]%M);
return ans;
}
poly get_int(poly p){
int n=p.size();
poly ans={0};
for (int i=0; i<n; i++){
ans.push_back(p[i]*inv[i+1]%M);
}
return ans;
}
poly get_ln(poly p){
poly ans=get_int(get_der(p)/p);
ans.resize(p.size());
return ans;
}
poly get_exp(poly p){
poly ans={1};
int m=1;
while (m<p.size()){
m*=2;
ans.resize(m); //remember to resize
poly tmp=p; tmp.resize(m);
ans=ans*((poly){1}-get_ln(ans)+tmp);
ans.resize(m);
}
ans.resize(p.size());
return ans;
}
poly Pow(poly a,int n,int N,int N2){
if (a[0]==1){
return get_exp(get_ln(a)*((poly){n}));
}
if (a[0]==0){
//delete some zeros
int id=-1;
for (int i=1; i<a.size(); i++){
if (a[i]!=0){
id=i; break;
}
}
if (id==-1){
poly ans;
ans.resize(a.size());
return ans;
}
else{
poly b;
for (int i=id; i<a.size(); i++) b.push_back(a[i]);
poly val=Pow(b,n,N,N2);
int tmp=min((int)a.size(),id*N2);
poly ans;
ans.resize(tmp);
for (int i=0; i<a.size()-tmp; i++) ans.push_back(val[i]);
return ans;
}
}
int bas=a[0],bas2=Pow(bas,N);
for (int i=0; i<a.size(); i++) a[i]=a[i]*Pow(bas,M-2)%M;
poly ans=Pow(a,n,N,N2);
for (int i=0; i<a.size(); i++) ans[i]=ans[i]*bas2%M;
return ans;
}
//quadratic residue
int a,omega;
int brute_force(int n){
for (int i=0; i<M; i++){
if (i*i%M==n) return i;
}
}
//complex for cipolla
struct cipolla{
int rel,ima;
};
cipolla operator+(cipolla a,cipolla b){
return cipolla{(a.rel+b.rel)%M,(a.ima+b.ima)%M};
}
cipolla operator-(cipolla a,cipolla b){
return cipolla{(a.rel-b.rel+M)%M,(a.ima-b.ima+M)%M};
}
cipolla operator*(cipolla a,cipolla b){
return cipolla{(a.rel*b.rel+a.ima*b.ima%M*omega)%M,(a.ima*b.rel+b.ima*a.rel)%M};
}
cipolla Pow(cipolla a,int n){
cipolla ans=cipolla{1,0};
while (n){
if (n&1) ans=ans*a;
a=a*a; n>>=1;
}
return ans;
}
mt19937 rnd(chrono::steady_clock::now().time_since_epoch().count());
void cipolla_init(int n){
while (true){
a=rnd()%M;
if (Pow((a*a-n+M)%M,(M-1)/2)==M-1){
omega=(a*a-n+M)%M;
return ;
}
}
}
int cipolla_work(int n){
cipolla_init(n);
cipolla tmp=Pow(cipolla{a,1},(M+1)/2);
return min(tmp.rel,M-tmp.rel);
}
poly sqrt(poly p){
poly ans={cipolla_work(p[0])};
int m=1;
while (m<p.size()){
m*=2;
auto tmp=p; tmp.resize(m);
ans=ans-(ans*ans-tmp)/((poly){2}*ans);
ans.resize(m);
}
ans.resize(p.size());
return ans;
}
//the last operator
poly zhengchu(poly a,poly b){
int n=a.size(),m=b.size();
if (n<m) a.resize(m),n=m;
int len=n-m+1;
reverse(a.begin(),a.end()); a.resize(len);
reverse(b.begin(),b.end()); b.resize(len);
poly ans=a/b; ans.resize(len); reverse(ans.begin(),ans.end());
return ans;
}
poly operator%(poly a,poly b){
poly ans=a-zhengchu(a,b)*b;
ans.resize(b.size()-1);
return ans;
}
//trigonometric function
poly get_sin(poly p){
poly I=(poly){ima};
poly ans=get_exp(I*p)-get_exp((poly){0}-I*p);
ans=ans/((poly){2}*I);
return ans;
}
poly get_cos(poly p){
poly I=(poly){ima};
poly ans=get_exp(I*p)+get_exp((poly){0}-I*p);
ans=ans/((poly){2});
return ans;
}
//linear recursion
poly linear_recursion_pow(poly f,int n){
poly ans=(poly){0,1},a=(poly){0,1};
while (n){
if (n&1) ans=ans*a%f;
a=a*a%f; n>>=1;
}
return ans;
}
int linear_recursion_easy(poly f,poly a,int n){
reverse(f.begin(),f.end());
for (auto&x:f) x=-x;
f.push_back(1);
poly p=linear_recursion_pow(f,n-1);
int ans=0;
for (int i=0; i<a.size(); i++){
ans=(ans+p[i]*a[i])%M;
}
return (ans%M+M)%M;
}
//FMT
void fill(poly &p){
int len=1;
while (len<p.size()) len<<=1;
p.resize(len);
}
void FMT_or(poly &p,int op){
for (int w=1,len=p.size(); w<len; w<<=1){
for (int i=1; i<len; i++){
if (i&w) p[i]=(p[i]+op*p[i^w]+M)%M;
}
}
}
void FMT_and(poly &p,int op){
for (int w=1,len=p.size(); w<len; w<<=1){
for (int i=0; i<len; i++){
if ((i&w)==0) p[i]=(p[i]+op*p[i^w]+M)%M;
}
}
}
poly operator|(poly a,poly b){
FMT_or(a,1); FMT_or(b,1);
for (int i=0; i<a.size(); i++) a[i]=a[i]*b[i]%M;
FMT_or(a,-1); return a;
}
poly operator&(poly a,poly b){
FMT_and(a,1); FMT_and(b,1);
for (int i=0; i<a.size(); i++) a[i]=a[i]*b[i]%M;
FMT_and(a,-1); return a;
}
//FWT
void FWT_xor(poly &a,int op){
for (int w=1,len=a.size(); w<len; w<<=1){
for (int l=0,r=w-1; r<len; l+=(w<<1),r+=(w<<1)){
for (int k=l; k<=r; k++){
a[k]=(a[k]+a[k+w])%M;
a[k+w]=(a[k]+2*M-2*a[k+w])%M;
a[k]=a[k]*(op==-1?inv2:1)%M;
a[k+w]=a[k+w]*(op==-1?inv2:1)%M;
}
}
}
}
poly operator^(poly a,poly b){
FWT_xor(a,1); FWT_xor(b,1);
for (int i=0; i<a.size(); i++) a[i]=a[i]*b[i]%M;
FWT_xor(a,-1); return a;
}
//subset convolution
int pop_count(int x){
int ans=0;
while (x){
ans++; x&=x-1;
}
return ans;
}
poly subset(poly a,poly b){
int len=a.size(),p=log2(len);
poly x[30],y[30],z[30],ans; ans.resize(len);
for (int i=0; i<=p; i++) x[i].resize(len),y[i].resize(len),z[i].resize(len);
for (int i=0; i<=p; i++){
for (int j=0; j<len; j++){
if (pop_count(j)==i) x[i][j]=a[j],y[i][j]=b[j];
else x[i][j]=0,y[i][j]=0;
}
FMT_or(x[i],1); FMT_or(y[i],1);
}
for (int i=0; i<=p; i++){
for (int j=0; j<=i; j++){
for (int k=0; k<len; k++) z[i][k]=(z[i][k]+x[j][k]*y[i-j][k])%M;
}
FMT_or(z[i],-1);
for (int k=0; k<len; k++) if (pop_count(k)==i) ans[k]=z[i][k];
}
return ans;
}
int cnt1[200010],cnt2[200010];
int fac[200010];
int C(int n,int m){
return fac[n]*Pow(fac[m],M-2)%M*Pow(fac[n-m],M-2)%M;
}
poly p[200010];
priority_queue <pair<int,int>,vector <pair<int,int>>,greater<pair<int,int>>> q;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
init(5e5);
fac[0]=1;
for (int i=1; i<=200000; i++){
fac[i]=fac[i-1]*i%M;
}
int n; cin>>n;
for (int i=1; i<=n; i++){
int x; cin>>x; cnt1[x]++;
}
for (int i=1; i<=n; i++){
int x; cin>>x; cnt2[x]++;
}
for (int i=1; i<=n; i++){
for (int j=0; j<=min(cnt1[i],cnt2[i]); j++){
p[i].push_back(C(cnt2[i],j)*Pow(fac[cnt1[i]-j],M-2)%M);
}
q.push({p[i].size(),i});
}
while (q.size()>=2){
auto x=q.top().second; q.pop();
auto y=q.top().second; q.pop();
p[x]=p[x]*p[y];
q.push({p[x].size(),x});
}
int lst=q.top().second;
int tmp=Pow(fac[n],M-2);
int ans=0;
for (int i=1; i<=n; i++) tmp=tmp*fac[cnt1[i]]%M;
for (int i=0; i<p[lst].size(); i++){
if (i%2==0) ans=(ans+p[lst][i]*fac[n-i])%M;
else ans=(ans-p[lst][i]*fac[n-i]%M+M)%M;
}
cout<<ans*tmp%M;
return 0;
}