T1:P14361 [CSP-S 2025] 社团招新 / club
考场瞪了一会就秒了。
先贪心再反悔。对于每个人先钦定他选他满意度最大的那个社团。最后如果没有社团人数超过 \(\frac{n}{2}\) 则皆大欢喜,否则将人数 \(>\frac{n}{2}\) 的社团的人再列出来,选择其中与次大值差值最小的一部分人出来将他们调整为次大值。
感觉是很好理解的。同学说他考场上严格证明了但是我不会。
T2:P14362 [CSP-S 2025] 道路修复 / road
考场上写的是 80pts 做法。
首先发现 \(k\) 很小考虑 \(2^k\) 枚举增加的乡镇。这样如果每次加点加边跑暴力最小生成树的复杂度是 \(\mathcal{O}(2^k m\log m)\)。然后再观察到最开始的 \(m\) 条边中只有 \(n-1\) 条最开始的最小生成树树边是有用的,于是复杂度变为 \(\mathcal{O}(2^k nk\log nk)\)。这样就是很简单的 80pts 做法。
考场上的心态是八十也算赢所以没有负担的去写后面的题。感谢出题人给这个做法这么多部分分。/bx
其实优化到 100pts 也很简单。只需要将乡镇的 \(nk\) 条边挪到 \(2^k\) 的循环外面排序。在 \(2^k\) 的循环内部跑最小生成树的时候如果当前边有端点不是拟增加的乡镇则直接跳过。这样的复杂度是 \(\mathcal{O}(2^knk)\) 的。
T3:P14363 [CSP-S 2025] 谐音替换 / replace
考场上会写显而易见的 \(\mathcal{O}(nq)\) 做法,期望得到 50pts。在各大平台上可以拿到 65pts~80pts 不等。然而好像 ccf 的数据就是为了防止这个做法拿到更多分而造的,于是最后一分没多拿。/ll
现在讲的是 \(\mathcal{O}(n\log L)\) 的「Trie+二维数点」写法,写了 3.5KB 终于过了。/kx
首先判掉 \(s_1=s_2\) 和 \(|t_1|\neq |t_2|\)。然后先考虑 \(\mathcal{O}(nq)\) 的做法:对于 \(s_1,s_2\) 可以处理出极大的不同字串,比如对于样例:
xabcx xadex
其极大的不同子串就是:
bc de
那么容易得到结论:如果 \(s_1,s_2\) 能替换 \(t_1,t_2\),那么他们的极大不同子串组一定是要相同的。这个可以 hash 预处理维护。
同时,截掉极大不同字串之后,\(s_1,s_2\) 会剩下两段相同的前缀 \(pre\) 和后缀 \(suf\),\(t_1,t_2\) 也会剩下相同的前后缀 \(pre',suf'\)。那么显然 \(s_1,s_2\) 能替换 \(t_1,t_2\) 的另一个必要条件是 \(pre\) 是 \(pre'\) 的后缀,\(suf\) 是 \(suf'\) 的前缀。
判断前后缀也可以提前维护一下 \(t\) 的 hash 数组。对于 \(q\) 个询问依次枚举 \(n\) 个替换串是否可以替换,判断利用 hash 是 \(\mathcal{O}(1)\) 的。
那么考虑优化这个东西。首先我们对于极大不同字串组不一样的串分组考虑。现在只处理一组内的信息,这样三维限制转化为二维。
我们把 \(pre,pre'\) 翻转,那么条件变为 \(pre\) 是 \(pre'\) 的前缀,方便处理。
我们用 \(pre,pre'\) 建立第一棵 Trie,用 \(suf,suf'\) 建立第二棵 Trie。Trie 上的前缀关系可以表示为 \(pre/suf\) 是 \(pre'/suf'\) 结点的祖先。我们处理出 Trie 树后可以将结点打上 dfn 序,那么祖先关系转化为 dfn 序的区间关系。即:
\(pre\) 的子树覆盖 dfn 序区间 \(l_1\sim r_1\),\(suf\) 的子树覆盖 dfn 序区间 \(l_2\sim r_2\)。\(pre'\) 结点的 dfn 序是 \(x\),\(suf'\) 结点的 dfn 序是 \(y\)。那么两个前缀关系可以表示为 \(x\in[l_1,r_1],y\in[l_2,r_2]\)。理解成偏序的模型:点 \((x,y)\) 被左下角为 \((l_1,l_2)\),右上角为 \((r_1,r_2)\) 的矩阵覆盖。那么我们将所有替换串构成的矩阵以及所有询问串构成的点都处理出来,问题转化为询问每个点被多少个矩阵覆盖。
这个东西是很显然的二维数点模板。使用扫描线解决(将矩阵拆为两条带权的竖线,使用 BIT 实时维护每一个纵坐标的权值)。
Tips:如何使用 BIT 维护区间加、单点查?利用树状数组的前缀和特性,利用类似差分的思想,将区间 \([l,r]\) 加 \(val\) 转化为单点 \(l\) 加 \(val\),\(r+1\) 减 \(val\)。单点查直接询问该点的前缀和。
每一步的思路很清晰但是实现起来有 \(\inf\) 个细节。空间给了 2048MB 所以放心开 Trie 树,但是不要滥开 long long。
代码:
#include<bits/stdc++.h>
#define ull unsigned long long
#define pii pair<int,int>
#define fi first
#define se second
using namespace std;
const int N=2e5+5,M=5e6+5;
const ull bas=19260817;
int n,q,tot,num,len,ans[N];
int jd[2],tr[M][26][2],dfn[M][2],sz[M][2],fwk[M],c[M<<1];
string str;
map<pair<ull,ull>,int>mp;
struct query{string s,t;int id;};
vector<pair<string,string> >v[N];
vector<query>qt[N];
vector<int>qkd;
struct node{int l,r,val,typ;};
vector<node>sd[M>>1];
void ins(int op,int siz){int now=0;for(int i=0;i<siz;i++){if(!tr[now][str[i]-'a'][op])tr[now][str[i]-'a'][op]=++num;now=tr[now][str[i]-'a'][op];}return;
}
void dfs(int x,int op){dfn[x][op]=++jd[op],sz[x][op]=1;for(int i=0;i<26;i++){if(!tr[x][i][op])continue;dfs(tr[x][i][op],op);sz[x][op]+=sz[tr[x][i][op]][op];}return;
}
pii fd(int op,int siz){int now=0;for(int i=0;i<siz;i++)now=tr[now][str[i]-'a'][op];return {dfn[now][op],sz[now][op]};
}
int lb(int x){return x&(-x);}
void upd(int x,int val){while(x<=M-5)fwk[x]+=val,x+=lb(x);return;}
int qy(int x){int res=0;while(x)res+=fwk[x],x-=lb(x);return res;}
int fnd(int x){return lower_bound(c+1,c+len+1,x)-c;}
int main(){ios::sync_with_stdio(0);cin.tie(0),cout.tie(0);cin>>n>>q;for(int i=1;i<=n;i++){string s,t;cin>>s>>t;if(s==t)continue;int m=s.size(),l,r;ull hs=0,ht=0;s=' '+s,t=' '+t;for(int j=1;j<=m;j++)if(s[j]!=t[j])r=j;for(int j=m;j>=1;j--)if(s[j]!=t[j])l=j;for(int j=l;j<=r;j++)hs=hs*bas+s[j],ht=ht*bas+t[j];pair<ull,ull>tmp={hs,ht};if(!mp.count(tmp))mp[tmp]=++tot;string t1="",t2="";for(int j=l-1;j>=1;j--)t1+=s[j];for(int j=r+1;j<=m;j++)t2+=s[j];v[mp[tmp]].push_back({t1,t2});}for(int i=1;i<=q;i++){string s,t;cin>>s>>t;if(s.size()!=t.size())continue;int m=s.size(),l,r;ull hs=0,ht=0;s=' '+s,t=' '+t;for(int j=1;j<=m;j++)if(s[j]!=t[j])r=j;for(int j=m;j>=1;j--)if(s[j]!=t[j])l=j;for(int j=l;j<=r;j++)hs=hs*bas+s[j],ht=ht*bas+t[j];pair<ull,ull>tmp={hs,ht};if(!mp.count(tmp))continue;string t1="",t2="";for(int j=l-1;j>=1;j--)t1+=s[j];for(int j=r+1;j<=m;j++)t2+=s[j];qt[mp[tmp]].push_back({t1,t2,i});}for(int T=1;T<=tot;T++){for(auto it:v[T]){str=it.fi,ins(0,it.fi.size());str=it.se,ins(1,it.se.size());}for(auto it:qt[T]){str=it.s,ins(0,it.s.size());str=it.t,ins(1,it.t.size());}dfs(0,0),dfs(0,1),len=0;for(auto it:v[T]){pii tmp1={0,0},tmp2={0,0};str=it.fi,tmp1=fd(0,it.fi.size());str=it.se,tmp2=fd(1,it.se.size());c[++len]=tmp1.fi,c[++len]=tmp1.fi+tmp1.se;}for(auto it:qt[T]){pii tmp={0,0};str=it.s,tmp=fd(0,it.s.size());c[++len]=tmp.fi;}sort(c+1,c+len+1),len=unique(c+1,c+len+1)-(c+1);for(auto it:v[T]){pii tmp1={0,0},tmp2={0,0};str=it.fi,tmp1=fd(0,it.fi.size());str=it.se,tmp2=fd(1,it.se.size());sd[fnd(tmp1.fi)].push_back({tmp2.fi,tmp2.fi+tmp2.se-1,1,0});sd[fnd(tmp1.fi+tmp1.se)].push_back({tmp2.fi,tmp2.fi+tmp2.se-1,-1,0});}for(auto it:qt[T]){pii tmp1={0,0},tmp2={0,0};str=it.s,tmp1=fd(0,it.s.size());str=it.t,tmp2=fd(1,it.t.size());sd[fnd(tmp1.fi)].push_back({tmp2.fi,it.id,0,1});}for(int i=1;i<=len;i++){for(auto it:sd[i]){if(!it.typ)upd(it.l,it.val),upd(it.r+1,-it.val);else ans[it.r]=qy(it.l);}}for(int i=1;i<=len;i++)sd[i].clear();for(int i=0;i<=num;i++)for(int j=0;j<26;j++)tr[i][j][0]=tr[i][j][1]=0;jd[0]=jd[1]=len=num=0;}for(int i=1;i<=q;i++)cout<<ans[i]<<"\n";return 0;
}