「BJOI2019」
#4372. 「BJOI2019」排兵布阵
题目描述:
小 C 正在玩一款排兵布阵的游戏。在游戏中有 $n$ 座城堡,每局对战由两名玩家来争夺这些城堡。每名玩家有 $m$ 名士兵,可以向第 $i$ 座城堡派遣 $a_i$ 名士兵去争夺这个城堡,使得总士兵数不超过 $m$。
如果一名玩家向第 $i$ 座城堡派遣的士兵数严格大于对手派遣士兵数的两倍,那么这名玩家就占领了这座城堡,获得 $i$ 分。
现在小 C 即将和其他 $s$ 名玩家两两对战,这 $s$ 场对决的派遣士兵方案必须相同。小 C 通过某些途径得知了其他 $s$ 名玩家即将使用的策略,他想知道他应该使用什么策略来最大化自己的总分。
由于答案可能不唯一,你只需要输出小 C 总分的最大值。
思路:
背包问题
$f_{i,j}$ 表示选到前 $i$ 座城堡已经用了 $j$ 个士兵所能获得总分的最大值。
倘若直接枚举每座城堡派几个士兵效率是 $O(nm^{2})$ ,但是我们发现只有多获胜一个人才能使总分增多,所以我们可以考虑枚举每座城堡赢几个人,效率变成 $O(nms)$ 。
代码:
#include<bits/stdc++.h> #define il inline #define _(d) while(d(isdigit(ch=getchar()))) using namespace std; const int N=105,M=2e4+5; int n,s,m,a[N][N],f[M]; il int read(){ int x,f=1;char ch; _(!)ch=='-'?f=-1:f;x=ch^48; _()x=(x<<1)+(x<<3)+(ch^48); return f*x; } int main() { s=read();n=read();m=read(); for(int i=1;i<=s;i++)for(int j=1;j<=n;j++)a[j][i]=read()*2+1; for(int i=1;i<=n;i++)sort(a[i]+1,a[i]+1+s); for(int i=1;i<=n;i++)for(int j=m-1;j>=0;j--){ for(int k=1;k<=s;k++){ if(a[i][k]+j>m)break; f[a[i][k]+j]=max(f[a[i][k]+j],f[j]+k*i); } } int ans=0; for(int i=1;i<=m;i++)ans=max(ans,f[i]); printf("%d\n",ans); return 0; }
#4374. 「BJOI2019」光线
题目描述:
当一束光打到一层玻璃上时,有一定比例的光会穿过这层玻璃,一定比例的光会被反射回去,剩下的光被玻璃吸收。
设对于任意 $x$,有 $x\times a_i\%$ 单位的光会穿过它,有 $x\times b_i\%$ 的会被反射回去。
现在 $n$ 层玻璃叠在一起,有 $1$ 单位的光打到第 $1$ 层玻璃上,那么有多少单位的光能穿过所有 $n$ 层玻璃呢?
思路:
$dp$ 问题
令 $f_{i}$ 表示 $1$ 单位光射入第 $i$ 层玻璃,能有多少穿过 $n$ 层玻璃。
令 $g_{i}$ 表示 $1$ 单位的光射入第 $i$ 层玻璃,最终能有多少光反射出去。
$$
f_{i}=a_i*(f_{i+1}+g_{i+1}*b_{i}*f_{i+1}+...)
$$
$$
f_{i}=a_i*f_{i+1}*\frac{1}{1-b_i*g_{i+1}}
$$
$$
gi=b_i+a_i*g_{i+1}*a_{i}+a_i*g_{i+1}*b_{i}*g_{i+1}*a_{i}...
$$
$$
g_{i}=b_i+a_i^2*g_{i+1}*\frac{1}{1-b_i*g_{i+1}}
$$
代码:
#include<bits/stdc++.h> #define il inline #define LL long long #define _(d) while(d(isdigit(ch=getchar()))) using namespace std; const int N=5e5+5,p=1e9+7,ny=570000004; int n,a[N],b[N],f[N],g[N]; il int read(){ int x,f=1;char ch; _(!)ch=='-'?f=-1:f;x=ch^48; _()x=(x<<1)+(x<<3)+(ch^48); return f*x; } il int ksm(LL a,int y){ LL b=1; while(y){ if(y&1)b=b*a%p; a=a*a%p;y>>=1; } return b; } il int mu(int x,int y){ return (x+y>=p)?x+y-p:x+y; } int main() { n=read(); for(int i=1;i<=n;i++){ a[i]=1ll*read()*ny%p;b[i]=1ll*read()*ny%p; } f[n]=a[n];g[n]=b[n]; for(int i=n-1;i;i--){ f[i]=1ll*a[i]*f[i+1]%p*ksm(mu(1,p-1ll*g[i+1]*b[i]%p),p-2)%p; g[i]=mu(b[i],1ll*a[i]*a[i]%p*g[i+1]%p*ksm(mu(1,p-1ll*b[i]*g[i+1]%p),p-2)%p); } printf("%d\n",f[1]); return 0; }
#4375. 「BJOI2019」删数
题目描述:
对于任意一个数列,如果能在有限次进行下列删数操作后将其删为空数列,则称这个数列可以删空。一次删数操作定义如下:
- 记当前数列长度为 $k$,则删掉数列中所有等于 $k$ 的数。
现有一个长度为 $n$ 的数列 $a$,有 $m$ 次修改操作,第 $i$ 次修改后你要回答:经过 $i$ 次修改后的数列 $a$,至少还需要修改几个数才可删空?
每次修改操作为单点修改或数列整体加一或数列整体减一。
思路:
对于一个数 $x$ 共有 $b[x]$ 个,那么他所能覆盖的区间为 $[x-b[x]+1,x]$ ,答案即为区间内没有被覆盖的数。
对于整体加减相当于把被覆盖区间范围整体移动。
可以用线段树维护。
代码:
#include<bits/stdc++.h> #define il inline #define _(d) while(d(isdigit(ch=getchar()))) using namespace std; const int N=6e5+5; int n,m,a[N],ans,mx,b[N],fir,mn[N<<2],num[N<<2],tag[N<<2]; il int read(){ int x,f=1;char ch; _(!)ch=='-'?f=-1:f;x=ch^48; _()x=(x<<1)+(x<<3)+(ch^48); return f*x; } il void update(int x){ mn[x]=min(mn[x<<1],mn[x<<1|1]); num[x]=(mn[x]==mn[x<<1]?num[x<<1]:0)+(mn[x]==mn[x<<1|1]?num[x<<1|1]:0); } il void pushdown(int x){ if(!tag[x])return; int v=tag[x];tag[x]=0; mn[x<<1]+=v;mn[x<<1|1]+=v;tag[x<<1]+=v;tag[x<<1|1]+=v; } il void build(int x,int l,int r){ num[x]=r-l+1; if(l==r)return; int mid=(l+r)>>1; build(x<<1,l,mid);build(x<<1|1,mid+1,r); } il void change(int x,int l,int r,int ql,int qr,int v){ if(ql<=l&&r<=qr){ tag[x]+=v;mn[x]+=v; return; } int mid=(l+r)>>1;pushdown(x); if(ql<=mid)change(x<<1,l,mid,ql,qr,v); if(mid<qr)change(x<<1|1,mid+1,r,ql,qr,v); update(x); } il int query(int x,int l,int r,int ql,int qr){ if(ql<=l&&r<=qr){ if(mn[x]==0)return num[x]; else return 0; } int res=0,mid=(l+r)>>1;pushdown(x); if(ql<=mid)res=query(x<<1,l,mid,ql,qr); if(mid<qr)res+=query(x<<1|1,mid+1,r,ql,qr); return res; } int main() { n=read();m=read(); mx=2*(n+m);fir=n+m;build(1,1,mx); for(int i=1;i<=n;i++)a[i]=read()+fir,b[a[i]]++; for(int i=fir+1;i<=fir+n;i++)if(b[i])change(1,1,mx,i-b[i]+1,i,1); for(int i=1;i<=m;i++){ int p=read(),x=read(); if(!p){ if(x==1){ if(b[fir+n])change(1,1,mx,fir+n-b[fir+n]+1,fir+n,-1); fir--; if(b[fir+1])change(1,1,mx,fir+1-b[fir+1]+1,fir+1,1); } else{ if(b[fir+1])change(1,1,mx,fir+1-b[fir+1]+1,fir+1,-1); fir++; if(b[fir+n])change(1,1,mx,fir+n-b[fir+n]+1,fir+n,1); } } else{ int now=a[p]; if(now<=fir+n&&now>fir)change(1,1,mx,now-b[now]+1,now-b[now]+1,-1);b[now]--; now=fir+x;b[now]++;a[p]=now; if(now<=fir+n&&now>fir)change(1,1,mx,now-b[now]+1,now-b[now]+1,1); } printf("%d\n",query(1,1,mx,fir+1,fir+n)); } return 0; }
#4369. 「BJOI2019」奥术神杖
题目:
Bezorath 大陆抵抗地灾军团入侵的战争进入了僵持的阶段,世世代代生活在 Bezorath 这片大陆的精灵们开始寻找远古时代诸神遗留的神器,试图借助神器的神秘力量帮助她们战胜地灾军团。
在付出了惨痛的代价后,精灵们从步步凶险的远古战场取回了一件保存尚完好的神杖。但在经历过那场所有史书都视为禁忌的“诸神黄昏之战”后,神杖上镶嵌的奥术宝石已经残缺,神力也几乎消耗殆尽。精灵高层在至高会议中决定以举国之力收集残存至今的奥术宝石,并重金悬赏天下能工巧匠修复这件神杖。
你作为神术一脉第五百零一位传人,接受了这个艰巨而神圣的使命。 神杖上从左到右镶嵌了 $n$ 颗奥术宝石,奥术宝石一共有 $10$ 种,用数字 `0123456789` 表示。有些位置的宝石已经残缺,用 `.` 表示,你需要用完好的奥术宝石填补每一处残缺的部分(每种奥术宝石个数不限,且不能够更换未残缺的宝石)。古老的魔法书上记载了 $m$ 种咒语 $(S_i,V_i)$,其中 $S_i$ 是一个非空数字串,$V_i$ 是这种组合能够激发的神力。
神杖的初始神力值 $\mathrm{Magic} = 1$,每当神杖中出现了连续一段宝石与 $S_i$ 相等时,神力值 $\mathrm{Magic}$ 就会乘以 $V_i$。但神杖如果包含了太多咒语就不再纯净导致神力降低:设 $c$ 为神杖包含的咒语个数(若咒语类别相同但出现位置不同视为多次),神杖最终的神力值为 $\sqrt[c]{\mathrm{Magic}}$。(若 $c = 0$ 则神杖最终神力值为 $1$。)
例如有两种咒语 $(01,3)$ 、$(10,4)$,那么神杖 `0101` 的神力值为 $\sqrt[3]{ 3 \times 4 \times 3}$。
你需要使修复好的神杖的最终的神力值最大,输出任何一个解即可。
思路:
第一部的转换还是挺妙妙的
$$
val=\sqrt[c]{\prod_{i=1}^{c} V_{t_i}}
$$
如果对权值取对数
$$
ln^{val}=\frac{\sum_{i=1}^{c}V_{ti}}{c}
$$
这就是典型的分数规划了
至少在 $ac$ 自动机上跑 $dp$ 。效率是 $O(sn)$ 。
代码:
#include<bits/stdc++.h> #define il inline #define db double #define _(d) while(d(isdigit(ch=getchar()))) using namespace std; const int N=1505; const db eps=1e-7; int n,m,ch[N][11],cnt=1,num[N],fa[N]; db val[N],eg[N],f[N][N]; char s[N],t[N],res; struct node{ int x,y,tp; }fr[N][N]; il int read(){ int x,f=1;char ch; _(!)ch=='-'?f=-1:f;x=ch^48; _()x=(x<<1)+(x<<3)+(ch^48); return f*x; } il void insert(db v){ int x=1,l=strlen(t+1); for(int i=1;i<=l;i++){ if(!ch[x][t[i]-'0'])ch[x][t[i]-'0']=++cnt; x=ch[x][t[i]-'0']; } num[x]++;val[x]+=v; } il void getfail(){ queue<int> q;fa[1]=1; for(int i=0;i<10;i++){ if(!ch[1][i])ch[1][i]=1; else q.push(ch[1][i]),fa[ch[1][i]]=1; } while(!q.empty()){ int x=q.front();q.pop(); val[x]+=val[fa[x]];num[x]+=num[fa[x]]; for(int i=0;i<10;i++){ if(!ch[x][i])ch[x][i]=ch[fa[x]][i]; else{ fa[ch[x][i]]=ch[fa[x]][i]; q.push(ch[x][i]); } } } } il bool pd(db m){ for(int i=1;i<=cnt;i++)eg[i]=m*num[i]-val[i]; for(int i=0;i<=n;i++)for(int j=1;j<=cnt;j++)f[i][j]=1e9; f[0][1]=0; for(int i=1;i<=n;i++){ for(int j=1;j<=cnt;j++){ if(f[i-1][j]>1e6)continue; if(s[i]=='.'){ for(int k=0;k<10;k++){ if(f[i-1][j]+eg[ch[j][k]]<f[i][ch[j][k]]){ f[i][ch[j][k]]=f[i-1][j]+eg[ch[j][k]]; fr[i][ch[j][k]]=(node){i-1,j,k}; } } } else{ int k=s[i]-'0'; if(f[i-1][j]+eg[ch[j][k]]<f[i][ch[j][k]]){ f[i][ch[j][k]]=f[i-1][j]+eg[ch[j][k]]; fr[i][ch[j][k]]=(node){i-1,j,k}; } } } } for(int i=1;i<=cnt;i++)if(f[n][i]<0)return 1; return 0; } il void back(int x,int y){ if(!x)return; back(x-1,fr[x][y].y); printf("%c",fr[x][y].tp+'0'); } int main() { n=read();m=read(); scanf(" %s",s+1); for(int i=1;i<=m;i++){ scanf(" %s",t+1); db x=read();x=log2(x); insert(x); } getfail(); db l=0,r=100; while(r-l>eps){ db mid=(l+r)/2.0; if(pd(mid))l=mid; else r=mid; } pd(l);//cout<<l<<endl; for(int i=1;i<=cnt;i++)if(f[n][i]<0){ back(n,i);break; } puts(""); return 0; }