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}^ninom{fib_i}k)有关。
再看看(ans3),显然(n)为奇数是没有答案,设(f_n)表示(2n)列时的答案。
考虑怎么放。首先有一种放法就是三个横条,从(f_{n-1})转移过来,1种放法;还有一种方法就是
---------
| | | |
- ----- -
| | | |
---------
| | |
---------
这样可以放任意长度为偶数的段,而且上下翻转也是一种方案,所以从任意的(f_{i}(i<n))可以转移过来,有2种方法。
综上,(f_n=f_{n-1}+2sum_{i=0}^{n-1}f_i)
设(g_n=sum_{i=0}^nf_n),那么用上面的递推式改改可以得到另一个递推式(g_n=4g_{n-1}-g_{n-2})。
下面(ans2)和(ans3)的方法是类似的。
(inom nk)显然是个(k)次多项式,可以(O(k^2))的时间预处理出来
那么答案变成了
(sum_{i=0}^nsum_{j=0}^ka_jfib_i^j)
(sum_{j=0}^ka_jsum_{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}^nsum_{j=0}^kinom kj (ab^i)^j(cd^i)^{k-j})
(sum_{i=0}^nsum_{j=0}^kinom kj a^jb^{ij}c^{k-j}d^{i(k-j)})
(sum_{i=0}^nf(i)^k=sum_{j=0}^kinom kj a^jc^{k-j}sum_{i=0}^n(b^{j}d^{k-j})^i)
后面直接等比数列求和即可,那么这个式子就能算了。
但是需要注意, (3)和(5)在(mod 998244353)意义下都没有二次剩余,可以开一个struct存(a,b),真实数就是(a+bsqrt 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
",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
",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
",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
",ans.first?0:ans.second);
}
return 0;
}