A - CF594E Cutting the Line
题意
有一个字符串 S S S。给出 k k k,你可以将字符串划分成不超过 k k k段,将每一段翻转或者不翻转,然后按照原来的顺序拼接起来。问最后能够得到的字典序最小的字符串。 ∣ S ∣ ≤ 5000000 |S|\le 5000000 ∣S∣≤5000000
Sol
当 k > 2 k>2 k>2时
考虑 S R S^R SR中字典序最小的后缀 s s s。我们首先要最大化 s s s在最终的串的开头的出现次数。
s s s在 S R S^R SR中出现的那些位置一定没有交集,因为如果存在交集则 s s s具有周期,则 s s s具有字典序小于 s s s的后缀,与定义矛盾。
如果假设 S R S^R SR是这样的: t 1 s a 1 t 2 s a 2 ⋯ t p s a p t_1s^{a_1} t_2s^{a_2}\cdots t_{p} s^{a_p} t1sa1t2sa2⋯tpsap,则我们一定能够让最终的字符串的开头有 a p + max i ∈ [ 1 , p ) { a i } a_p + \max_{ i \in [1,p) } \{ a_i \} ap+maxi∈[1,p){ai}个 s s s,这是因为我们可以这样操作:第一段翻成 s a p s^{a_p} sap,第二段翻成 s a i t i + 1 s a i + 1 ⋯ t p s^{a_i}t_{i+1}s^{a_{i+1}}\cdots t_p saiti+1sai+1⋯tp。并且这也是我们能够在开头放的 s s s最多的操作方法。此时前两段的划分和是否翻转都已经确定,我们继续去确定剩下的字符串允许划分为至多 k − 2 k-2 k−2段能够得到的字典序最小的就可以了。
进一步挖掘,发现这个过程本质上就是:将 S R S^R SR这个字符串 L y n d o n Lyndon Lyndon分解之后,每一次翻转 S R S^R SR末尾的相等的一组 L y n d o n Lyndon Lyndon串。注意特殊考虑串长为 1 1 1的情况,此时不需要翻转,直接将连续的这样串长为 1 1 1的 L y n d o n Lyndon Lyndon串划分在一段里面就可以了。
当 k ≤ 2 k\le 2 k≤2时
如果 k = 1 k=1 k=1,直接讨论翻转和不翻转两种情况即可。
如果 k = 2 k=2 k=2,则讨论划分成两段之后是否翻转第一段这两种情况。
第一段不翻转:则我们枚举在哪个位置断开、将后缀翻转,然后取最优的。两个断开位置的方案的比较可以在用 Z − a l g o r i t h m Z-algorithm Z−algorithm预处理之后做到 O ( 1 ) O(1) O(1)。
第一段要翻转:则我们一定是先考虑最小化翻转之后的开头那段。设 s R s^R sR的最小后缀是 w ′ w' w′,则我们选取去翻转的那个 S R S^R SR的后缀,一定有 w ′ w' w′作为前缀。考虑这样一个以 w ′ w' w′作为前缀的后缀 T T T,假设包含 w ′ w' w′的最长 L y n d o n Lyndon Lyndon串是 w w w, T T T去掉开头的 w w w之后剩下的后缀为 B B B,如下图。
由于 T T T不能包含严格小于 T T T(即不为 T T T的前缀且字典序小于 T T T)的后缀,否则翻转那个严格小于 T T T的后缀一定比翻转 T T T更优秀,所以 B B B要么同时也是 T T T的前缀,要么字典序严格大于 T T T。
根据 L y n d o n Lyndon Lyndon串的定义,我们可以知道 B B B的最长的为 L y n d o n Lyndon Lyndon串的前缀一定小于等于 w w w,否则可以将 B B B开头的最长的 L y n d o n Lyndon Lyndon串和 w w w合并得到一个更长的 L y n d o n Lyndon Lyndon串,与 w w w的定义矛盾。
而若 B B B的最长的 L y n d o n Lyndon Lyndon串前缀严格小于 w w w,则 B B B的字典序严格小于 T T T,与前面的分析矛盾了。所以 B B B的最长的 L y n d o n Lyndon Lyndon串前缀一定等于 w w w。
进一步推导,可以得出结论: T T T一定是 w + w + w + ⋯ + w ′ w + w + w +\cdots + w' w+w+w+⋯+w′。
最后,考虑 w + w ′ w + w' w+w′和 w ′ + w w' + w w′+w的大小关系,我们可以知道最优的翻转方式要么不翻 w w w只翻 w ′ w' w′,要么就把所有的 w w w和 w ′ w' w′都翻过来。在两种情况之中取一个较优的就可以了。
Code
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=5e6+10;
void Lyndon(char *S,int n,int *xi,int *len,int &m) {
for(int i=1,j,k;i<=n;) {
for(j=i,k=i+1;k<=n&&S[k]>=S[j];++k)
if(S[k]==S[j]) ++j;
else j=i;
xi[++m]=i,len[m]=k-j;
while(i+(k-j)-1<k) i+=k-j;
}
xi[m+1]=n+1;
}
void Exkmp(char *S,int n,int *nxt) {
nxt[1]=n;
for(int i=2,p=0;i<=n;++i) {
nxt[i]=i<=p+nxt[p]-1?min(p+nxt[p]-i,nxt[i-p+1]):0;
while(i+nxt[i]<=n&&S[i+nxt[i]]==S[nxt[i]+1]) nxt[i]++;
if(i+nxt[i]>p+nxt[p]) p=i;
}
}
struct item {
char S[N]; int n;
void add0(char *str,int len) { for(int i=0;i<len;++i) S[++n]=str[i]; }
void add1(char *str,int len) { for(int i=len-1;i>=0;--i) S[++n]=str[i]; }
void add2(char *str,int len) {
int l=0,r=len-1;
while(l<r&&str[l]==str[r]) l++,r--;
if(str[l]<str[r]) add0(str,len);
else add1(str,len);
}
friend bool operator <(item A,item B) {
for(int i=1;i<=min(A.n,B.n);++i)
if(A.S[i]!=B.S[i]) return A.S[i]<B.S[i];
return A.n<B.n;
}
void print() { for(int i=1;i<=n;++i) putchar(S[i]); }
}ans,ans2,ans3;
char S[N],Sr[N],St[N<<1];
int nxt[N<<1],xi[N],len[N],n,m,k;
int main() {
scanf("%s",S+1),rd(k); n=strlen(S+1);
St[n+1]='#';
for(int i=1;i<=n;++i) {
Sr[i]=S[n-i+1];
St[i]=St[2*n+2-i]=S[n-i+1];
}
Lyndon(Sr,n,xi,len,m);
Exkmp(St,n*2+1,nxt);
while(k>2&&m) {
if(len[m]==1) while(m&&len[m]==1) ans.add0(Sr+xi[m],xi[m+1]-xi[m]),m--;
else ans.add0(Sr+xi[m],xi[m+1]-xi[m]),m--;
k--;
}
if(k==1||m<=1) ans.add2(Sr+1,xi[m+1]-1);
else {
ans2=ans,ans3=ans;
ans2.add0(Sr+xi[m],xi[m+1]-xi[m]),ans2.add2(Sr+1,xi[m]-1);
ans.add0(Sr+xi[m-1],xi[m+1]-xi[m-1]),ans.add2(Sr+1,xi[m-1]-1);
if(ans2<ans) ans=ans2;
int p=n-xi[m+1]+2,q=p;
for(int i=p+1;i<=n;++i) {
char c1,c2;
if(p+nxt[p+n+1]<i) c1=S[p+nxt[p+n+1]],c2=Sr[nxt[p+n+1]+1];
else {
int h=min(nxt[i-p+1],n-i);
c1=Sr[1+h],c2=Sr[(i-p+1)+h];
}
if(c1<c2) p=i;
}
ans3.add0(S+n-xi[m+1]+2,p-(n-xi[m+1]+2));
ans3.add1(S+p,n-p+1);
if(ans3<ans) ans=ans3;
}
ans.print();
return 0;
}
B - AGC034F RNG And XOR
题解
有一个长度为 2 N 2^N 2N的序列 A 0 , A 1 , A 2 ⋯ A 2 N − 1 A_0,A_1,A_2\cdots A_{2^N-1} A0,A1,A2⋯A2N−1。设 p i = A i ∑ j = 0 2 N − 1 A j p_i = {A_i \over \sum_{j=0}^{2^N-1} A_j} pi=∑j=02N−1AjAi。有一个初始为 0 0 0的变量 X X X,每一次操作将以 p i p_i pi的概率将 X X X异或上 i i i。问对于所有的 i ∈ [ 0 , 2 N ) i\in [0,2^N) i∈[0,2N),期望进行几次操作后 X X X将变成 i i i。 N ≤ 18 N\le 18 N≤18,所有运算在对 998244353 998244353 998244353取模的意义下进行。
Sol
设 n = 2 N n=2^N n=2N。
设 i i i的答案是 x i x_i xi,那么我们有:
( x 0 , x 1 ⋯ x n − 1 ) ⨁ ( p 0 , p 1 , ⋯ p n − 1 ) = ( ? , x 1 − 1 , x 2 − 1 , x 3 − 1 ⋯ x n − 1 − 1 ) (x_0,x_1\cdots x_{n-1}) \bigoplus (p_0,p_1,\cdots p_{n-1}) = (?,x_1-1,x_2-1,x_3-1\cdots x_{n-1}-1) (x0,x1⋯xn−1)⨁(p0,p1,⋯pn−1)=(?,x1−1,x2−1,x3−1⋯xn−1−1)
其中 ⨁ \bigoplus ⨁表示异或卷积。
由于 ∑ p i = 1 \sum p_i = 1 ∑pi=1,所以右边得到的所有东西的和等于左边所有东西的和。并且 x 0 = 0 x_0=0 x0=0。故而:
( x 0 , x 1 ⋯ x n − 1 ) ⨁ ( p 0 , p 1 , ⋯ p n − 1 ) = ( n − 1 , x 1 − 1 , x 2 − 1 , x 3 − 1 ⋯ x n − 1 − 1 ) (x_0,x_1\cdots x_{n-1}) \bigoplus (p_0,p_1,\cdots p_{n-1}) = (n-1,x_1-1,x_2-1,x_3-1\cdots x_{n-1}-1) (x0,x1⋯xn−1)⨁(p0,p1,⋯pn−1)=(n−1,x1−1,x2−1,x3−1⋯xn−1−1)
考虑把右边式子中的 x i x_i xi去掉。对于左边, x i x_i xi对右边第 i i i项的贡献是 p 0 ⋅ x i p_0\cdot x_i p0⋅xi,那么把 p 0 − = 1 p_0-=1 p0−=1就可以得到:
( x 0 , x 1 ⋯ x n − 1 ) ⨁ ( p 0 − 1 , p 1 , p 2 , ⋯ p n − 1 ) = ( n − 1 , − 1 , − 1 , ⋯ − 1 ) (x_0,x_1\cdots x_{n-1})\bigoplus (p_0-1,p_1,p_2,\cdots p_{n-1}) = (n-1,-1,-1,\cdots -1) (x0,x1⋯xn−1)⨁(p0−1,p1,p2,⋯pn−1)=(n−1,−1,−1,⋯−1)
如果 p p p的FWT的结果中不存在 0 0 0,那么直接用右边的式子的FWT点值除以 p p p的点值,就可以得到 x x x的点值,再IFWT回去就可以了。
观察发现 t f ( p ) 0 = 0 tf(p)_0 = 0 tf(p)0=0,其他点值都不为 0 0 0。这是因为所有 p i p_i pi的和等于 1 1 1,而 t f ( p ) i tf(p)_i tf(p)i中 p 0 − 1 p_0 - 1 p0−1这一项的系数一定是 1 1 1,所以 t f ( p ) i tf(p)_i tf(p)i一定小于等于 0 0 0。
设 y = t f ( x ) 0 y=tf(x)_0 y=tf(x)0。IFWT的时候,这一项对每一个 x i x_i xi的贡献都是 y n y\over n ny。可以先把 t f ( x ) 0 tf(x)_0 tf(x)0当做 0 0 0来做IFWT,然后观察 x 0 x_0 x0看多加了多少,把每一项都减去这个多加的就可以了。
Code
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=(1<<18)+10,mod=998244353;
int Pow(int x,int y) {
int res=1;
while(y) {
if(y&1) res=res*(ll)x%mod;
x=x*(ll)x%mod,y>>=1;
}
return res;
}
int A[N],B[N],C[N];
int m,n;
void FWT(int *A,int f) {
for(int l=1;l<n;l<<=1)
for(int p=l<<1,i=0;i<n;i+=p)
for(int j=0;j<l;++j) {
int t1=A[i+j],t2=A[i+l+j];
A[i+j]=(t1+t2)%mod;
A[i+l+j]=(t1-t2+mod)%mod;
}
if(f==1) {
int inv=Pow(n,mod-2);
for(int i=0;i<n;++i) A[i]=A[i]*(ll)inv%mod;
}
}
int main() {
rd(m); n=(1<<m);
int sum=0;
for(int i=0;i<n;++i)
rd(A[i]),sum+=A[i];
sum=Pow(sum,mod-2);
for(int i=0;i<n;++i)
A[i]=A[i]*(ll)sum%mod;
A[0]=(A[0]-1+mod)%mod;
FWT(A,0);
// for(int i=0;i<n;++i) printf("%d ",A[i]); puts("");
B[0]=n-1;
for(int i=1;i<n;++i)
B[i]=mod-1;
FWT(B,0);
C[0]=0;
for(int i=1;i<n;++i)
C[i]=B[i]*(ll)Pow(A[i],mod-2)%mod;
FWT(C,1);
int tmp=C[0];
for(int i=0;i<n;++i) C[i]=(C[i]-tmp+mod)%mod;
for(int i=0;i<n;++i) printf("%d\n",C[i]);
return 0;
}
C - AGC030D Inversion Sum
题意
有一个长度为 n n n的序列 { A i } \{ A_i \} {Ai}以及一个长度为 q q q的操作序列。操作序列中的每个操作形如 ( x i , y i ) (x_i,y_i) (xi,yi),表示交换 A x i A_{x_i} Axi和 A y i A_{y_i} Ayi。每个操作可以选择进行或者不进行。问这 2 q 2^q 2q种方式得到的序列的逆序对个数的和。 n , q ≤ 3000 , A i ≤ 1 0 9 n,q\le 3000,A_i \le 10^9 n,q≤3000,Ai≤109,答案对 1 0 9 + 7 10^9+7 109+7取模。
Sol
设 f [ t ] [ i ] [ j ] f[t][i][j] f[t][i][j]表示进行完前 t t t个操作之后 A i A_i Ai比 A j A_j Aj大的方案数。则
- 第
t
+
1
t+1
t+1个操作进行:
f [ t + 1 ] [ x t ] [ j ] + = f [ t ] [ y t ] [ j ] f [ t + 1 ] [ i ] [ x t ] + = f [ t ] [ i ] [ y t ] f [ t + 1 ] [ y t ] [ j ] + = f [ t ] [ x t ] [ j ] f [ t + 1 ] [ i ] [ y t ] + = f [ t ] [ i ] [ x t ] f [ t + 1 ] [ x t ] [ y t ] + = f [ t ] [ y t ] [ x t ] f [ t + 1 ] [ y t ] [ x t ] + = f [ t ] [ x t ] [ y t ] f [ t + 1 ] [ i ] [ j ] + = f [ t ] [ i ] [ j ] ( i ≠ x t , i ≠ y t , j ≠ x t , j ≠ y t ) f[t+1][x_t][j] += f[t][y_t][j]\\ f[t+1][i][x_t] += f[t][i][y_t]\\ f[t+1][y_t][j] += f[t][x_t][j]\\ f[t+1][i][y_t] += f[t][i][x_t]\\ f[t+1][x_t][y_t] += f[t][y_t][x_t]\\ f[t+1][y_t][x_t] += f[t][x_t][y_t]\\ f[t+1][i][j]+=f[t][i][j] (i \not = x_t, i\not = y_t,j\not = x_t,j\not = y_t) f[t+1][xt][j]+=f[t][yt][j]f[t+1][i][xt]+=f[t][i][yt]f[t+1][yt][j]+=f[t][xt][j]f[t+1][i][yt]+=f[t][i][xt]f[t+1][xt][yt]+=f[t][yt][xt]f[t+1][yt][xt]+=f[t][xt][yt]f[t+1][i][j]+=f[t][i][j](i=xt,i=yt,j=xt,j=yt) - 第 t t t个操作不进行: f [ t + 1 ] [ i ] [ j ] + = f [ t ] [ i ] [ j ] f[t+1][i][j]+=f[t][i][j] f[t+1][i][j]+=f[t][i][j]
这样转移是 O ( n 2 ) O(n^2) O(n2)的。
考虑将状态改成前 t t t个操作之后 A i A_i Ai比 A j A_j Aj大的概率,那么 f [ t + 1 ] f[t+1] f[t+1]和 f [ t ] f[t] f[t]相比就将这有 O ( n ) O(n) O(n)个元素会被改变。最后的 d p dp dp中的数值乘以 2 q 2^q 2q就是我们要求的方案数。
Code
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
x=0; char c=getchar(); int f=1;
while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=3010,mod=1e9+7;
const int inv2=(mod+1)/2;
int f[N][N],n;
int A[N],xi[N],m;
int main() {
int q,x,y; rd(n),rd(q);
for(int i=1;i<=n;++i) rd(A[i]),xi[i]=A[i];
sort(xi+1,xi+n+1);
m=unique(xi+1,xi+n+1)-xi-1;
for(int i=1;i<=n;++i) A[i]=lower_bound(xi+1,xi+m+1,A[i])-xi;
for(int i=1;i<=n;++i)
for(int j=1;j<=n;++j) if(i!=j)
f[i][j]=A[i]>A[j];
int ax=1;
while(q--) {
ax=ax*2ll%mod;
int x,y; rd(x),rd(y);
for(int i=1;i<=n;++i) if(i!=x&&i!=y) {
int tmp1=(f[i][x]+f[i][y])*(ll)inv2%mod;
f[i][x]=f[i][y]=tmp1;
int tmp2=(f[x][i]+f[y][i])*(ll)inv2%mod;
f[x][i]=f[y][i]=tmp2;
}
int tmp=(f[x][y]+f[y][x])*(ll)inv2%mod;
f[x][y]=f[y][x]=tmp;
}
int ans=0;
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j)
ans=(ans+f[i][j])%mod;
printf("%d",ans*(ll)ax%mod);
return 0;
}