なんか後半ほど簡単になっていったんですが…。
https://atcoder.jp/contests/abc268/tasks/abc268_g
問題
N個の異なる文字列S[i]が与えられる。
アルファベット小文字26文字の辞書順が、26!通りのうち等確率ランダムに選ばれるとする。
その時、各文字列は、N個中辞書順で何番目に来るか、期待値を求めよ。
解法
文字列A,Bがあったときの辞書順での順序を考える。
互いが互いのprefixではない場合は、それぞれ1/2の確率で小さい方となる。
A,Bいずれかがいずれかのprefixである場合、常に大小関係が固定される。
f(i) := i番目の文字列のprefixであるような文字列の数 (i番目の文字列自身は除く)
g(i) := i番目の文字列をprefixとして含むような文字列の数 (i番目の文字列自身は除く)
とすると、i番目の順位の期待値は1+f(i)+(N-1-f(i)-g(i))/2となる。
f(i),g(i)はN個の文字列でTrieを構築し、そこを探索することでO(sum(S))で求められる。
int N; vector<string> S; map<string,int> M; const int NUMC=26; class Trie { public: vector<vector<int> > V; int tar[505050]; pair<vector<int>,int> find(string s) { int cur=0; vector<int> ret; FORR(c,s) { cur=V[cur][c+1]; if(tar[cur]!=-1) ret.push_back(tar[cur]); } return {ret,cur}; } void create(vector<string> S) { // 0 is for backtrack V.clear(); V.push_back(vector<int>(NUMC+1)); MINUS(tar); sort(S.begin(),S.end()); FORR(s,S) { int cur=0; FORR(c,s) { if(V[cur][c+1]==0) V.push_back(vector<int>(NUMC+1)),V[cur][c+1]=V.size()-1; cur=V[cur][c+1]; } } } }; Trie t; const ll mo=998244353; ll modpow(ll a, ll n = mo-2) { ll r=1;a%=mo; while(n) r=r*((n%2)?a:1)%mo,a=a*a%mo,n>>=1; return r; } int le[505050],mor[505050]; void solve() { int i,j,k,l,r,x,y; string s; cin>>N; FOR(i,N) { cin>>s; FORR(c,s) c-='a'; S.push_back(s); } t.create(S); FOR(i,N) { x=t.find(S[i]).second; t.tar[x]=i; } FOR(i,N) { auto v=t.find(S[i]).first; v.pop_back(); FORR(a,v) { le[i]++, mor[a]++; } } FOR(i,N) { int oth=N-1-le[i]-mor[i]; ll a=(1+le[i]+oth*modpow(2))%mo; cout<<a<<endl; } }
まとめ
これはすんなり。