Educational Codeforces Round 125 (Rated for Div. 2)

本文介绍了五个算法问题的解决方案,包括从(x,y)到(0,0)的最短整数步数、构造特定条件的序列、删除好前缀的最少操作次数、在有限预算内战胜怪物的最小花费以及满足特定路径条件的星形最小生成树。每个问题都提供了思路和AC代码,涉及贪心、动态规划和回文判断等算法思想。
摘要由CSDN通过智能技术生成

 

目录

A. Integer Moves 

B. XY Sequence

C. Bracket Sequence Deletion

D. For Gamers. By Gamers.

E. Star MST

F. Words on Tree


A. Integer Moves 

  • 题意:

初始时在点(x,y) ,如果(x,y)与点(x1,y1)满足如下条件,那么可以从(x,y)转移至(x1,y1):

\sqrt{(x-x1)^{2}+(y-y1)^{2}} = Integer

即两点之间的距离为整数,即可转移。

请你输出从(x,y)----->(0,0)的最小步数。     

  • 思路: 

 我们可以证明,最大步数不会超过2

  1. 设初始点为(x,y)
  2. 我们可以选择这两种方案进行转移。
  3. (x,y)-->(x,0)-->(0,0),(x,y)-->(0,y)-->(0,0)
  4. 显然沿着坐标轴移动距离一定是整数,所以最大步数不会超过2。

那么我们只需要分析步数=1和步数=0的情况:

1.步数=1

        1)初始点在(x,0)或者(0,y)上。

        2)从点(x,y)到(0,0)的距离为整数。

2.步数=0

        1)初始点在(0,0)

特别声明(判断浮点数是整数的代码):

long double temp = sqrt(x*x+y*y);
if(temp - (int)temp == 0)std::cout<<1<<'\n';
  • 时间复杂度: 

 O(1)

AC代码:

#include<bits/stdc++.h>

typedef long long ll;
typedef unsigned long long  ull;

const int N = 2e5+10,M = 2 * N,INF = 0x3f3f3f3f,mod = 1e9+7;

void solve()
{
    int x,y;
    std::cin>>x>>y;
    if(x==0&&y==0)std::cout<<0<<'\n';
    else if(x==0||y==0)std::cout<<1<<'\n';
    else
    {
        long double temp = sqrt(x*x+y*y);
        if(temp - (int)temp == 0)std::cout<<1<<'\n';
        else std::cout<<2<<'\n';
    }
}

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int T;
    std::cin>>T;
    while(T--)
    {
        solve();
    }
    return 0;
}

B. XY Sequence

  • 题意:

给定4个数n,B,x,y。

构造一个长度为n+1的序列,满足如下条件:

  1. a_{i}\leq B(0\leq i\leq n)
  2. a_{i+1} = a_{i} + x     或
  3. a_{i+1} = a_{i} - y

要求所构造出的序列满足:

\sum_{i=1}^{n}a_{i}      最大,并输出这个最大值。

  • 思路: 

既然要最大,那么贪心即可。

如果:

a_{i} + x \leq B

那么就令

a_{i+1} = a_{i} + x

反之

a_{i+1} = a_{i} - y

最后求和即可。

  • 时间复杂度: 

O(n)

AC代码:

#include<bits/stdc++.h>

typedef long long ll;
typedef unsigned long long  ull;

const int N = 2e5+10,M = 2 * N,INF = 0x3f3f3f3f,mod = 1e9+7;

void solve()
{
    ll res = 0;
    int n,b,x,y;
    std::cin>>n>>b>>x>>y;
    int temp = 0;
    for(int i = 1 ; i <= n ; i++)
    {
        if(temp + x <= b)temp += x;
        else temp -= y;
        res += temp;
    }
    std::cout<<res<<'\n';
}

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int T;
    std::cin>>T;
    while(T--)
    {
        solve();
    }
    return 0;
}

C. Bracket Sequence Deletion

  • 题意:

给定一个由 '(' 和 ')'组成的字符串,我们现在要删除这个字符串的最短好前缀,输出删除的次数,以及最后留下来的无法删除的字符个数

好前缀是满足下列条件之一的前缀:

  1. 这个前缀是这个正常的括号
  2. 这个前缀是回文
  • 思路: 

有两种情况:

1.当前字符串的第一个字符是  '(':

        当一个字符是 '(' 时,无论下一个字符是什么,一定可以满足好前缀中的一个条件。

        () 或((

2.当前字符串的第一个字符是  ')':

        首先,这个前缀一定不可能是正常的括号,所以我们只需要找回文即可。

        我们很容易发现,第一个字符一定会与后面的第一个' ) '之间形成回文

        把括号换成a和b可能更容易观察:

        abbbaba    aa      abbabababa

所以只需要按照上面的规则删去前缀即可,如果无法满足上面的条件,那么剩下的字符无法被删除。

  • 时间复杂度: 

O(n)

AC代码:

#include<bits/stdc++.h>

typedef long long ll;
typedef unsigned long long  ull;

const int N = 2e5+10,M = 2 * N,INF = 0x3f3f3f3f,mod = 1e9+7;

void solve()
{
    int n;
    std::string s;
    std::cin>>n>>s;
    s = '#' + s;
    int res = 0,i = 1;
    while(i <= n)
    {
        if(s[i]=='(')
        {
            if(i < n)res++,i+=2;
            else break;
        }
        else
        {
            int j = i + 1;
            while(j < n && s[j] =='(')j++;
            if(j <= n && s[j]==')')res++,i = j + 1;
            else break;
        }
    }
    std::cout<<res<<' '<<n - i + 1<<'\n';
}

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int T;
    std::cin>>T;
    while(T--)
    {
        solve();
    }
    return 0;
}

D. For Gamers. By Gamers.

  • 题意:

小明有C元钱。

给定n种类型的部队,购买第 i 种的部队:

1.花费  ci   元

2.获得(增加)  di   的攻击力

3.获得(增加)  hi    的血量

(一种部队可以多次购买)

现有m种怪物,第 i 种怪物具有两个属性:

1.具有  Di  的攻击力

2.具有  Hi  的血量

在攻击每一个怪物之前,小明的金额,攻击力和血量的数值都会重置。

如果小明的部队可以打赢怪物,那么小明杀死怪物的速度必须要严格快于怪物杀死小明的速度。

对于每一只怪物,输出杀死这只怪物所需要花费的最小金额,如果不存在购买方案可以杀死怪物,输出-1。

  • 思路: 

小明要更快得杀死怪物,即满足下式:

设怪物攻击力Di,血量Hi。

小明购买部队后攻击为d,血量h。

\frac{H_{i}}{d} < \frac{h}{Di}\rightarrow h*d > H_{i}*D_{i}

那么我们可以把血量和攻击力相乘,作为战斗力保存

又因为C的范围不会太大,不超过1e6,所以我们可以预处理出花费 i 元可以获得的最大战斗力。

for(int i = 1 ; i <= n ; i++)
{
    ll w,x,y;
    std::cin>>w>>x>>y;
    f[w] = std::max(x*y,f[w]);
}
for(int i = 1 ; i <= C ; i++)
    for(int j = 2 ; j * i <= C ; j++)
        f[i*j] = std::max(f[i*j],f[i]*j);

这里花费的时间是O(ClogC)

在预处理好后,对于第 i 个怪物,我们可以二分找到大于怪物战斗力所需要的最小花费。

因为需要二分,所有我们要保证  f[i]  是单调的,所以需要额外处理一下。

for(int i = 1 ; i <= C ; i++)f[i] = std::max(f[i],f[i-1]);
  • 时间复杂度: 

O((C+m)logC)

AC代码:

#include<bits/stdc++.h>

typedef long long ll;
typedef unsigned long long  ull;

const int N = 3e5+10,M = 1e6 + 10,INF = 0x3f3f3f3f,mod = 1e9+7;

ll f[M];

void solve()
{
    int n,C;
    std::cin>>n>>C;
    for(int i = 1 ; i <= n ; i++)
    {
        ll w,x,y;
        std::cin>>w>>x>>y;
        f[w] = std::max(x*y,f[w]);
    }
    for(int i = 1 ; i <= C ; i++)
        for(int j = 2 ; j * i <= C ; j++)
            f[i*j] = std::max(f[i*j],f[i]*j);
    for(int i = 1 ; i <= C ; i++)f[i] = std::max(f[i],f[i-1]);
    int m;
    std::cin>>m;
    while(m--)
    {
        ll x,y;
        std::cin>>x>>y;
        if(x*y >= f[C])std::cout<<-1<<' ';
        else
        {
            std::cout<<std::upper_bound(f+1,f+1+C,x*y) - f<<' ';
        }
    }
    std::cout<<'\n';
}

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
//    int T;
//    std::cin>>T;
//    while(T--)
    {
        solve();
    }
    return 0;
}

E. Star MST

  • 题意:

给定一个n个点的无向完全图,每条边的权值在1~k之间。

有多少种方案给每条边分配权值,使得该图满足以下条件:

最小生成树的权值和 = 所有与1号点相连的边的权值和。

  • 思路: 

我们考虑动态规划 dp[w][i] :

1.dp表示方案数。

2.w 表示当前所有边的权值小w 。

3.i 表示已经为 i 个点分配好了边权。

那么怎么进行状态转移呢?

 2. i 个点中选 j 个点。

C_{i}^{j}

3,4 .在 j 个点内部连边,内部和外部连边。

其中(k+1-w)是可选的边权值。

\sum_{j=0}^{i}(k+1-w)^{j*(j-1)/2}*(k+1-w)^{(i-j)*j}

特别声明:

1.我们可以预处理出所有的组合数

for(int i = 0 ; i < n ; i++)
{
    comb[i][0] = comb[i][i] = 1;
    for(int j = 1 ; j < i ; j++)
        comb[i][j] = (comb[i-1][j-1] + comb[i-1][j]) % mod;
}

2.我们可以在每次计算之前预处理出所有的(k+1-w)的幂次

pw[0] = 1;
for(int i = 1 ; i <= n * n ; i++)pw[i] = (pw[i-1]*(k + 1 - w)) % mod;

3.我们可以发现,我们在计算权值小于等于w的方案数时,我们只关系权值小于等于(w-1)的方案数,所以我们可以使用滚动数组,用一维的dp[i]表示即可,并使用last[i]记录(w-1)的方案数。

  • 时间复杂度: 

O(k*n^{2})

AC代码:

#include<bits/stdc++.h>

typedef long long ll;
typedef unsigned long long  ull;

const int N = 3e2+10,M = 1e6 + 10,INF = 0x3f3f3f3f,mod = 998244353;

ll dp[N],pw[N*N],last[N],comb[N][N];

void solve()
{
    int n,k;
    std::cin>>n>>k;
    dp[0] = 1;
    for(int i = 0 ; i < n ; i++)
    {
        comb[i][0] = comb[i][i] = 1;
        for(int j = 1 ; j < i ; j++)
            comb[i][j] = (comb[i-1][j-1] + comb[i-1][j]) % mod;
    }
    for(int w = 1 ; w <= k ; w++)
    {
        for(int i = 0 ;i < n ; i++)last[i] = dp[i],dp[i] = 0;
        pw[0] = 1;
        for(int i = 1 ; i <= n * n ; i++)pw[i] = (pw[i-1]*(k + 1 - w)) % mod;
        for(int i = 0 ; i < n ; i++)
            for(int j = 0 ; i - j >= 0 ; j++)
                dp[i] = (dp[i] + comb[i][j] * pw[j*(j-1)/2 + (i - j) * j] % mod * last[i-j]) % mod;
    }
    std::cout<<dp[n-1];
}

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
//    int T;
//    std::cin>>T;
//    while(T--)
    {
        solve();
    }
    return 0;
}

F. Words on Tree

  • 题意:

给定一棵树

每个节点储存一个字母

现有q个要求:

每个要求有u,v两个节点编号和一个字符串S

现在给树中的每个节点赋一个字母,满足如下条件:

对每一个要求,把从节点u到节点v路径中所有的字母按顺序取出,所组成的字符串是S。

或者把从节点v到节点u路径中所有的字母按顺序取出,所组成的字符串是S。

  • 思路: 

 首先:

1.取出路径--->采用类似于述树链剖分的策略

void dfs(int u,int father,int depth)
{
    dep[u] = depth,fa[u] = father;
    for(int i = h[u] ; ~i ; i = ne[i])
    {
        int v = e[i];
        if(v==father)continue;
        dfs(v,u,depth+1);
    }
}

std::vector<int> getPath(int u,int v)
{
    std::vector<int> L,R;
    while(u!=v)
    {
        if(dep[u] < dep[v])
        {
            R.push_back(v);
            v = fa[v];
        }
        else
        {
            L.push_back(u);
            u = fa[u];
        }
    }
    L.push_back(u);
    L.insert(L.end(),R.rbegin(),R.rend());
    return L;
}

2.选取正序的字符串或是逆序的字符串--->2-SAT

我们这里按照要求的顺序,给树中每一个节点确定一个答案,再根据2-SAT来建立关系

这里是建图过程:

for(int i = 0 ; i < q ; i++)
{
    int a,b;
    std::string s;
    std::cin>>a>>b>>s;
    a--,b--;
    query[i] = {a,b,s};
    std::vector<int> temp = getPath(a,b);
    for(int j = 0 ; j < temp.size() ; j++)
    {
        ans[temp[j]][0] = s[j];
        ans[temp[j]][1] = s[temp.size() - j - 1];
    }
}

for(int i = 0 ; i < q ; i++)
{
    int x,y;
    std::string s;
    std::tie(x,y,s) = query[i];
    std::vector<int> temp = getPath(x,y);
    for(int j = 0 ; j < temp.size() ; j++)
        for(int o = 0 ; o < 2 ; o++)
        {
            if(ans[temp[j]][o] != s[j])
            {
                add(hs,temp[j] * 2 + o,(i+n) * 2 + 1);
                add(hs,(i+n) * 2 + 0,temp[j] * 2 + !o);
            }
            if(ans[temp[j]][o] != s[temp.size() - 1 - j])
            {
                add(hs,temp[j] * 2 + o,(i+n) * 2 + 0);
                add(hs,(i+n) * 2 + 1,temp[j] * 2 + !o);
            }
        }
}

建完图后跑一边tarjan即可。

  • 时间复杂度: 

时间复杂度上限由tarjan算法决定。

本题中每个要求的每个字母都可能产生3对关系,即建立6条边,所以时间复杂度:

O(n+m)

(m不会超过n的6倍)

AC代码:

 

#include<bits/stdc++.h>

typedef long long ll;
typedef unsigned long long  ull;

const int N = 4e5+10,M = N * 2,INF = 0x3f3f3f3f,mod = 998244353;

int h[N],hs[N*4],ne[M*4],e[M*4],idx;
int dfn[N*4],low[N*4],id[N*4],stk[N*4],top,ts,scc;
int fa[N],dep[N];
bool book[N*4],res[N];
int n,q;

void init()
{
    memset(h,-1,sizeof h);
    memset(hs,-1,sizeof hs);
}

void add(int h[],int a,int b)
{
    e[idx] = b,ne[idx] = h[a],h[a] = idx++;
}

void dfs(int u,int father,int depth)
{
    dep[u] = depth,fa[u] = father;
    for(int i = h[u] ; ~i ; i = ne[i])
    {
        int v = e[i];
        if(v==father)continue;
        dfs(v,u,depth+1);
    }
}

void tarjan(int u)
{
    dfn[u] = low[u] = ++ts;
    stk[++top] = u,book[u] = true;
    for(int i = hs[u] ; ~i ; i = ne[i])
    {
        int v = e[i];
        if(!dfn[v])
        {
            tarjan(v);
            low[u] = std::min(low[u],low[v]);
        }
        else if(book[v])low[u] = std::min(low[u],dfn[v]);
    }
    if(dfn[u]==low[u])
    {
        int temp;
        scc++;
        do{
            temp = stk[top--];
            book[temp] = false;
            id[temp] = scc;
        }while(u!=temp);
    }
}

std::vector<int> getPath(int u,int v)
{
    std::vector<int> L,R;
    while(u!=v)
    {
        if(dep[u] < dep[v])
        {
            R.push_back(v);
            v = fa[v];
        }
        else
        {
            L.push_back(u);
            u = fa[u];
        }
    }
    L.push_back(u);
    L.insert(L.end(),R.rbegin(),R.rend());
    return L;
}

void solve()
{
    std::cin>>n>>q;
    init();
    for(int i = 1 ; i < n ; i++)
    {
        int a,b;
        std::cin>>a>>b;
        a--,b--;
        add(h,a,b),add(h,b,a);
    }
    dfs(0,-1,1);

    std::vector<std::array<char,2>> ans(n,{'a','b'});
    std::vector<std::tuple<int,int,std::string>> query(q);

    for(int i = 0 ; i < q ; i++)
    {
        int a,b;
        std::string s;
        std::cin>>a>>b>>s;
        a--,b--;
        query[i] = {a,b,s};
        std::vector<int> temp = getPath(a,b);
        for(int j = 0 ; j < temp.size() ; j++)
        {
            ans[temp[j]][0] = s[j];
            ans[temp[j]][1] = s[temp.size() - j - 1];
        }
    }

    for(int i = 0 ; i < q ; i++)
    {
        int x,y;
        std::string s;
        std::tie(x,y,s) = query[i];
        std::vector<int> temp = getPath(x,y);
        for(int j = 0 ; j < temp.size() ; j++)
            for(int o = 0 ; o < 2 ; o++)
            {
                if(ans[temp[j]][o] != s[j])
                {
                    add(hs,temp[j] * 2 + o,(i+n) * 2 + 1);
                    add(hs,(i+n) * 2 + 0,temp[j] * 2 + !o);
                }
                if(ans[temp[j]][o] != s[temp.size() - 1 - j])
                {
                    add(hs,temp[j] * 2 + o,(i+n) * 2 + 0);
                    add(hs,(i+n) * 2 + 1,temp[j] * 2 + !o);
                }
            }
    }

    for(int i = 0 ; i < (n + q) * 2 ; i++)if(!dfn[i])tarjan(i);
    for(int i = 0 ; i < n + q ; i++)
        if(id[i*2] == id[i*2+1])
        {
            std::cout<<"NO";
            return;
        }
        else res[i] = id[i*2+1] < id[i*2];
    std::cout<<"YES\n";
    for(int i = 0 ; i < n ; i++)std::cout<<ans[i][res[i]];
}

int main()
{
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
//    int T;
//    std::cin>>T;
//    while(T--)
    {
        solve();
    }
    return 0;
}

"educational codeforces round 103 (rated for div. 2)"是一个Codeforces平台上的教育性比赛,专为2级选手设计评级。以下是有关该比赛的回答。 "educational codeforces round 103 (rated for div. 2)"是一场Codeforces平台上的教育性比赛。Codeforces是一个为程序员提供竞赛和评级的在线平台。这场比赛是专为2级选手设计的,这意味着它适合那些在算法和数据结构方面已经积累了一定经验的选手参与。 与其他Codeforces比赛一样,这场比赛将由多个问题组成,选手需要根据给定的问题描述和测试用例,编写程序来解决这些问题。比赛的时限通常有两到三个小时,选手需要在规定的时间内提交他们的解答。他们的程序将在Codeforces的在线评测系统上运行,并根据程序的正确性和效率进行评分。 该比赛被称为"educational",意味着比赛的目的是教育性的,而不是针对专业的竞争性。这种教育性比赛为选手提供了一个学习和提高他们编程技能的机会。即使选手没有在比赛中获得很高的排名,他们也可以从其他选手的解决方案中学习,并通过参与讨论获得更多的知识。 参加"educational codeforces round 103 (rated for div. 2)"对于2级选手来说是很有意义的。他们可以通过解决难度适中的问题来测试和巩固他们的算法和编程技巧。另外,这种比赛对于提高解决问题能力,锻炼思维和提高团队合作能力也是非常有帮助的。 总的来说,"educational codeforces round 103 (rated for div. 2)"是一场为2级选手设计的教育性比赛,旨在提高他们的编程技能和算法能力。参与这样的比赛可以为选手提供学习和进步的机会,同时也促进了编程社区的交流与合作。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值