A. Appleman and Toastman
题目链接
http://codeforces.com/contest/461/problem/A
题目大意
给你
n
个数构成的集合
思路
这样的贪心感觉比较多吧,比如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
条树边后,树就变成了一个包含
思路
树上DP
设
f[i][0]=
在删边后的森林里,点
i
所在的树里,子树
里不包含黑点的方案数。设
f[i][1]=
在删边后的森林里,点
i
所在的树里,子树
假设对于点
u
,我们已经确定了它的所有的儿子
1、点
显然
f[u][0]
永远为0,
f[u][1]=∏vf[v][0]∗f[v][1]
(子树
v
里全是白点的话,必须与
2、点
u
为白点
这个情况下,
f[u][1]
比较特殊些,因为
f[u][1]
的方案里,必须是与
u
相连的儿子
当前已经枚举了前面的儿子是否和点 u 相连,因此有三种决策:1、子树
要想在枚举了之前的儿子和当前的儿子
v
之后,子树
1、如果在之前枚举的
v
里,已经让子树
2、如果在之前枚举的
对这两种情况,我们用加法计数和乘法计数来统计方案数即可。
虽然这个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
题目大意
这是简化后的题目大意,部分细节和输入数据不同,需要注意
给你
思路
我们可以用树状数组维护,每次执行操作1时,就
l
次在树状数组里做单点增加数值的操作,操作1复杂度为
但是有个问题,假设当前数字序列的有效区间为 [L,R] ,若 l>⌊R−L+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;
}