Codeforces #263 Div 1 简要题解

本文提供 CodeForces 461 赛事 A、B、C 三题的详细解析及代码实现。A题介绍了一种贪心策略,通过不断分裂集合来获得最大得分的方法;B题利用树形DP解决如何删除树边以确保每棵树仅含一个黑点的问题;C题则展示了使用树状数组维护操作和查询的技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A. Appleman and Toastman

题目链接

http://codeforces.com/contest/461/problem/A

题目大意

给你 n 个数构成的集合S,每次操作你可以选择当前的一个集合,将它分裂成两个非空集合,每次操作后,你将每个集合里的数字之和加起来,若出现了大小为1的集合,就将这个集合删去。问你操作的最大得分是多少。

思路

这样的贪心感觉比较多吧,比如NOIP的合并果子等等

显然在这个题目里,我们要让数字大的数字的加的次数尽量多点,我们可以考虑每次将一个集合分裂成两个集合,其中一个新集合的大小为1,这样就相当于每次操作会将所有的数字求和后加入得分里,然后删去其中一个数字。显然每次删最小的数字是最优的,自行yy脑补下这个贪心的正确性吧,我也很难给出详细的证明。

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 310000

using namespace std;

typedef long long int LL;

int n;
LL a[MAXN],sum[MAXN],ans=0;

int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)
        scanf("%I64d",&a[i]);
    sort(a+1,a+n+1);
    for(int i=1;i<=n;i++)
        sum[i]=sum[i-1]+a[i];
    ans+=sum[n];
    for(int i=2;i<=n;i++)
        ans+=a[i-1]+sum[n]-sum[i-1];
    printf("%I64d\n",ans);
    return 0;
}

B. Appleman and Tree

题目链接

http://codeforces.com/contest/461/problem/B

题目大意

给你一个树,树上的点要么是白点要么是黑点,删去 k 条树边后,树就变成了一个包含k+1个树的森林。问有多少种删边方案,使得最终森林里每个树上都只有一个黑点

思路

树上DP

f[i][0]= 在删边后的森林里,点 i 所在的树里,子树i
里不包含黑点的方案数。设 f[i][1]= 在删边后的森林里,点 i 所在的树里,子树i只包含一个黑点的方案数。

假设对于点 u ,我们已经确定了它的所有的儿子v f[v][0],f[v][1] 的值,我们按照顺序枚举儿子 v ,分情况进行讨论:
1、点u为黑点
显然 f[u][0] 永远为0, f[u][1]=vf[v][0]f[v][1] (子树 v 里全是白点的话,必须与u连,不连就不合法了,子树 v 包含一个黑点的话,必须与u不连)
2、点 u 为白点
f[u][0]=vf[v][0]f[v][1](子树 v 里全是白点的话,必须与uu连,不连的话就有一个树没有黑点,不合法了;子树 v 包含一个黑点的话,必须与u不连,连的话就会有一个树包含两个黑点,同样不合法)

这个情况下, f[u][1] 比较特殊些,因为 f[u][1] 的方案里,必须是与 u 相连的儿子v中,只有一个是黑点。考虑按顺序枚举 v ,对于每个v

f[u][1]=f[u][1](f[v][0]+f[v][1])+f[u][0]f[v][1]

当前已经枚举了前面的儿子是否和点 u 相连,因此有三种决策:1、子树u尚不包含黑点,点 u 在这时和包含黑点的子树v相连;2、子树 u 已经包含黑点,点u在这时只能和不包含黑点的子树 v 相连;3、子树u尚不包含黑点,点 u 在这时仍然和不包含黑点的子树v相连。

要想在枚举了之前的儿子和当前的儿子 v 之后,子树u包含一个黑点,有两种选择:
1、如果在之前枚举的 v 里,已经让子树u包含有黑点1的话,那么只有让点 u 和全为白点的子树v相连,或者和保护黑点的子树 v 不连接。
2、如果在之前枚举的v里,子树 u 没有包含黑点1的话,那么只有让点u和包含一个黑点的子树 v 相连。
对这两种情况,我们用加法计数和乘法计数来统计方案数即可。

虽然这个DP思路有点绕,不过大家仔细研究下DP方程还是能搞明白的

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 110000
#define MOD 1000000007

using namespace std;

typedef long long int LL;

struct edge
{
    int u,v,next;
}edges[MAXN*2];

int head[MAXN],nCount=0;

void AddEdge(int U,int V)
{
    edges[++nCount].u=U;
    edges[nCount].v=V;
    edges[nCount].next=head[U];
    head[U]=nCount;
}

int color[MAXN],n;
LL f[MAXN][2];

void DFS(int u,int fa)
{
    for(int p=head[u];p!=-1;p=edges[p].next)
    {
        int v=edges[p].v;
        if(v==fa) continue;
        DFS(v,u);
    }
    if(color[u]) //u为黑点
    {
        f[u][0]=0; //!!!!!
        f[u][1]=1;
        for(int p=head[u];p!=-1;p=edges[p].next)
        {
            int v=edges[p].v;
            if(v==fa) continue;
            f[u][1]=(f[u][1]*(f[v][0]+f[v][1]))%MOD;
        }
    }
    else
    {
        f[u][0]=1;
        f[u][1]=0;
        for(int p=head[u];p!=-1;p=edges[p].next)
        {
            int v=edges[p].v;
            if(v==fa) continue;
            f[u][1]=(f[u][1]*(f[v][0]+f[v][1])%MOD+f[u][0]*f[v][1]%MOD)%MOD; //!!!!
            f[u][0]=(f[u][0]*((f[v][0]+f[v][1])%MOD))%MOD; //v子树都是是白点可以选择连或者不连
        }
    }
}

int main()
{
    memset(head,-1,sizeof(head));
    scanf("%d",&n);
    for(int i=2;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        x++;
        AddEdge(x,i);
        AddEdge(i,x);
    }
    for(int i=1;i<=n;i++) scanf("%d",&color[i]);
    DFS(1,-1);
    printf("%I64d\n",(f[1][1])%MOD);
    return 0;
}

C. Appleman and a Sheet of Paper

题目链接

http://codeforces.com/contest/461/problem/C

题目大意

这是简化后的题目大意,部分细节和输入数据不同,需要注意
给你n个数,这些数字初始为1,分别放在 [1,n] ,每次有两种操作:1、让区间 [L,L+l1] 部分的数字依次加入到 [L+l,L+2l1] ,这个数字序列的有效部分变成 [L+l,max{L+2l1,R}]([L,R]) ;2、询问某个区间里的数字和

思路

我们可以用树状数组维护,每次执行操作1时,就 l 次在树状数组里做单点增加数值的操作,操作1复杂度为O(qllogn),对于操作2,就是树状数组区间求和了,操作2复杂度为 O(qlogn)

但是有个问题,假设当前数字序列的有效区间为 [L,R] ,若 l>RL+12 ,直观的讲就是左边叠过去的长度比右边被覆盖的部分更长。这样的情况可能会炸掉内存。为了保证每次有效区间永远在 [1,n] 之中,我们可以打一个翻转标记 rev ,若出现上述的情况时,就相当于是右边往左边叠过去,此时整个序列翻转了一遍,rev^=1

这个题特判和细节真的非常多,虽然口头讲下还是很简单的,不过想一遍写对还是非常难的

代码

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>

#define MAXN 110000
#define MOD 1000000007

using namespace std;

typedef long long int LL;

struct edge
{
    int u,v,next;
}edges[MAXN*2];

int head[MAXN],nCount=0;

void AddEdge(int U,int V)
{
    edges[++nCount].u=U;
    edges[nCount].v=V;
    edges[nCount].next=head[U];
    head[U]=nCount;
}

int color[MAXN],n;
LL f[MAXN][2];

void DFS(int u,int fa)
{
    for(int p=head[u];p!=-1;p=edges[p].next)
    {
        int v=edges[p].v;
        if(v==fa) continue;
        DFS(v,u);
    }
    if(color[u]) //u为黑点
    {
        f[u][0]=0; //!!!!!
        f[u][1]=1;
        for(int p=head[u];p!=-1;p=edges[p].next)
        {
            int v=edges[p].v;
            if(v==fa) continue;
            f[u][1]=(f[u][1]*(f[v][0]+f[v][1]))%MOD;
        }
    }
    else
    {
        f[u][0]=1;
        f[u][1]=0;
        for(int p=head[u];p!=-1;p=edges[p].next)
        {
            int v=edges[p].v;
            if(v==fa) continue;
            f[u][1]=(f[u][1]*(f[v][0]+f[v][1])%MOD+f[u][0]*f[v][1]%MOD)%MOD; //!!!!
            f[u][0]=(f[u][0]*((f[v][0]+f[v][1])%MOD))%MOD; //v子树都是是白点可以选择连或者不连
        }
    }
}

int main()
{
    memset(head,-1,sizeof(head));
    scanf("%d",&n);
    for(int i=2;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        x++;
        AddEdge(x,i);
        AddEdge(i,x);
    }
    for(int i=1;i<=n;i++) scanf("%d",&color[i]);
    DFS(1,-1);
    printf("%I64d\n",(f[1][1])%MOD);
    return 0;
}
### 关于 Codeforces Round 839 Div 3 的题目与解答 #### 题目概述 Codeforces Round 839 Div 3 是一场面向不同编程水平参赛者的竞赛活动。这类比赛通常包含多个难度层次分明的问题,旨在测试选手的基础算法知识以及解决问题的能力。 对于特定的比赛问题及其解决方案,虽然没有直接提及 Codeforces Round 839 Div 3 的具体细节[^1],但是可以根据以往类似的赛事结构来推测该轮次可能涉及的内容类型: - **输入处理**:给定一组参数作为输入条件,这些参数定义了待解决的任务范围。 - **逻辑实现**:基于输入构建满足一定约束条件的结果集。 - **输出格式化**:按照指定的方式呈现最终答案。 考虑到提供的参考资料中提到的其他几场赛事的信息[^2][^3],可以推断出 Codeforces 圆桌会议的一般模式是围绕着组合数学、图论、动态规划等领域展开挑战性的编程任务。 #### 示例解析 以一个假设的例子说明如何应对此类竞赛中的一个问题。假设有如下描述的一个简单排列生成问题: > 对于每一个测试案例,输出一个符合条件的排列——即一系列数字组成的集合。如果有多种可行方案,则任选其一给出即可。 针对上述要求的一种潜在解法可能是通过随机打乱顺序的方式来获得不同的合法排列形式之一。下面是一个 Python 实现示例: ```python import random def generate_permutation(n, m, k): # 创建初始序列 sequence = list(range(1, n + 1)) # 执行洗牌操作得到新的排列 random.shuffle(sequence) return " ".join(map(str, sequence[:k])) # 测试函数调用 print(generate_permutation(5, 2, 5)) # 输出类似于 "4 1 5 2 3" ``` 此代码片段展示了怎样创建并返回一个长度为 `k` 的随机整数列表,其中元素取自 `[1..n]` 这个区间内,并且保证所有成员都是唯一的。需要注意的是,在实际比赛中应当仔细阅读官方文档所提供的精确规格说明,因为这里仅提供了一个简化版的方法用于解释概念。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值