BJOI2019 题解

BJOI2019 题解


在更了在更了

P5319 [BJOI2019]奥术神杖

\(V_i\)求个\(\ln\)变成了让平均数最大,显然套分数规划,然后ac自动机上面dp

#include<bits/stdc++.h>
#define il inline
#define vd void
typedef long long ll;
il ll gi(){
    ll x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    return x*f;
}
char T[1510],S[1510];
int ch[1510][10],trans[1510][10],fail[1510],cnt;
double W[1510];int sum[1510];
il vd insert(double d){
    int n=strlen(S+1);
    int x=0;
    for(int i=1;i<=n;++i){
        S[i]-='0';
        if(!ch[x][S[i]])ch[x][S[i]]=++cnt;
        x=ch[x][S[i]];
    }
    W[x]+=d;++sum[x];
}
int que[1510],hd,tl;
double f[1510][1510];
int g[1510][1510];
char h[1510][1510];
template<class T> il vd chkmx(T&a,T b){if(b>a)a=b;}
int main(){
#ifdef XZZSB
    freopen("in.in","r",stdin);
    freopen("out.out","w",stdout);
#endif
    int n=gi(),m=gi();
    scanf("%s",T+1);
    for(int i=1;i<=m;++i)scanf("%s",S+1),insert(log(gi()));
    for(int i=0;i<10;++i)if(ch[0][i])trans[0][i]=ch[0][i],que[tl++]=ch[0][i];
    while(hd^tl){
        int x=que[hd++];
        for(int i=0;i<10;++i)
            if(ch[x][i]){
                int f=fail[x];
                while(f&&!ch[f][i])f=fail[f];
                fail[ch[x][i]]=ch[f][i];
                que[tl++]=ch[x][i];
                W[ch[x][i]]+=W[ch[f][i]];
                sum[ch[x][i]]+=sum[ch[f][i]];
                trans[x][i]=ch[x][i];
            }else trans[x][i]=trans[fail[x]][i];
    }
    double l=0,r=1e9,mid;
    while(r-l>1e-4){
        mid=(l+r)*0.5;
        for(int i=0;i<=cnt;++i)f[0][i]=-1e18;
        f[0][0]=0;
        for(int i=0;i<n;++i){
            for(int j=0;j<=cnt;++j)f[i+1][j]=-1e18;
            for(int j=0;j<=cnt;++j){
                if(f[i][j]<-1e17)continue;
                for(int k=0;k<10;++k)if(T[i+1]=='.'||k+'0'==T[i+1])chkmx(f[i+1][trans[j][k]],f[i][j]+W[trans[j][k]]-mid*sum[trans[j][k]]);
            }
        }
        bool flg=0;
        for(int i=0;i<=cnt;++i)if(f[n][i]>1e-7)flg=1;
        if(flg)l=mid;
        else r=mid;
    }
    for(int i=0;i<=cnt;++i)f[0][i]=-1e18;
    f[0][0]=0;
    for(int i=0;i<n;++i){
        for(int j=0;j<=cnt;++j)f[i+1][j]=-1e18;
        for(int j=0;j<=cnt;++j){
            if(f[i][j]<-1e17)continue;
            for(int k=0;k<10;++k)
                if(T[i+1]=='.'||k+'0'==T[i+1])
                if(f[i+1][trans[j][k]]<f[i][j]+W[trans[j][k]]-l*sum[trans[j][k]]){
                    f[i+1][trans[j][k]]=f[i][j]+W[trans[j][k]]-l*sum[trans[j][k]];
                    g[i+1][trans[j][k]]=j;
                    h[i+1][trans[j][k]]='0'+k;
                }
        }
    }
    double F=1e-18;int G=0;
    for(int i=0;i<=cnt;++i)if(f[n][i]>F)F=f[n][i],G=i;
    for(int i=n;i;--i)T[i]=h[i][G],G=g[i][G];
    printf("%s",T+1);
    return 0;
}

P5320 [BJOI2019]勘破神机

神鸡???

这是一个强行二合一,但这两个题还是有关系的

先看\(ans2\) 化一下式子就可以知道答案和\(\sum_{i=0}^n\binom{fib_i}k\)有关。

再看看\(ans3\),显然\(n\)为奇数是没有答案,设\(f_n\)表示\(2n\)列时的答案。

考虑怎么放。首先有一种放法就是三个横条,从\(f_{n-1}\)转移过来,1种放法;还有一种方法就是

---------
| |   | |
- ----- -
| |   | |
---------
|   |   |
---------

这样可以放任意长度为偶数的段,而且上下翻转也是一种方案,所以从任意的\(f_{i}(i<n)\)可以转移过来,有2种方法。

综上,\(f_n=f_{n-1}+2\sum_{i=0}^{n-1}f_i\)

\(g_n=\sum_{i=0}^nf_n\),那么用上面的递推式改改可以得到另一个递推式\(g_n=4g_{n-1}-g_{n-2}\)

下面\(ans2\)\(ans3\)的方法是类似的。

\(\binom nk\)显然是个\(k\)次多项式,可以\(O(k^2)\)的时间预处理出来

那么答案变成了

\(\sum_{i=0}^n\sum_{j=0}^ka_jfib_i^j\)

\(\sum_{j=0}^ka_j\sum_{i=0}^nfib_i^j\)

那么问题变成了对每一个\(k\)计算\(\sum_{i=0}^nfib_i^k\)

考虑\(fib\)的通项公式,我们知道了是\(f(n)=\frac{(\frac{1+\sqrt5}2)^n-(\frac{1-\sqrt5}2)^n}{\sqrt5}\)

再看看\(ans3\)的通项公式,可以解出来\(f(n)=\frac{(3+\sqrt3)(2+\sqrt3)^n+(3-\sqrt3)(2-\sqrt3)^n}{6}\)

统一写成\(f(x)=ab^i+cd^i\)

\(\sum_{i=0}^n(ab^i+cd^i)^k\)

用二项式定理直接展开,\(\sum_{i=0}^n\sum_{j=0}^k\binom kj (ab^i)^j(cd^i)^{k-j}\)

\(\sum_{i=0}^n\sum_{j=0}^k\binom kj a^jb^{ij}c^{k-j}d^{i(k-j)}\)

\(\sum_{i=0}^nf(i)^k=\sum_{j=0}^k\binom kj a^jc^{k-j}\sum_{i=0}^n(b^{j}d^{k-j})^i\)

后面直接等比数列求和即可,那么这个式子就能算了。

但是需要注意, \(3\)\(5\)\(\mod 998244353\)意义下都没有二次剩余,可以开一个struct存\(a,b\),真实数就是\(a+b\sqrt 5\)

\(F_x\)通项的方法:不想写了,贴个链接https://blog.csdn.net/liuzibujian/article/details/82595918

#include<bits/stdc++.h>
#define il inline
#define vd void
#define mod 998244353
#define ll long long
il ll gi(){
	ll x=0,f=0;char ch=getchar();
	while(!isdigit(ch))f^=ch=='-',ch=getchar();
	while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
	return f?-x:x;
}
il int pow(int a,int b){
	int ret=1;
	while(b){
		if(b&1)ret=1ll*ret*a%mod;
		a=1ll*a*a%mod;b>>=1;
	}
	return ret;
}
ll k,l,r,qt,m;
struct number{
	int a,b;
	number inv()const{
		int fm=pow((1ll*a*a-qt*b*b%mod+mod)%mod,mod-2);
		return (number){1ll*a*fm%mod,(mod-1ll*b*fm%mod)%mod};
	}
};//a+b*sqrt(qt)
il number getnum(int x){return(number){x,0};}
il number operator+(const number&a,const number&b){return(number){(a.a+b.a)%mod,(a.b+b.b)%mod};}
il number operator-(const number&a,const number&b){return(number){(a.a-b.a+mod)%mod,(a.b-b.b+mod)%mod};}
il number operator*(const number&a,const number&b){return(number){(1ll*a.a*b.a+qt*a.b*b.b)%mod,(1ll*a.a*b.b+1ll*a.b*b.a)%mod};}
il number operator/(const number&a,const number&b){return a*b.inv();}
il number Pow(number a,ll b){
	number ret=getnum(1);
	while(b){
		if(b&1)ret=ret*a;
		a=a*a;b>>=1;
	}
	return ret;
}
int A[510],B[510],C[510][510];
number pa[510],pb[510],pc[510],pd[510];
il ll solve(ll n){
	ll ans=0;
	for(int i=0;i<=k;++i){
		ll res=0;
		for(int o=0;o<=i;++o){
			number _res=getnum(C[i][o]);
			number p=pb[o]*pd[i-o];
			if(p.a==1&&p.b==0)_res=_res*getnum(n%mod);
			else _res=_res*(Pow(p,n+1)-getnum(1))/(p-getnum(1));
			res=(res+(_res*pa[o]*pc[i-o]).a)%mod;
		}
		ans=(ans+res*A[i])%mod;
	}
	return ans;
}
il vd init(){
	if(m==2)qt=5;
	else qt=3;
	C[0][0]=1;
	for(int i=1;i<=501;++i){
		C[i][0]=1;
		for(int j=1;j<=i;++j)C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
	}
	pa[0]=pb[0]=pc[0]=pd[0]=getnum(1);
	if(m==2){
		pa[1]=(number){1,0}/(number){0,1};
		pb[1]=(number){1,1}/getnum(2);
		pc[1]=(number){mod-1,0}/(number){0,1};
		pd[1]=(number){1,mod-1}/getnum(2);
	}else{
		pa[1]=(number){3,1}/getnum(6);
		pb[1]=(number){2,1};
		pc[1]=(number){3,mod-1}/getnum(6);
		pd[1]=(number){2,mod-1};
	}
	for(int i=2;i<=501;++i)pa[i]=pa[i-1]*pa[1],pb[i]=pb[i-1]*pb[1],pc[i]=pc[i-1]*pc[1],pd[i]=pd[i-1]*pd[1];
}
il vd work(){
	memset(A,0,sizeof A);A[0]=1;
	for(int i=0;i<k;++i){
		memcpy(B,A,sizeof A);
		memset(A,0,sizeof A);
		for(int j=0;j<=k;++j)A[j]=mod-1ll*B[j]*i%mod;
		for(int j=0;j<=k;++j)A[j+1]=(A[j+1]+B[j])%mod;
	}
	ll _l,_r;
	if(m==3)_l=(l+1)/2-1,_r=r/2-1;
	else _l=l,_r=r;
	ll ans=(solve(_r+1)-solve(_l)+mod)%mod;
	for(int i=1;i<=k;++i)ans=ans*pow(i,mod-2)%mod;
	printf("%lld\n",ans*pow((r-l+1)%mod,mod-2)%mod);
}
int main(){
#ifdef XZZSB
    freopen("in.in","r",stdin);
    freopen("out.out","w",stdout);
#endif
	int T=gi();m=gi();
	init();
	while(T--){
		l=gi(),r=gi(),k=gi();
		work();
	}
	return 0;
}

P5321 [BJOI2019]送别

等zsy更了我再更。

P5322 [BJOI2019] 排兵布阵

直接dp。

#include<bits/stdc++.h>
#define il inline
#define vd void
typedef long long ll;
il ll gi(){
    ll x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    return x*f;
}
int f[101][20001],a[101][101];
int main(){
#ifdef XZZSB
    freopen("in.in","r",stdin);
    freopen("out.out","w",stdout);
#endif
    int s=gi(),n=gi(),m=gi();
    for(int i=1;i<=s;++i)
        for(int j=1;j<=n;++j)
            a[j][i]=gi()*2+1;
    for(int i=1;i<=n;++i){
        std::sort(a[i]+1,a[i]+s+1);
        for(int j=0;j<=m;++j)
            for(int k=0;k<=s;++k)
                if(j+a[i][k]<=m)f[i][j+a[i][k]]=std::max(f[i][j+a[i][k]],f[i-1][j]+i*k);
                else break;
    }
    printf("%d\n",f[n][m]);
    return 0;
}

P5323 [BJOI2019] 光线

几块玻璃可以合起来,这块玻璃有从上到下/从下到上的透光度/反射度。

每次合并\(1-i\)的玻璃和\(i+1\)玻璃,发现上面这个玻璃只要记上到下的透光度和下到上的反射度就行了。

新玻璃透光度和反射度的式子手推就行了,大约是一个等比数列。

#include<bits/stdc++.h>
#define il inline
#define vd void
#define mod 1000000007
typedef long long ll;
il ll gi(){
    ll x=0,f=1;
    char ch=getchar();
    while(!isdigit(ch)){
        if(ch=='-')f=-1;
        ch=getchar();
    }
    while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    return x*f;
}
il vd exgcd(int a,int b,int&x,int&y){
    if(!b)x=1,y=0;
    else exgcd(b,a%b,y,x),y-=x*(a/b);
}
il int inv(int o){
    int a=o,b=mod,x,y;
    exgcd(a,b,x,y);
    return (x+mod)%mod;
}
int main(){
#ifdef XZZSB
    freopen("in.in","r",stdin);
    freopen("out.out","w",stdout);
#endif
    int n=gi();
    int a1=gi()*570000004ll%mod,b1=gi()*570000004ll%mod;
    for(int i=2;i<=n;++i){
        int a2=gi()*570000004ll%mod,b2=gi()*570000004ll%mod;
        int iv=inv((mod+1-1ll*b1*b2%mod)%mod);
        int a3=1ll*a1*a2%mod*iv%mod;
        int b3=(b2+1ll*a2*a2%mod*b1%mod*iv%mod)%mod;
        a1=a3,b1=b3;
    }
    printf("%d\n",a1);
    return 0;
}

P5324 [BJOI2019] 删数

有一个简单的dp,\(f[i]\)表示现在长度为\(i\),每次删\(j\)\(i\)并跳到\(i-j\),如果没有一个\(i\)直接跳到\(i-1\),这样跳到\(0\)最多删掉多少数。

然后有一个差不多的问题:设数\(i\)\(cnt_i\)个,就覆盖\([i-cnt_i+1,i]\),求最后多少个数没有被覆盖。感性理解感性证明这个的答案和上面的\(dp\)一样。

那么就是一个普及题了:用线段树维护,每次修改两个\(cnt\)或者移动区间(都可以变成区间加),查询线段树上一段\(0\)的数量。移动区间可以线段树两边都扩展\(m\)

查询线段树上一段\(0\)的数量,我以为要记是0的数量,就做不了了。实际上不会有数减到\(-1\),所以可以记最小值的数量,就能做了。

#include<bits/stdc++.h>
#define il inline
#define vd void
typedef long long ll;
il int gi(){
	int x=0,f=1;
	char ch=getchar();
	while(!isdigit(ch))f^=ch=='-',ch=getchar();
	while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
	return x*f;
}
int n,m,N,L,a[450002],cnt[450002];
#define mid ((l+r)>>1)
int lz[1800010];
std::pair<int,int>s[1800010];
il std::pair<int,int>operator+(std::pair<int,int>a,std::pair<int,int>b){
	if(a.first<b.first)return a;
	else if(a.first>b.first)return b;
	else return{a.first,a.second+b.second};
}
il vd build(int x,int l,int r){
	s[x].second=r-l+1;if(l==r)return;
	build(x<<1,l,mid),build(x<<1|1,mid+1,r);
}
il vd upd(int x,int y){s[x].first+=y;lz[x]+=y;}
il vd down(int x){if(lz[x])upd(x<<1,lz[x]),upd(x<<1|1,lz[x]),lz[x]=0;}
il vd update(int x,int l,int r,const int&L,const int&R,const int&d){
	if(L<=l&&r<=R)return upd(x,d);
	down(x);
	if(L<=mid)update(x<<1,l,mid,L,R,d);
	if(mid<R)update(x<<1|1,mid+1,r,L,R,d);
	s[x]=s[x<<1]+s[x<<1|1];
}
il std::pair<int,int>query(int x,int l,int r,const int&L,const int&R){
	if(L<=l&&r<=R)return s[x];
	down(x);
	if(L<=mid)
		if(mid<R)return query(x<<1,l,mid,L,R)+query(x<<1|1,mid+1,r,L,R);
		else return query(x<<1,l,mid,L,R);
	else return query(x<<1|1,mid+1,r,L,R);
}
il vd update(int p,int x){
	if(x==1){
		if(1<=p-cnt[p]&&p-cnt[p]<=N&&L+1<=p&&p<=L+n)update(1,1,N,p-cnt[p],p-cnt[p],1);
		++cnt[p];
	}else{
		if(1<=p-cnt[p]+1&&p-cnt[p]+1<=N&&L+1<=p&&p<=L+n)update(1,1,N,p-cnt[p]+1,p-cnt[p]+1,-1);
		--cnt[p];
	}
}
#undef mid
int main(){
	n=gi(),m=gi(),N=n+m*2+2,L=m+1;
	int p,x;
	build(1,1,N);
	for(int i=1;i<=n;++i)update(a[i]=gi()+L,1);
	for(int i=1;i<=m;++i){
		p=gi(),x=gi();
		if(p>0)update(a[p],-1),update(a[p]=x+L,1);
		else
			if(x==1)update(1,1,N,L+n-cnt[L+n]+1,L+n,-1),--L,update(1,1,N,L+1-cnt[L+1]+1,L+1,1);
			else update(1,1,N,L+1-cnt[L+1]+1,L+1,-1),++L,update(1,1,N,L+n-cnt[L+n]+1,L+n,1);
		auto ans=query(1,1,N,L+1,L+n);
		printf("%d\n",ans.first?0:ans.second);
	}
	return 0;
}
posted @ 2019-04-23 15:34  菜狗xzz  阅读(407)  评论(0编辑  收藏  举报