CSP2025 題解
你猜我為什么不寫游記。
代碼均已通過民間數(shù)據(jù)。
社團(tuán)招新(club)
直接貪心即可。
#include<bits/stdc++.h>
#define Debug puts("-------------------------")
using namespace std;
const int N=1e5+5;
inline int read(){
int w=1,s=0;
char c=getchar();
for(;c<'0'||c>'9';w*=(c=='-')?-1:1,c=getchar());
for(;c>='0'&&c<='9';s=s*10+c-'0',c=getchar());
return w*s;
}
int T,n,a[N][5],id[N],cnt[5],ans;
signed main(){
// freopen("club.in","r",stdin);
// freopen("club.out","w",stdout);
T=read();
while(T--){
cnt[1]=cnt[2]=cnt[3]=0;
n=read();
ans=0;
for(int i=1;i<=n;i++){
int maxn=0;
for(int j=1;j<=3;j++) a[i][j]=read(),maxn=max(maxn,a[i][j]);
for(int j=1;j<=3;j++){
if(maxn==a[i][j]){
id[i]=j,cnt[j]++;
break;
}
}
ans+=maxn;
}
int mx=max({cnt[1],cnt[2],cnt[3]});
if(mx>n/2){
int o;
for(int i=1;i<=3;i++) if(mx==cnt[i]) o=i;
vector<int> vec;
for(int i=1;i<=n;i++){
if(id[i]==o){
int se=0;
for(int j=1;j<=3;j++) if(j!=o) se=max(se,a[i][j]);
vec.push_back(a[i][o]-se);
}
}
sort(vec.begin(),vec.end());
for(int i=0;i<mx-n/2;i++) ans-=vec[i];
}
printf("%d\n",ans);
}
return 0;
}
道路修復(fù)(road)
根據(jù) Kruskal 的過程不難發(fā)現(xiàn)在加上那 \(O(nk)\) 條邊后原圖的邊只有原最小生成樹上的邊是有用的,因此有用的邊數(shù)是 \(O(nk)\) 的。
然后 \(O(2^k)\) 枚舉選擇了哪些新點(diǎn),然后跑 Kruskal 求出此時(shí)的最小生成樹即可。
可以提前在外面排好 \(O(nk)\) 條邊的順序,這樣枚舉之后就不需要再排一遍序了。
\(O(2^knk\alpha(n))\),應(yīng)該不是正解,但跑得挺快的。
code
#include<bits/stdc++.h>
#define Debug puts("-------------------------")
#define LL long long
using namespace std;
const int N=1e4+100,M=1e6+5;
inline int read(){
int w=1,s=0;
char c=getchar();
for(;c<'0'||c>'9';w*=(c=='-')?-1:1,c=getchar());
for(;c>='0'&&c<='9';s=s*10+c-'0',c=getchar());
return w*s;
}
int n,m,k,fa[N],c[15],a[15][N],tot;
bool flag[15];
LL ans=LLONG_MAX;
struct Edge{ int u,v,w,op; } E[M],MST[N];
int get(int x){ return (x==fa[x])?(x):(fa[x]=get(fa[x])); }
bool cmp(Edge x,Edge y){ return x.w<y.w; }
void Kruskal(){
for(int i=1;i<=n;i++) fa[i]=i;
sort(E+1,E+m+1,cmp);
for(int i=1;i<=m;i++){
int x=E[i].u,y=E[i].v;
if(get(x)==get(y)) continue;
MST[++tot]=E[i],fa[get(x)]=get(y);
}
}
void Init(){
Kruskal();
for(int i=1;i<=tot;i++) E[i]=MST[i];
for(int i=1;i<=k;i++) for(int j=1;j<=n;j++) E[++tot]={n+i,j,a[i][j],i};
sort(E+1,E+tot+1,cmp);
}
LL work(int S){
LL res=0;
for(int i=1;i<=k;i++) (S>>(i-1)&1)?(res+=c[i],flag[i]=true):(flag[i]=false);
for(int i=1;i<=n+k;i++) fa[i]=i;
for(int i=1;i<=tot;i++){
if(!flag[E[i].op]) continue;
int x=E[i].u,y=E[i].v,w=E[i].w;
if(get(x)==get(y)) continue;
fa[get(x)]=get(y),res+=w;
}
return res;
}
signed main(){
// freopen("road.in","r",stdin);
// freopen("road.out","w",stdout);
double beg=clock();
n=read(),m=read(),k=read();
for(int i=1;i<=m;i++){
int u=read(),v=read(),w=read();
E[i]={u,v,w,0};
}
for(int i=1;i<=k;i++){
c[i]=read();
for(int j=1;j<=n;j++) a[i][j]=read();
}
Init();
flag[0]=true;
for(int S=0;S<(1<<k);S++) ans=min(ans,work(S));
printf("%lld\n",ans);
cerr << fixed << setprecision(3) << (double)(clock()-beg) << endl;
return 0;
}
諧音替換(replace)
先求出詢問串 \(t_1,t_2\) 的 LCP 和 LCS,不妨設(shè) \(t_1=ACB,t_2=ADB\),那顯然如果一對(duì) \((s_1,s_2)\) 合法需要滿足他倆中間不一樣的部分也分別為 \(C,D\),不妨設(shè) \(s_1=A'CB',s_2=A'DB'\),那么合法的另一個(gè)條件是 \(B'\) 是 \(B\) 的前綴,\(reverse(A')\) 是 \(reverse(A)\) 的前綴。
因此先用哈希(比如 map 套 vector)將所有 \(C,D\) 相同的二元組和詢問分到同一類里,對(duì)每一類單獨(dú)求解。在每一類中,對(duì)每個(gè) \((s_1,s_2)\) 或 \((t_1,t_2)\) 將 \(A\) 倒序插入第一棵 Trie,將 \(B\) 正序插入第二棵 Trie,那么合法的條件可以轉(zhuǎn)化為 \((t_1,t_2)\) 在兩棵 Trie 上對(duì)應(yīng)的節(jié)點(diǎn)均在 \((s_1,s_2)\) 對(duì)應(yīng)的節(jié)點(diǎn)子樹內(nèi),這是經(jīng)典二維偏序,直接樹狀數(shù)組即可。
復(fù)雜度 \(O((L1+L2)|\sum| + (n+q)\log L)\)。
Tip:題目沒保證 \(|t_1|=|t_2|\),注意特判。
code
#include<bits/stdc++.h>
#define Debug puts("-------------------------")
#define ULL unsigned long long
#define PII pair<int,int>
#define fi first
#define se second
#define mk make_pair
using namespace std;
const int N=2e5+5,M=5e6+5,p=13331;
inline int read(){
int w=1,s=0;
char c=getchar();
for(;c<'0'||c>'9';w*=(c=='-')?-1:1,c=getchar());
for(;c>='0'&&c<='9';s=s*10+c-'0',c=getchar());
return w*s;
}
ULL mi[M];
int n,q,ans[N],Ls[N],Rs[N],len1[N],Lt[N],Rt[N],len2[N];
string s[N][2],t[N][2];
ULL Hash(string &s,int l,int r){
ULL H=0;
for(int i=l;i<=r;i++) H=H*p+s[i];
return H;
}
map<ULL,vector<PII> > mp;
void init(string &s,string &t,int &len,int &L,int &R,int op,int id){
len=s.size();
s=' '+s,t=' '+t;
L=0,R=len+1;
for(int j=1;j<=len;j++){
if(s[j]==t[j]) L=j;
else break;
}
for(int j=len;j>=1;j--){
if(s[j]==t[j]) R=j;
else break;
}
if(L==len) return;
int l=R-L+1;
mp[Hash(s,L+1,R-1)*mi[l]+Hash(t,L+1,R-1)].push_back({op,id});
}
namespace Solve{
struct Trie{
int ch[M][26],tot,dfn[M],siz[M],num;
void Init(){
for(int i=1;i<=tot;i++) memset(ch[i],0,sizeof ch[i]);
tot=1,num=0;
}
int insert(string &s,int l,int r,int o){
int p=1;
for(int i=(o?r:l);o?(i>=l):(i<=r);o?(i--):(i++)){
int c=s[i]-'a';
if(!ch[p][c]) ch[p][c]=++tot;
p=ch[p][c];
}
return p;
}
void dfs(int u){
dfn[u]=++num,siz[u]=1;
for(int i=0;i<=25;i++) if(ch[u][i]) dfs(ch[u][i]),siz[u]+=siz[ch[u][i]];
}
}T1,T2;
PII ed1[N],ed2[N];
struct BIT{
int c[M];
void init(int num){ for(int i=1;i<=num;i++) c[i]=0; }
int lowbit(int x){ return x&(-x); }
void add(int i,int x,int num){ for(;i<=num;i+=lowbit(i)) c[i]+=x; }
int ask(int i,int sum=0){
for(;i;i-=lowbit(i)) sum+=c[i];
return sum;
}
}Bit;
struct P{ int op,x,y1,y2; }a[N*3];
bool cmp(P x,P y){
if(x.x!=y.x) return x.x<y.x;
return x.op<y.op;
}
void work(vector<PII> vec){
T1.Init(),T2.Init();
for(PII o:vec){
int op=o.fi,id=o.se;
if(op==0) ed1[id]=mk(T1.insert(s[id][0],1,Ls[id],1),T2.insert(s[id][1],Rs[id],len1[id],0));
else ed2[id]=mk(T1.insert(t[id][0],1,Lt[id],1),T2.insert(t[id][1],Rt[id],len2[id],0));
}
T1.dfs(1),T2.dfs(1);
int cnt=0;
for(PII o:vec){
int op=o.fi,id=o.se,u,v;
if(op==0){
u=ed1[id].fi,v=ed1[id].se;
a[++cnt]={0,T1.dfn[u],T2.dfn[v],T2.dfn[v]+T2.siz[v]};
a[++cnt]={-1,T1.dfn[u]+T1.siz[u],T2.dfn[v],T2.dfn[v]+T2.siz[v]};
}
else{
u=ed2[id].fi,v=ed2[id].se;
a[++cnt]={id,T1.dfn[u],T2.dfn[v],0};
}
}
sort(a+1,a+cnt+1,cmp);
int num=T2.num;
Bit.init(num);
for(int i=1;i<=cnt;i++){
int op=a[i].op;
if(op==0) Bit.add(a[i].y1,1,num),Bit.add(a[i].y2,-1,num);
else if(op==-1) Bit.add(a[i].y1,-1,num),Bit.add(a[i].y2,1,num);
else ans[op]=Bit.ask(a[i].y1);
}
}
}
signed main(){
// freopen("replace.in","r",stdin);
// freopen("replace.out","w",stdout);
double beg=clock();
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
mi[0]=1;
for(int i=1;i<M;i++) mi[i]=mi[i-1]*p;
cin>>n>>q;
for(int i=1;i<=n;i++){
cin>>s[i][0]>>s[i][1];
init(s[i][0],s[i][1],len1[i],Ls[i],Rs[i],0,i);
}
for(int i=1;i<=q;i++){
cin>>t[i][0]>>t[i][1];
if(t[i][0].size()!=t[i][1].size()) continue;
init(t[i][0],t[i][1],len2[i],Lt[i],Rt[i],1,i);
}
for(pair<ULL,vector<PII> > o:mp) Solve::work(o.se);
for(int i=1;i<=q;i++) printf("%lld\n",ans[i]);
cerr << fixed << setprecision(3) << (double)(clock()-beg) << endl;
return 0;
}
員工招聘(employ)
設(shè) \(cnt_i\) 表示耐心值為 \(i\) 的人數(shù),\(pre\) 是 \(cnt\) 的前綴和。
一個(gè)顯然的 DP 狀態(tài)是 \(f_{i,j}\) 表示前 \(i\) 個(gè)人,有 \(j\) 個(gè)人被拒絕了的方案數(shù)。但發(fā)現(xiàn)這樣是轉(zhuǎn)移不動(dòng)的,因?yàn)楸热绠?dāng)我們需要往第 \(i+1\) 放一個(gè) \(c\le j\) 的人時(shí),由于我們并不知道前 \(i\) 個(gè)人具體的耐心值情況,所以并不知道還可以放的人有多少。
因此我們不妨加一維 \(f_{i,j,k}\) 其中 \(k\) 表示前 \(i\) 個(gè)人中有 \(k\) 個(gè)人的耐心值 \(\le j\)。此時(shí)我們就知道,如果我要在 \(i+1\) 放一個(gè)耐心值 \(\le j\) 的人,那么總共就有 \(pre_j-k\) 的方案了。但是一個(gè)新的問題是,當(dāng)?shù)?\(i+1\) 個(gè)人被拒絕時(shí),\(j\) 會(huì)變成 \(j+1\),此時(shí)我們就需要知道 \(c\le j+1\) 的人數(shù)了,但顯然根據(jù)我們目前的狀態(tài),這個(gè)信息是不能直接推出來的。
解決方法其實(shí)也比較套路,我們考慮貢獻(xiàn)延后計(jì)算,先不去確定那 \(i-k\) 個(gè) \(c>j\) 的人的具體的耐心值,而是在 \(j\to j+1\) 時(shí),再去分配有哪些人的 \(c=j+1\),具體的,考慮刷表法:
- \(s_{i+1}=1\) 且第 \(i+1\) 個(gè)人被錄取,選一個(gè) \(c>j\) 的人即可,但先不去確定他是誰:\(f_{i,j,k} \to f_{i+1,j,k}\)。
- \(s_{i+1}=1\) 且第 \(i+1\) 個(gè)人不被錄取,先從 \(pre_j-k\) 個(gè)人當(dāng)中選一個(gè) \(c\le j\) 的人,再枚舉 \(l\) 表示那 \(i-k\) 個(gè)人當(dāng)中有幾個(gè)人的耐心值為 \(j+1\):\(f_{i,j,k}\times (pre_j-k) \times \binom{i-k}{l} \times \binom{cnt_{j+1}}{l} \times l! \to f_{i+1,j+1,k+l+1}\)。
- \(s_{i+1}=0\) 此時(shí)無論填什么都會(huì)拒絕錄取,因此轉(zhuǎn)移跟 2 差不多,具體看代碼。
看似狀態(tài)數(shù)是 \(O(n^3)\),轉(zhuǎn)移枚舉 \(l\) 是 \(O(n)\) 的,但是注意到 \(l\le cnt_{j+1}\),因此當(dāng) \(i,k\) 確定時(shí),對(duì)于所有 \(1\le j\le n\),\(l\) 的總枚舉量為 \(O(n)\)??倧?fù)雜度為 \(O(n^3)\)。
code
#include<bits/stdc++.h>
#define Debug puts("-------------------------")
using namespace std;
const int N=5e2+5,mod=998244353;
inline int read(){
int w=1,s=0;
char c=getchar();
for(;c<'0'||c>'9';w*=(c=='-')?-1:1,c=getchar());
for(;c>='0'&&c<='9';s=s*10+c-'0',c=getchar());
return w*s;
}
int n,m,cnt[N],pre[N],fact[N],inv[N],f[N][N][N];
char s[N];
int C(int n,int m){
if(n<0||m<0||n<m) return 0;
return 1ll*fact[n]*inv[m]%mod*inv[n-m]%mod;
}
void Add(int &x,int y){ (x+=y)%=mod; }
signed main(){
// freopen("employ.in","r",stdin);
// freopen("employ.out","w",stdout);
double beg=clock();
fact[0]=inv[0]=inv[1]=1;
for(int i=1;i<N;i++) fact[i]=1ll*fact[i-1]*i%mod;
for(int i=2;i<N;i++) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
for(int i=2;i<N;i++) inv[i]=1ll*inv[i-1]*inv[i]%mod;
scanf("%d%d%s",&n,&m,s+1);
for(int i=1,x;i<=n;i++) scanf("%d",&x),cnt[x]++;
pre[0]=cnt[0];
for(int i=1;i<=n;i++) pre[i]=cnt[i]+pre[i-1];
f[0][0][0]=1;
for(int i=0;i<n;i++){
for(int j=0;j<=i;j++){
for(int k=0;k<=min(i,pre[j]);k++){
if(!f[i][j][k]) continue;
int val=f[i][j][k];
if(s[i+1]=='1'){
Add(f[i+1][j][k],val); //通過,選一個(gè)耐心值 >j 的人
if(k<pre[j]){
for(int l=0;l<=min(cnt[j+1],i-k);l++){ //不通過,選一個(gè)耐心值 <=j 的人,并決策之前哪些人的耐心值 =j+1
Add(f[i+1][j+1][k+l+1],1ll*val*(pre[j]-k)%mod*C(i-k,l)%mod*C(cnt[j+1],l)%mod*fact[l]%mod);
}
}
}
else{ //必定失敗
for(int l=0;l<=min(cnt[j+1],i-k);l++){
Add(f[i+1][j+1][k+l],1ll*val*C(i-k,l)%mod*C(cnt[j+1],l)%mod*fact[l]%mod); //耐心值 >j+1
if(k+l<pre[j+1]) Add(f[i+1][j+1][k+l+1],1ll*val*(pre[j+1]-k-l)%mod*C(i-k,l)%mod*C(cnt[j+1],l)%mod*fact[l]%mod); //耐心值 <=j+1
}
}
}
}
}
int ans=0;
for(int j=0;j<=n-m;j++) Add(ans,1ll*f[n][j][pre[j]]*fact[n-pre[j]]%mod);
printf("%d\n",ans);
cerr << fixed << setprecision(3) << (double)(clock()-beg) << endl;
return 0;
}
后記
考場(chǎng)上玩了一整場(chǎng)原神,什么都沒寫出來。不知道場(chǎng)上咋想的,認(rèn)為 \(dfn_x\le dfn_y\le dfn_x+siz_x-1\) 是二維偏序,結(jié)果就變成四維偏序了,然后都寫一半了認(rèn)為自己做法是錯(cuò)的把代碼全刪了。成功被機(jī)房所有同學(xué)偏序。
希望是給 NOIP 攢 RP 吧。

浙公網(wǎng)安備 33010602011771號(hào)