【Codeforces Round #526 (Div. 2)】

前言

上午考完编译原理,12:52左右回到实验室,然后学弟说打算13:00开一场vp,之后我们愉快的在13:05开始了这场vp,开场看A,题意把我搞晕了好久,后来冷静了一下15min1A,之后看B,一眼二分,check写错wa了一次,26min2A,之后看C,发现就是个暴力,42min1A,来到D,发现做过,开始写,奈何建边的时候边权写错,浪费了20分钟找这个bug实在不应该,之后数组开小RE一发,也实在是不应该,最后82min2A最后看E,一眼字典树,但是感觉不好实现,就慢慢写,最后一分钟写完,数组开小,再交,119min2A

这场比赛完全暴露了编译原理考完变成智障的事实,两个数组开小,还有A题慌张,都是不应该的,下次注意吧。


A. The Fair Nut and Elevator

题意

给你n个楼层的用户个数,每一个用户每天上下楼两次,电梯运转是这样的,有一个基础位置x,用户想从a->b的话,电梯的运转是这样的,x->a->b->x,问x选在哪个楼层能使电梯的行驶距离最短。

做法

因为n<=100,暴力枚举就可以。

代码

#include<stdio.h>
#include<iostream>
using namespace std;
const int maxn = 1e5+5;
int a[maxn];
int main()
{
    int n;
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]);
    int ans=INF;
    for(int i=1;i<=n;i++)
    {
        int sum=0;
        for(int j=1;j<=n;j++)
        {
            int tmp=0;
            tmp=tmp+i+j-2;
            tmp+=abs(i-j);
            sum+=a[j]*tmp;
        }
        ans=min(ans,sum*2);
    }
    printf("%d\n",ans);
    return 0;
}


B. Kvass and the Fair Nut

题意

题意就是给你n个杯子,每个杯子中有 v i v_i vi升水,从中倒出s剩水,尽量让剩下杯子中的水的最小值最大

做法

最小值最大,这明显的二分,所以写个二分+check就可以了。
要注意check的时候如果有杯子容量本来就小于当前mid,直接return false

代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
ll v[maxn];
int n;
ll s;
bool check(ll mid)
{
    ll sum=0;
    for(int i=1;i<=n;i++)
    {
        if(v[i]>=mid) sum+=v[i]-mid;
        else return false;
    }
    if(sum>=s) return true;
    else return false;
}
int main()
{
    scanf("%d%lld",&n,&s);
    for(int i=1;i<=n;i++) scanf("%lld",&v[i]);
    ll l=0,r=1000000000,mid;
    while(l<=r)
    {
        mid=(l+r)/2;
        if(check(mid)) l=mid+1;
        else r=mid-1;
    }
    printf("%lld\n",r);
    return 0;
}


C. The Fair Nut and String

题意

给你一个字符串,现在统计子序列的个数,满足

1.子序列所有元素都是’a’
2.子序列中任意两个连续的a,在原序列中,这两个a之间必须有一个b

做法

我们发现字符串中只有ab是有意义的字符,所以字符串就可以看成
一段a,b,一段a,一段b,一段a,b,一段a,b…
对于某一段a,他可以和之前每一段a组合,所以我们正向算
用pre表示之前有多少合法的子序列,num表示当前段有多少个a
那么pre*num就是以这段为结尾的方案数,之后更新pre,
这样最后统计以每一段结尾的方案数就是答案。

代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<set>
#include<vector>
#include<string.h>
using namespace std;
typedef long long ll;
const int maxn = 1e5+5;
const int Mod=1000000007;
char str[maxn];
vector<ll> v;
int main()
{
    scanf("%s",str);
    int len=strlen(str);
    for(int i=len-1;i>=0;i--)
    {
        if(str[i]=='a')
        {
            ll sum=0;
            while(i>=0&&str[i]!='b')
            {
                if(str[i--]=='a') sum++;
            }
            v.push_back(sum);
        }
    }
    ll ans=0,pre=0;
    for(int i=0;i<v.size();i++)
    {
        ans=(ans+v[i]+v[i]*pre%Mod)%Mod;
        pre=ans;
    }
    printf("%lld\n",ans);
    return 0;
}


D. The Fair Nut and the Best Path

题意

给你一棵树,在树中找出一条路径(也可以只有一个点),
这条路径(点权和-边权和)最大。

做法

我们设dp[i]为以i为出发点向子树方向的最优路径,
那么我们可以很轻松的用儿子的dp数组更新父亲的dp数组
也就是取一个最大的dp[i]-w[v],w[v]表示父亲与这个儿子之间的路径权值

我们有了dp数组,我们发现,对于某个点,如果答案在这个点的子树内而且经过这个点,答案要么是从这个点出发的一条向子树的路径,要么是从某个子树到这个点再到另一颗子树。对于第一种情况,答案很显然就是dp[i]中的最大值,第二种情况,就对每个点在他的儿子中选两个最优的连接起来,同样取所有点的最大值就可以。
在这里插入图片描述
比如上图,经过rt的而且在他的子树内的最优路径,一定是在所有的 D P [ i ] − w i DP[i]-w_i DP[i]wi中选出两个最大而且大于0的值,连接起来。就这样不断从叶子向上更新就可以得到答案。
代码

#include<stdio.h>
#include<iostream>
#include<algorithm>
#include<vector>
#include<string.h>
using namespace std;
typedef long long ll;
const int maxn = 5e5+5;
vector<int> G[maxn];
vector<ll> val[maxn];
ll w[maxn];
ll dp[maxn];
int n,u,v;
ll we,ans;
vector<ll> tmp;
bool cmp(ll a,ll b)
{
    return a>b;
}
void dfs(int rt,int fa)
{
    dp[rt]=w[rt];
    for(int i=0;i<G[rt].size();i++)
    {
        int to=G[rt][i];
        ll va=val[rt][i];
        if(to==fa) continue ;
        dfs(to,rt);
        dp[rt]=max(dp[rt],w[rt]+dp[to]-va);
    }
    ans=max(ans,dp[rt]);
    tmp.clear();
    for(int i=0;i<G[rt].size();i++)
    {
        int to=G[rt][i];
        ll va=val[rt][i];
        if(to==fa) continue ;
        tmp.push_back(dp[to]-va);
    }
    sort(tmp.begin(),tmp.end(),cmp);
    ll tt=w[rt];
    if(tmp.size()>=2)
    {
        if(tmp[0]>=0) tt+=tmp[0];
        if(tmp[1]>=0) tt+=tmp[1];
    }
    ans=max(ans,tt);
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++) scanf("%lld",&w[i]);
    for(int i=1;i<=n-1;i++)
    {
        scanf("%d%d%lld",&u,&v,&we);
        G[u].push_back(v);
        G[v].push_back(u);
        val[u].push_back(we);
        val[v].push_back(we);
    }
    dfs(1,-1);
    printf("%lld\n",ans);
    return 0;
}

E. The Fair Nut and Strings

题意

给你两个只含ab的长度为n的字符串,让你在字典序在这两个之间的字符串中找出k个字符串,使这k个字符串有最多的不同前缀,输出不同的前缀个数。

做法

把ab考虑为01,而且还要考虑前缀,很显然可以想到01字典树
我们把字典树画出来一看,
在这里插入图片描述
如果我们要查011和110之间的字符串,我们发现就是这样一个区域
在这里插入图片描述
很明显我们只要统计每个长度的前缀有多少种就可以,
也就是从第一层开始每一层有多少个节点,选的时候尽量选层数靠后的就可以
最开始的写法不太好写,后来优化了一下就变成了下面这么短
代码

#include<stdio.h>
#include<iostream>
using namespace std;
typedef long long ll;
const int maxn = 5e5+5;
char str1[maxn],str2[maxn];
int main()
{
    int n,k;
    scanf("%d%lld",&n,&k);
    scanf("%s",str1);
    scanf("%s",str2);
    ll ans=0,pre=0;
    for(int i=0;i<n;i++)
    {
        pre=min(pre*2+(int)(str2[i]-str1[i]),1LL<<40);
        ans+=min(pre+1,(ll)k);
    }
    printf("%lld\n",ans);
    return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值