AtCoder Regular Contest 096 题解

C - Half and Half
题意:你需要买两种披萨 A A B 分别 Ai A i Bi B i 个。 Ai A i Bi B i 为整数
3 3 种购买方式。
1 A A
1 B B .
0.5 A A 0.5 B B
三种购买方式价格不相同,求最小花费。

简单贪心即可。

#include<bits/stdc++.h>
using namespace std;

int A,B,C,X,Y;

int main(){
    scanf("%d%d%d%d%d",&A,&B,&C,&X,&Y);
    if(C*2>A+B){
        printf("%d\n",A*X+B*Y);
    }
    else {
        int D=C*2,num=min(X,Y),ans=0;
        ans+=D*num;
        X-=num;Y-=num;
        if(X){
            if(D<A)ans+=D*X;
            else ans+=X*A;
        }
        else {
            if(D<B)ans+=D*Y;
            else ans+=B*Y;
        }
    printf("%d\n",ans);
    }
    return 0;
}

D - Static Sushi
寿司店是环形吧台,周长 C 米。
你在初始点
,题目按照距离该点顺时针方向给出那个地方的相对距离 与所放寿司的能量值
你可以随便走 但每走 1 1 米要花费 1 的能量值 可以随时结束 最大化结束时的能量值 初始能量值为 0 0

考虑你可以怎么走
1 顺时针走到结束
2 2 逆时针走到结束

以上前缀和 O(n) 扫一下就好了
3 3 顺时针走到某点后 逆时针走到某点
4 逆时针走到某点后 顺时针走到某点

然后我当时就看了眼数据范围可以 nlogn n l o g n 做。。
于是我就直接枚举了先走到某点,然后走回起始点的距离。然后用 ST S T 表查询接下来最大能获取的能量值。。
emmm e m m m 其实这题直接前缀和就好了

#include <bits/stdc++.h>
using namespace std;

const int MAXN=110000;

typedef long long ll;

ll c,n,ans=0,sum=0;

struct data{
    ll dis,val;
}d[MAXN];

ll f[MAXN][21],g[MAXN][21],lg[MAXN];

void get_log(int n){
    int log=0;
    for(int i=1;i<=n;i++){
        lg[i]=(1<<log+1==i)?++log:log;
    }
}

ll querymaxf(int l,int r){
    if(l>r)return 0;
    int k=lg[r-l+1];
    return max(f[l][k],f[r-(1<<k)+1][k]);
}

ll querymaxg(int l,int r){
    if(l>r)return 0;
    int k=lg[r-l+1];
    return max(g[l][k],g[r-(1<<k)+1][k]);
}

void get1(){
    ll sum=0;
    for(int i=1;i<=n;i++){
        sum+=d[i].val;
        f[i][0]=sum-d[i].dis;
    }
    for(int i=1;(1<<i)<=n;i++){
        for(int j=1;j+(1<<i)-1<=n;j++){
            f[j][i]=max(f[j][i-1],f[j+(1<<(i-1))][i-1]);
        }
    }
}

void get2(){
    ll sum=0;
    for(int i=n;i>=1;i--){
        sum+=d[i].val;
        g[i][0]=sum-(c-d[i].dis);
    }
    for(int i=1;(1<<i)<=n;i++){
        for(int j=1;j+(1<<i)-1<=n;j++){
            g[j][i]=max(g[j][i-1],g[j+(1<<(i-1))][i-1]);
        }
    }
}

ll baoli(int limit){
    ll sum=0,M=0;
    for(int i=1;i<=limit;i++){
        sum+=d[i].val;
        M=max(M,sum-d[i].dis);
    }
    return M;
}

int main(){
    scanf("%lld%lld",&n,&c);
    get_log(n);
    for(int i=1;i<=n;i++){
        scanf("%lld%lld",&d[i].dis,&d[i].val);
    }
    for(int i=1;i<=n;i++){
        sum+=d[i].val;
        ans=max(ans,sum-d[i].dis);
    }
    sum=0;
    for(int i=n;i>=1;i--){
        sum+=d[i].val;
        ans=max(ans,sum-(c-d[i].dis));
    }
    get1();
    get2();
    //两个拼起来 
    ll prepos=0,premax=0,tmpsum=0;
    //先顺时针走 
    for(int i=1;i<=n;i++){
        tmpsum+=d[i].val;
        if(tmpsum-(2*d[i].dis)>0){
            premax=tmpsum-(2*d[i].dis);
        //  cout<<premax<<"a"<<endl;
            ans=max(ans,premax+querymaxg(i+1,n));
        }   
    }

    prepos=0,premax=0,tmpsum=0;
    //再走逆时针
    for(int i=n;i>=1;i--){
        tmpsum+=d[i].val;
        if(tmpsum-(2*(c-d[i].dis))>0){
            premax=tmpsum-(2*(c-d[i].dis));
            ans=max(ans,premax+querymaxf(1,i-1));
        //  ans=max(ans,premax+baoli(i-1));
        }
    }
    cout<<ans<<endl;
    return 0;
 }

E - Everything on It
拉面有 n n 种配料 每种配料可以选择加入到拉面中 也可以不加入 一共 2n种组合 有人来订购一些拉面
要求:
每种拉面配料不能相同。
每种配料在全部的面中至少出现过两次。

求拉面的方案数 数据范围 n<=3000 n <= 3000

计数问题两种方法—— DP D P 和容斥
这题看上去就不太好设状态。。不太能直接 DP D P

考虑容斥。 f(i) f ( i ) 表示 有 i i 种配料没出现两次 其他配料随意但不重复的方案数。
得到 ans=(1)iC(n,i)f(i)
如果我们得到 f(i) f ( i ) 即可 O(n) O ( n ) 求出 ans a n s

如何计算 f(i) f ( i )
先考虑 i i 种配料没出现两次的方案数 其他配料到时候可以添加计算
g(i,j) j j 个面中有 i个 不会出现两次及以上的配料的个数 即 只出现一次或没出现过 j<=i j <= i
是一个斯特林数递推式
g(i,0)=1 g ( i , 0 ) = 1 i i 种配料放到 0 个碗中 只有不放的情况
g(i,j)=g(i1,j1)+g(i1,j)(j+1) g ( i , j ) = g ( i − 1 , j − 1 ) + g ( i − 1 , j ) ∗ ( j + 1 )
其中前半部分是把这个配料放到第 j j 个碗中,后半部分是把这个配料放到 1j 个碗中或不放。
这里解释一下为什么前后都存在 j j 个碗但不重复的原因。
因为 这个状态的 j<=i
所以最后一个碗不会是空碗的。 所以这两个转移一个是碗 j j 中 只有一种不符合配料, 另一个是 有多种不符合的配料

这样我们可以得到把不符合的 i 种配料放在前 k k 个 碗中的方案数。
然后我们考虑把其他配料加进来的方案数。

我们可以直接把这些配料组合 首先有 2ni种组合。
每种组合可以选择放不放在碗中 总计 22ni 2 2 n − i 种组合。

然后前j个碗中的每一个玩 我们可以选择加不加其他配料。直接添加并不会导致重复。
所以是 2(ni)j 2 ( n − i ) j 种方案。

得到 f(i)=g(i)(j)22ni2(ni)j f ( i ) = ∑ g ( i ) ( j ) ∗ 2 2 n − i ∗ 2 ( n − i ) j
关于 22ni 2 2 n − i 的计算,由于指数部分过大,我们可以要进行取模。
结论: ananmod(p1)(modp) a n ≡ a n · m o d ( p − 1 ) ( m o d p )
证明参考百度。。
计算出 f(i) f ( i ) 后 预处理组合数 代入 ans=(1)iC(n,i)f(i) a n s = ∑ ( − 1 ) i C ( n , i ) f ( i ) 计算答案。

#include<bits/stdc++.h>
using namespace std;

const int MAXN=4005;
typedef long long ll;
ll n,MOD,C[MAXN][MAXN],f[MAXN][MAXN],p[MAXN];

ll qpow(ll a,ll b){
    ll ans=1,base=a;
    while(b){
        if(b&1)(ans*=base)%=MOD;
        (base*=base)%=MOD;
        b>>=1;
    }
    return ans;
}

ll qpowt(ll a,ll b){
    ll ans=1,base=a;
    while(b){
        if(b&1)(ans*=base)%=(MOD-1);
        (base*=base)%=(MOD-1);
        b>>=1;
    }
    return ans;
}


void get(){
    for(ll i=0;i<=n;i++)C[i][0]=1;
    for(ll i=1;i<=n;i++){
            //c[i][0]=1;  
       // c[i][i]=1;  
        for(ll j=1;j<=i;j++){
            C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
        }
    }
    for(ll i=0;i<=n;i++){
        f[i][0]=1;
        for(ll j=1;j<=i;j++){
            f[i][j]=f[i-1][j-1]+((f[i-1][j]*(j+1))%MOD);
            f[i][j]%=MOD;
        }
    }
    for(ll i=0;i<=n;i++){
        ll B=qpow(2,1ll*(n-i));
        ll mul=1;
        for(ll j=0;j<=i;j++){
            p[i]+=f[i][j]*mul;  
            p[i]%=MOD;
            mul*=B;mul%=MOD;
        }
        p[i]*=qpow(2,qpowt(2,n-i));
        p[i]%=MOD;
    }
}

//ans=sigma (-1)^i * C(n,i)*f[i];

inline void add(ll &a,ll b){
    a+=b;if(a>=b)a-=b;
}

int main(){
    scanf("%lld%lld",&n,&MOD);
    get();
    ll ans=0;
    for(ll i=0;i<=n;i++){
        ans+=((qpow(-1,i)*C[n][i])%MOD)*p[i];
        ans%=MOD;add(ans,MOD);
    } 
    cout<<ans<<endl;
    return 0;
}

F - Sweet Alchemy
这次是做甜甜圈的。。强烈谴责四道美食题。
n50 n ≤ 50 的树,每个点有权值,现要选点(可多次选一个点)使点数尽量多,如下限制:
选的总权值不超过 C1e9 C ≤ 1 e 9 ci c i 表示 i i 选的次数,pi 表示 i i 的父亲,那么cpicicpi+D
D1e9 D ≤ 1 e 9 是给定常数。

题意要求 儿子选的次数要不严格大于父亲,但不能多出 D D 次。

即 若根选了 xi次 子树中所有点至少选 xi x i 次。
不妨直接把点与子树打包。。
vi v i 表示以 i i 为根子树点权和 wi表示以 i i 为根子树点的个数。
题目变成了 有 50 个物品 每个物品有体积和价值。最大化背包价值。
只不过这是超大容量的背包 显然不能 O(nv) O ( n v )
但我们注意到 价值和物品数量 都很少 均 <=50 <= 50 <script type="math/tex" id="MathJax-Element-26817"><=50</script>。

背包问题的经典错误解法 算性价比然后贪心选择。
错误的原因是 选性价比高的可能因为个体空间较大 从而没充分利用背包容量 有浪费的容量。
我们假设有选了超过 n n 个性价比低的和少于 n 个性价比高的。
可以直接替换使答案更优。所以我们应该把一些浪费的空间充分利用,但题目中空间不允许DP 我们可以按照价值来DP。

因为贪心按照 w1/v1>=w2/v2 w 1 / v 1 >= w 2 / v 2 所以 w1v2>=w2v1 w 1 v 2 >= w 2 v 1
可理解为 把 w1 w 1 个物品2 替换为 w2 w 2 v1 v 1 后 体积会变小 价格却不变。
w w n 同阶。
所以把 每种物品取 min(d,n) m i n ( d , n ) 个放入背包,求出当前价值下最小体积。 剩下的物品贪心即可比较答案。
可以知道最大价值最多为 O(n3) O ( n 3 ) 多重背包计算
复杂度 O(n4logn) O ( n 4 l o g n )

#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll INF=2e18;
const int MAXN=55;

ll m[MAXN],p[MAXN],w[MAXN],n,x,d;
// m->v w->w p->fa
ll V[MAXN*100],W[MAXN*100],f[MAXN*MAXN*MAXN];

struct data{
    ll v,w,num;
}da[MAXN];

bool cmp(data a,data b){
    return (1.0*a.w/a.v)>(1.0*b.w/b.v);
}

int cnt=0;ll maxval=0;
void split(){
    int limit,zuoyi=0;
    for(int i=1;i<=n;i++){
        zuoyi=0;
        if(i==1){
            limit=n;
            da[i].v=m[i],da[i].w=w[i],da[i].num=INF;    
        }
        else {
            limit=min(1ll*n,d);
            da[i].v=m[i],da[i].w=w[i],da[i].num=d-limit;
        }
        while(limit){
            int p=1<<zuoyi;
            if(limit>=p){
                limit-=p;if(p*m[i]>x)break;
                V[++cnt]=p*m[i];W[cnt]=p*w[i];
                maxval+=W[cnt];
            }
            else {
                p=limit;limit=0;if(p*m[i]>x)break;
                V[++cnt]=p*m[i];W[cnt]=p*w[i];
                maxval+=W[cnt];
            }
            zuoyi++;
        }
    }
}

ll solve(ll price,ll volume){
    ll ReMain=x-volume,ans=price;
    for(int i=1;i<=n;i++){
        ll picked=min(da[i].num,ReMain/da[i].v);
        ans+=picked*da[i].w;
        ReMain-=picked*da[i].v;
    }   
    return ans;
}

int main(){
    scanf("%lld%lld%lld",&n,&x,&d);
    for(int i=1;i<=n;i++){
        ll tmp;
        scanf("%lld",&tmp);
        m[i]+=tmp; 
        if(i>1)scanf("%lld",&p[i]);
        w[i]=1;
    }
    for(int i=n;i>=1;i--)w[p[i]]+=w[i],m[p[i]]+=m[i];
    split();
//  for(int i=1;i<=n;i++)cout<<m[i]<<" "<<w[i]<<endl;
//  puts("");
    //for(int i=1;i<=cnt;i++)cout<<V[i]<<" "<<W[i]<<endl;
    for(int i=0;i<=n*n*n;i++)f[i]=INF;
    f[0]=0;
    for(int i=1;i<=cnt;i++){
        for(int j=n*n*n;j>=W[i];j--){
            f[j]=min(f[j],f[j-W[i]]+V[i]);
        }
    }
//  for(int i=0;i<=n*n*n;i++)cout<<i<<":"<<f[i]<<endl;
    ll ans=0;
    sort(da+1,da+1+n,cmp);
//  for(int i=1;i<=n;i++)cout<<da[i].v<<" ";puts("");
    for(int i=0;i<=n*n*n;i++){
        if(f[i]<=x){
            ans=max(ans,solve(i,f[i]));
        }
    }
    cout<<ans<<endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值