http://codeforces.com/contest/461/problem/A
给你 n 个数构成的集合 S ,每次操作你可以选择当前的一个集合,将它分裂成两个非空集合,每次操作后,你将每个集合里的数字之和加起来,若出现了大小为1的集合,就将这个集合删去。问你操作的最大得分是多少。
这样的贪心感觉比较多吧,比如NOIP的合并果子等等
显然在这个题目里,我们要让数字大的数字的加的次数尽量多点,我们可以考虑每次将一个集合分裂成两个集合,其中一个新集合的大小为1,这样就相当于每次操作会将所有的数字求和后加入得分里,然后删去其中一个数字。显然每次删最小的数字是最优的,自行yy脑补下这个贪心的正确性吧,我也很难给出详细的证明。
#include
#include
#include
#include
#include
#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;
}
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 里全是白点的话,必须与u u 连,不连的话就有一个树没有黑点,不合法了;子树 v 包含一个黑点的话,必须与 u 不连,连的话就会有一个树包含两个黑点,同样不合法)
这个情况下, f[u][1] 比较特殊些,因为 f[u][1] 的方案里,必须是与 u 相连的儿子 v 中,只有一个是黑点。考虑按顺序枚举 v ,对于每个 v :
要想在枚举了之前的儿子和当前的儿子 v 之后,子树 u 包含一个黑点,有两种选择:
1、如果在之前枚举的 v 里,已经让子树 u 包含有黑点1的话,那么只有让点 u 和全为白点的子树 v 相连,或者和保护黑点的子树 v 不连接。
2、如果在之前枚举的 v 里,子树 u 没有包含黑点1的话,那么只有让点 u 和包含一个黑点的子树 v 相连。
对这两种情况,我们用加法计数和乘法计数来统计方案数即可。
虽然这个DP思路有点绕,不过大家仔细研究下DP方程还是能搞明白的
#include
#include
#include
#include
#include
#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;
}
http://codeforces.com/contest/461/problem/C
这是简化后的题目大意,部分细节和输入数据不同,需要注意
给你 n 个数,这些数字初始为1,分别放在 [1,n] ,每次有两种操作:1、让区间 [L,L+l−1] 部分的数字依次加入到 [L+l,L+2l−1] ,这个数字序列的有效部分变成 [L+l,max{L+2l−1,R}]([L,R]是原来的序列的有效部分) ;2、询问某个区间里的数字和
我们可以用树状数组维护,每次执行操作1时,就 l 次在树状数组里做单点增加数值的操作,操作1复杂度为 O(qllogn) ,对于操作2,就是树状数组区间求和了,操作2复杂度为 O(qlogn)
但是有个问题,假设当前数字序列的有效区间为 [L,R] ,若 l>⌊R−L+12⌋ ,直观的讲就是左边叠过去的长度比右边被覆盖的部分更长。这样的情况可能会炸掉内存。为了保证每次有效区间永远在 [1,n] 之中,我们可以打一个翻转标记 rev ,若出现上述的情况时,就相当于是右边往左边叠过去,此时整个序列翻转了一遍,rev^=1
这个题特判和细节真的非常多,虽然口头讲下还是很简单的,不过想一遍写对还是非常难的
#include
#include
#include
#include
#include
#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;
}