C. Tyler and Strings(组合数学,树状数组维护前缀和)(Codeforces Round #775 (Div. 1, based on Moscow Open Olympiad i)

对我来说比较困难的一题了,尝试着自己写了一下,调不出来遂放弃.
Codeforces Round #775 (Div. 1, based on Moscow Open Olympiad in Informatics)
https://codeforces.com/contest/1648/problem/C
C.Tyler and Strings
题意:给你字符串 s , t s,t s,t,你可以对 s s s任意排序,问你有多少种方案使得 s s s重排后字典序会比 t t t小.
思路:枚举每次在哪里断掉.比如说在 i i i的位置,令 s j < t j s_j<t_j sj<tj,那么合法的方案就是任意排序了.
比如说 s = 1 , 2 , 3 , 4 , t = 4 , 3 , 2 , 1 s=1,2,3,4,t=4,3,2,1 s=1,2,3,4,t=4,3,2,1,第一个位置选择1,剩下的 2 , 3 , 4 2,3,4 2,3,4任意排列都是合法方案.
这种情况下就是 A ( 3 , 3 ) = 6 A(3,3)=6 A(3,3)=6.
可以预见,后面 对 于 t 来 说 , 后 续 元 素 [ i + 1 , n ] 对于t来说,后续元素[i+1,n] t,[i+1,n]包含重复元素的全排列就是当前令 s i < t i si<ti si<ti的合法方案.
为什么是重复元素,因为你一个数字后面可能出现多次.
因为我们还可以选择不同的字符在当前位置 j j j,令 s i < t i si<ti si<ti.令所有的 字 符 i , s i < t i 字符i,s_i<t_i i,si<ti组成一个集合.我们每次都要统计这个集合对答案的贡献.(枚举到 t i t_i ti)的时候.
包含重复元素的全排列数目的计算公式是: n ! c n t 1 ! ∗ c n t 2 ! ∗ . . . c n t k ! {n!}\over{cnt_1!*cnt_2!*...cnt_k!} cnt1!cnt2!...cntk!n!
其中 n n n为总数目,k有多少种类, c n t k cnt_k cntk为不同种类的数目.
那么每一个 s i < t i s_i<t_i si<ti对应的贡献记为 a d d i add_i addi(注意这里的n是指题目来说,实际上是 n − i − 1 n-i-1 ni1)
a d d i = ( n − i ) ! c n t 1 ! ∗ c n t 2 ! ∗ . . . . ∗ ( c n t i − 1 ) ! ∗ . . ∗ c n t k ! {add_i}= {(n-i)!\over{cnt_1!*cnt_2!*....*(cnt_i-1)!*..*cnt_k!}} addi=cnt1!cnt2!....(cnti1)!..cntk!(ni)!
显然,每次统计答案的时候,我们还需要求add数组的前缀和.而且add数组也会因为 i i i的推进不断更改值,因为推进的时候,你是让 s i − 1 = = t i − 1 s_{i-1}==t_{i-1} si1==ti1,保证了前i-1位是一个前缀,才能考虑第i位.
我们需要一种数据结构,支持单点改值,区间求和,使用树状数组/线段树.
但这并没有完结.不难发现,每次在i位使得 s i = = t i s_i==t_i si==ti后,对应的 c n t s i 会 减 少 1 cnt_{si}会减少1 cntsi1,造成整个 a d d 数 组 都 会 发 生 变 化 add数组都会发生变化 add.如果每次都要去更改一遍,无论是哪种数据结构都是难以接受的.
我们考虑写出每种元素 i i i,对应 a d d i 发 生 的 变 化 , 寻 求 共 同 改 变 的 地 方 以 降 低 复 杂 度 add_i发生的变化,寻求共同改变的地方以降低复杂度 addi,
对于不被删去的元素我们称为 j j j.
删去元素 i i i
a d d j = ( n − i − 1 ) ! c n t 1 ! ∗ c n t 2 ! ∗ . . . . ∗ ( c n t i − 1 ) ! ∗ . . ∗ c n t k ! add_j={(n-i-1)!\over{cnt_1!*cnt_2!*....*(cnt_i-1)!*..*cnt_k!}} addj=cnt1!cnt2!....(cnti1)!..cntk!(ni1)!
a d d i = ( n − i − 1 ) ! c n t 1 ! ∗ c n t 2 ! ∗ . . . . ∗ ( c n t i − 2 ) ! ∗ . . ∗ c n t k ! add_i={(n-i-1)!\over{cnt_1!*cnt_2!*....*(cnt_i-2)!*..*cnt_k!}} addi=cnt1!cnt2!....(cnti2)!..cntk!(ni1)!
等等,这不是几乎一模一样吗,只要我们在一开始给 a d d i add_i addi数组赋值的时候,给自己赋值为 ( c n t i − 1 ) ! 不 就 好 了 (cnt_i-1)!不就好了 (cnti1)!
那么每次修改数组,就相当于 a d d 数 组 整 体 乘 上 一 个 变 量 , 称 为 v a r add数组整体乘上一个变量,称为var add,var.
v a r = c n t i ( n − i + 1 ) var={cnt_i\over(n-i+1)} var=(ni+1)cnti其中分子代表把 a d d i 分 母 上 的 ( c n t i ) ! 变 为 ( c n t i − 1 ) ! add_i分母上的(cnt_i)!变为(cnt_i-1)! addicnti!(cnti1)!,分母代表 把 ( n − i ) ! 变 为 ( n − i − 1 ) ! 把(n-i)!变为(n-i-1)! (ni)!(ni1)!.
接下来,统计答案就是挨个扫描 t i t_i ti,令 x = t i x=t_i x=ti,查询 [ 1 , x − 1 ] 的 a d d 前 缀 和 , 加 到 a n s 上 即 可 [1,x-1]的add前缀和,加到ans上即可 [1,x1]add,ans.
题解的思想到这结束,然而我们会发现这些写很容易写错…我至少发现是这样的.
我们考虑继续优化 a d d add add数组.我们发现对于区间 [ 1 , x − 1 ] [1,x-1] [1,x1]写出他们 a d d add add的求和形式.
s u m 1 i = ( n − i ) ! ( c n t 1 − 1 ) ! ∗ c n t 2 ! ∗ . . . . ! ∗ . . ∗ c n t k ! + ( n − i ) ! ( c n t 1 ) ! ∗ ( c n t 2 − 1 ) ! ∗ . . . . ∗ . . ∗ c n t k ! + ( n − i ) ! ( c n t 1 ) ! ∗ ( c n t 2 ) ! ∗ . . . . ∗ ( c n t x − 1 − 1 ) ! ∗ . . ∗ c n t k ! sum1i={(n-i)!\over{(cnt_1-1)!*cnt_2!*....!*..*cnt_k!}}+{(n-i)!\over{(cnt_1)!*(cnt_2-1)!*....*..*cnt_k!}}+{(n-i)!\over{(cnt_1)!*(cnt_2)!*....*(cnt_{x-1}-1)!*..*cnt_k!}} sum1i=(cnt11)!cnt2!....!..cntk!(ni)!+(cnt1)!(cnt21)!......cntk!(ni)!+(cnt1)!(cnt2)!....(cntx11)!..cntk!(ni)!
我们再观察以下形式.
s u m 2 i = ( n − i ) ! ( c n t 1 ) ! ∗ c n t 2 ! ∗ . . . . ! ∗ . . ∗ c n t k ! + ( n − i ) ! ( c n t 1 ) ! ∗ c n t 2 ! ∗ . . . . ! ∗ . . ∗ c n t k ! + ( n − i ) ! ( c n t 1 ) ! ∗ c n t 2 ! ∗ . . . . ! ∗ . . ∗ c n t k ! sum2i={(n-i)!\over{(cnt_1)!*cnt_2!*....!*..*cnt_k!}}+{(n-i)!\over{(cnt_1)!*cnt_2!*....!*..*cnt_k!}}+{(n-i)!\over{(cnt_1)!*cnt_2!*....!*..*cnt_k!}} sum2i=(cnt1)!cnt2!....!..cntk!(ni)!+(cnt1)!cnt2!....!..cntk!(ni)!+(cnt1)!cnt2!....!..cntk!(ni)!
对于第一项需要乘以 c n t 1 , 第 二 项 需 要 c n t 2 . . . 第 x − 1 项 需 要 乘 以 c n t x cnt_1,第二项需要cnt_2...第x-1项需要乘以cnt_x cnt1,cnt2...x1cntx
那么提取公因式(实际上 s u m 2 i 每 一 项 都 一 样 sum2i每一项都一样 sum2i)
s u m 2 i ∗ ∑ j = 1 x − 1 c n t j = s u m 1 i sum2i*\sum_{j=1}^{x-1}cnt_j=sum1i sum2ij=1x1cntj=sum1i
也就是说,我们不再需要维护 a d d i add_i addi数组,而是去维护 c n t i cnt_i cnti数组即可
题解好像是去维护的 a d d add add,我搞半天搞不出来…

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5+5;
const int INF = 1e9+7;
const ll mod =  998244353;
typedef pair<int,int> pii;
ll f_pow(ll a,ll b){
	ll ans = 1;
	while(b){
		if(b&1) ans = (ans*a)%mod;
		a=(a*a)%mod;b>>=1;
	}
	return ans;
}
ll inv_fac[maxn];
ll inv[maxn];
ll fac[maxn];
void inv_table(ll n){
	inv_fac[0]=1;
	inv[1]=1;fac[1]=1;
	for(int i=2;i<=n;i++){
		inv[i] = (mod-mod/i)*inv[mod%i]%mod;
		fac[i] = (fac[i-1]*i)%mod;
	}
	inv_fac[n] = f_pow(fac[n],mod-2);
	for(ll i=n-1;i>=1;i--){
		inv_fac[i] = (inv_fac[i+1] *(i+1)) %mod;
	}
}
ll tree[maxn];
void add(int x,int val){
	while(x<=maxn-5){
		tree[x]=(tree[x]+val)%mod;
		x+=(x&-x);
	}
}
ll query(int x){
	ll ans = 0;
	while(x){
		ans +=tree[x];
		x-=(x&-x);
	}
	return ans;
}
int cnt[maxn];
int main(){
//    freopen("1.txt","r",stdin);
    ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	inv_table(200000);
	int n,m;cin>>n>>m;
	for(int i=1;i<=n;i++){
		int x;cin>>x;
		cnt[x]++;
		add(x,1);
	}
	ll var =fac[n];
	for(int i=1;i<=maxn-5;i++){
		var = var*inv_fac[cnt[i]]%mod;
	}
	ll ans = 0;
	for(int i=1;i<=m;i++){
		int x;cin>>x;
		ll sum = query(x-1);
		ans=(inv[n-i+1]*sum%mod*var%mod+ans)%mod;
		if(cnt[x]==0) break;
		var = var*cnt[x]%mod*inv[n-i+1]%mod;
		cnt[x]--;
		add(x,-1);
		if(i==n&&i<m){
			ans=(ans+1)%mod;
			break;
			//s用完了,t没用完,且s是t的前缀.
		}
	}
	cout<<ans<<"\n";
}
  • 15
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

minato_yukina

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值