Codeforces #263 Div 1 简要题解

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值