「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;
}
View Code

 

 

#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;
}
View Code

 

#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;
}
View Code

 

#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;
}
View Code

 

posted @ 2019-06-12 09:56  Jessiejzy  阅读(362)  评论(0编辑  收藏  举报