模拟赛20200211「LibreOJ NOI Round #2 Day 1」

「LibreOJ NOI Round #2 Day 1」

T1:

大意:支持尾部插入一个正整数 a i a_i ai,询问求 f ( a l , a l + 1 , . . . , a r ) f(a_l,a_{l+1},...,a_r) f(al,al+1,...,ar),其中 f ( x ) = x , f ( a 0 , a 1 . . . , a n ) = a 0 + 1 f ( a 1 , a 2 , . . . , a n ) ( n ≥ 1 ) f(x)=x,f(a_0,a_1...,a_n)=a_0+\frac 1{f(a_1,a_2,...,a_n)}(n\ge1) f(x)=x,f(a0,a1...,an)=a0+f(a1,a2,...,an)1(n1),输出解的最简分数形式 x y \frac xy yx x , y m o d    998244353 x,y \mod 998244353 x,ymod998244353后的值。

题解:

在这里插入图片描述
由上可知我们可以直接维护模之后的分子分母,用一个 2 × 2 2\times 2 2×2矩阵表示转移即可。线段树维护尾端插入可过。此题矩阵是有逆的,维护前缀积即可做到 O ( n + m ) O(n+m) O(n+m),注意矩乘不一定满足交换律,但满足结合律,所以要维护从左到右乘的前缀积和从右到左乘的前缀逆, A n s [ l , r ] = M l − 1 − 1 ∗ M r Ans[l,r]=M_{l-1}^{-1}*M_r Ans[l,r]=Ml11Mr
Code(线段树):

#include<bits/stdc++.h>
#define maxn 1000005
using namespace std;
char cb[1<<18],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<18,stdin),cs==ct)?0:*cs++)
inline void read(int &a){
	char c;while(!isdigit(c=getc()));
	for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
const int mod = 998244353;
int n,m,N,typ,a[maxn],id[maxn],ansx,ansy;
struct Mat{
	int s[2][2];
	Mat(){memset(s,0,sizeof s);}
	void init(int x){s[0][0]=0,s[0][1]=s[1][0]=1,s[1][1]=x;}
	Mat operator * (const Mat &B)const{
		Mat ret;
		for(int k=0;k<2;k++) for(int i=0;i<2;i++) for(int j=0;j<2;j++)
			ret.s[i][j]=(ret.s[i][j]+1ll*s[i][k]*B.s[k][j])%mod;
		return ret;
	}
}t[maxn<<2],ans;
void build(int i,int l,int r){
	if(l==r) {id[l]=i,t[i].init(a[l]);return;}
	int mid=(l+r)>>1;
	build(i<<1,l,mid),build(i<<1|1,mid+1,r);
	t[i]=t[i<<1|1]*t[i<<1];//!!!
}
void query(int i,int l,int r,int x,int y){
	if(x<=l&&r<=y) {ans=ans*t[i];return;}
	int mid=(l+r)>>1;
	if(y>mid) query(i<<1|1,mid+1,r,x,y);
	if(x<=mid) query(i<<1,l,mid,x,y);
}
int main()
{
	freopen("alone.in","r",stdin);
	freopen("alone.out","w",stdout);
	read(n),read(m),read(typ);
	for(int i=1;i<=n;i++) read(a[i]);
	build(1,1,N=n+m);
	int op,x,y,i;
	while(m--){
		read(op);
		if(op==1){
			read(x),typ&&(x^=ansx^ansy);
			t[i=id[++n]].init(x);
			while(i>1&&(i&1)) i>>=1,t[i]=t[i<<1|1]*t[i<<1];
		}
		else{
			read(x),read(y),typ&&(x^=ansx^ansy,y^=ansx^ansy);
			ans.s[0][0]=ans.s[1][0]=ans.s[1][1]=0,ans.s[0][1]=1,query(1,1,N,x,y);
			printf("%d %d\n",ansx=ans.s[0][1],ansy=ans.s[0][0]);
		}
	}
}

T2:

神仙模拟费用流,还没写完。


T3:

在这里插入图片描述
40 % , n ≤ 2000 40\%,n\le2000 40%,n2000
100 % , n ≤ 1 0 5 100\%,n\le10^5 100%,n105

题解:

n ≤ 2000 n\le2000 n2000时可以设 f [ i ] [ j ] f[i][j] f[i][j]表示前 i i i个数最后一个排名为 j j j的合法方案数,转移时枚举上一个的排名,前缀和优化一下就是 O ( n 2 ) O(n^2) O(n2)
然而这个做法并没有什么优化空间。
正解是容斥,但是钦定序列中间某个位置不满足条件的方案数并不好求,需要另类容斥。
发现DP做法的难点在于合并两个串时不好确定端点处的排名,如果通过容斥能够使得这个位置任意,那么就好做了。
先忽略所有的 < < <,把所有的 > > >看做限制 1 1 1
那么
111...111 = 111...11 ?   −   111...110 = 111...11 ?   −   ( 111...1 ? 0   − 111...100 ) = 111...11 ?   −   ( 111...1 ? 0   − ( 111... ? 00   −   111...000 ) ) = 111...11 ?   − ( 111... ? 0   − ( 111... ? 00   −   ( 11... ? 000   −   ( . . . − 000...000 ) ) ) ) 111...111=111...11? ~-~111...110\\ =111...11?~-~(111...1?0~-111...100)\\ =111...11?~-~(111...1?0~-(111...?00~-~111...000))\\ =111...11?~-(111...?0~-(111...?00~-~(11...?000~-~(...-000...000)))) 111...111=111...11?  111...110=111...11?  (111...1?0 111...100)=111...11?  (111...1?0 (111...?00  111...000))=111...11? (111...?0 (111...?00  (11...?000  (...000...000))))
这样就将后半部分变成了全小于,相接处任意,这样只需要选出一部分数放后面即可。记 c n t [ i ] cnt[i] cnt[i]表示前 i i i个字符有多少 > > > f [ i ] f[i] f[i]表示前 i i i个数字的方案数,有(设 s s s下标从1开始,钦定 s [ 0 ] s[0] s[0] > > >):
f [ i ] = ∑ j = 0 i − 1 [ s j = = ′ > ′ ] ( − 1 ) c n t [ i − 1 ] − c n t [ j ] ∗ ( i i − j ) ∗ f [ j ] f[i]=\sum_{j=0}^{i-1}[s_j=='>'](-1)^{cnt[i-1]-cnt[j]}*\binom i{i-j}*f[j] f[i]=j=0i1[sj==>](1)cnt[i1]cnt[j](iji)f[j]
将组合数拆开, f [ i ] f[i] f[i]变为 f [ i ] i ! \frac {f[i]}{i!} i!f[i],分治NTT即可。

Code:

#include<bits/stdc++.h>
#define maxn 300005
using namespace std;
const int mod = 998244353, G = 3;
int n,f[maxn],a[maxn],b[maxn],cnt[maxn],fac[maxn],inv[maxn];
char s[maxn];
int w[maxn],r[maxn],wlen;
inline int Pow(int a,int b){
	int s=1; for(;b;b>>=1,a=1ll*a*a%mod) if(b&1) s=1ll*s*a%mod;
	return s;
}
void init(int n){
	wlen=w[0]=1;while(wlen<=n) wlen<<=1;
	for(int i=1,g=Pow(G,(mod-1)/wlen);i<=wlen;i++) w[i]=1ll*w[i-1]*g%mod;
	fac[0]=fac[1]=inv[0]=inv[1]=1;
	for(int i=2;i<=wlen;i++) fac[i]=1ll*fac[i-1]*i%mod,inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(int i=2;i<=wlen;i++) inv[i]=1ll*inv[i]*inv[i-1]%mod;
}
void NTT(int *a,int len,int flg){
	for(int i=0;i<len;i++) if(i<(r[i]=r[i>>1]>>1|(i&1?len>>1:0))) swap(a[i],a[r[i]]);
	for(int i=2;i<=len;i<<=1)
		for(int j=0,t=wlen/i;j<len;j+=i)
			for(int k=j,o=0;k<j+i/2;k++,o+=t){
				int u=a[k],v=1ll*w[flg==1?o:wlen-o]*a[k+i/2]%mod;
				a[k]=(u+v)%mod,a[k+i/2]=(u-v)%mod;
			}
	if(flg==-1) for(int i=0,Inv=1ll*inv[len]*fac[len-1]%mod;i<len;i++) a[i]=1ll*a[i]*Inv%mod;
}
void solve(int l,int r){
	if(l==r) {if(l) f[l]=1ll*f[l]*(cnt[l-1]&1?-1:1)%mod;return;}
	int mid=(l+r)>>1;
	solve(l,mid);
	int len=1;while(len<=r-l+mid-l) len<<=1;
	fill(a,a+len,0),fill(b,b+len,0);
	for(int i=0;i<=r-l;i++) a[i]=inv[i];
	for(int i=l;i<=mid;i++) if(s[i]=='>') b[i-l]=1ll*f[i]*(cnt[i]&1?-1:1)%mod;
	NTT(a,len,1),NTT(b,len,1);
	for(int i=0;i<len;i++) a[i]=1ll*a[i]*b[i]%mod;
	NTT(a,len,-1);
	for(int i=mid+1;i<=r;i++) f[i]=(f[i]+a[i-l])%mod;
	solve(mid+1,r);
}
int main()
{
	//freopen("unequal.in","r",stdin);
	//freopen("unequal.out","w",stdout);
	scanf("%s",s+1),n=strlen(s+1)+1;
	for(int i=1;i<=n;i++) cnt[i]=cnt[i-1]+(s[i]=='>');
	init(n<<1);
	f[0]=1,s[0]='>',solve(0,n);
	printf("%d\n",(1ll*f[n]*fac[n]%mod+mod)%mod);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值