IOI2020集训队作业-2 (CF594E, AGC034F, AGC030D)

A - CF594E Cutting the Line

题意

有一个字符串 S S S。给出 k k k,你可以将字符串划分成不超过 k k k段,将每一段翻转或者不翻转,然后按照原来的顺序拼接起来。问最后能够得到的字典序最小的字符串。 ∣ S ∣ ≤ 5000000 |S|\le 5000000 S5000000

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} t1sa1t2sa2tpsap,则我们一定能够让最终的字符串的开头有 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+1tp。并且这也是我们能够在开头放的 s s s最多的操作方法。此时前两段的划分和是否翻转都已经确定,我们继续去确定剩下的字符串允许划分为至多 k − 2 k-2 k2段能够得到的字典序最小的就可以了。

进一步挖掘,发现这个过程本质上就是:将 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 k2

如果 k = 1 k=1 k=1,直接讨论翻转和不翻转两种情况即可。

如果 k = 2 k=2 k=2,则讨论划分成两段之后是否翻转第一段这两种情况。

第一段不翻转:则我们枚举在哪个位置断开、将后缀翻转,然后取最优的。两个断开位置的方案的比较可以在用 Z − a l g o r i t h m Z-algorithm Zalgorithm预处理之后做到 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,如下图。

1-2.PNG

由于 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,A2A2N1。设 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=02N1AjAi。有一个初始为 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 N18,所有运算在对 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,x1xn1)(p0,p1,pn1)=(?,x11,x21,x31xn11)

其中 ⨁ \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,x1xn1)(p0,p1,pn1)=(n1,x11,x21,x31xn11)

考虑把右边式子中的 x i x_i xi去掉。对于左边, x i x_i xi对右边第 i i i项的贡献是 p 0 ⋅ x i p_0\cdot x_i p0xi,那么把 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,x1xn1)(p01,p1,p2,pn1)=(n1,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 p01这一项的系数一定是 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,q3000,Ai109,答案对 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;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值