[大山中学模拟赛] 2016.9.17

这一场比上一场还吃屎 只back回三道题 第四题表示被吓尿不会back

Sonya and Problem Wihtout a Legend

第一题就出了codeforce #371 div1 的第三题我去 比赛的时候一看到这种题压根就是跳 肯定是什么想法题 但是的话这道题的想法好像后来听题解没这么重

就是把严格上升改为不上升 然后的话这些数记录下来 因为以后不管哪个数变都会变成这些数 理由很简单 在中间的不用变 在上面的变下来 在下面的变上去 不用变多不用变少

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#define Maxn 3010
using namespace std;
typedef long long LL;
LL A[Maxn]; LL N; LL B[Maxn];
LL F[Maxn][Maxn];
int main()
{
  //freopen("a.in","r",stdin);
  //freopen("a.out","w",stdout);
  scanf("%I64d",&N); for(LL i=1;i<=N;i++) scanf("%I64d",&A[i]),A[i]-=i,B[i]=A[i];
  sort(B+1,B+N+1); LL len=unique(B+1,B+N+1)-(B+1); memset(F,63,sizeof(F));
  for(LL i=1;i<=len;i++) F[1][i]=abs(A[1]-B[i]),F[1][i]=min(F[1][i],F[1][i-1]);
  for(LL i=2;i<=N;i++)
  {
    for(LL j=1;j<=len;j++)
    {
      F[i][j]=min(F[i][j],F[i-1][j]+abs(A[i]-B[j]));
      F[i][j]=min(F[i][j],F[i][j-1]);
    }
  }
  return printf("%I64d
",F[N][len]),0;
}
/*
7
2 1 5 11 5 9 11
*/
View Code

救灾

第二题是contest hunter上CH round #50的第三题 这里顺便说一句 ljm的比赛来源好广啊 被虐的好惨啊

这道题感觉很厉害 但听完题解后感觉题解的tree dp有点玄学

F1[x][i] 表示这个点的管辖者在子树下面且距离到i

F2[x][i] 表示这个点的管辖者在子树上面且距离到i

G[x]=min(F1[x][]);

然后转移就是:

F1[x][i]=min(F1[son[x]][i-1]+C[i]+sigma(G[otherson[x]],F2[otherson[x]][i+1]))

什么意思呢 就是因为是下面管我的 所以我要找一个孩子使得它子树下面管我 然后其他孩子要不是下面管 要不是上面管,反正我只要这个子树的花费

F2[x][i]=C[i]+sigma(min(F2[son[x]][i+1],G[son[x]])

这个的意思是现在我是上面管的 只要加上我现在的花费 然后下面子树的花费也传上来就好了

但是有个问题就是 如果F1[x][i]是在下面有i管 但是实际上是由另外一个管怎么办呢 其实没关系 因为另外一个管的自然会有一个更好的状态

然后还要优化一下F1的来做 自己建的时候F1就直接把下面的传上来就好了 加上费用 F2也是如此

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#define Maxn 200010
using namespace std;
const int inf=1e9;
int N,C[Maxn];
 
struct node{int x,y,next;}edge[Maxn*2]; int len,first[Maxn];
void ins(int x,int y){len++; edge[len].x=x; edge[len].y=y; edge[len].next=first[x]; first[x]=len;}

int prime[Maxn],pri,phi[Maxn]; bool v[Maxn];
void PHI()
{
  memset(v,1,sizeof(v)); v[0]=v[1]=0; pri=0;
  for(int i=2;i<=N;i++)
  {
    if(v[i]){prime[++pri]=i; phi[i]=i-1;}
    for(int j=1;(j<=pri)&&(prime[j]*i<=N);j++)
    {
      v[i*prime[j]]=0;
      if(i%prime[j]) phi[i*prime[j]]=phi[i]*phi[prime[j]];
      else{phi[i*prime[j]]=phi[i]*prime[j]; break;}
    }
  }
}

int T[Maxn][25]; int F1[Maxn][25]; int F2[Maxn][25]; int G[Maxn]; int sum[Maxn];
void Dfs(int x,int fa)
{
  if(first[x]==-1)
  {
    F1[x][0]=F2[x][0]=G[x]=C[0];
    for(int i=1;i<=19;i++) F2[x][i]=C[i];
    return ;
  }
  for(int k=first[x];k!=-1;k=edge[k].next)
  {
    int y=edge[k].y;
    if(y!=fa) Dfs(y,x);
  }
  for(int i=0;i<=19;i++) sum[i]=0;
  for(int d=0;d<=19;d++)
  {
    for(int k=first[x];k!=-1;k=edge[k].next)
    {
      int y=edge[k].y;
      if(y!=fa) {T[y][d]=min(G[y],F2[y][d+1]); sum[d]+=T[y][d];}
    }
  }
  for(int d=1;d<=19;d++)
  {
    for(int k=first[x];k!=-1;k=edge[k].next)
    {
      int y=edge[k].y;
      if(y!=fa) F1[x][d]=min(F1[x][d],C[d]+F1[y][d-1]+sum[d]-T[y][d]);
    }
  }F1[x][0]=min(F1[x][0],C[0]+sum[0]);
  for(int d=0;d<=19;d++)
  {
    for(int k=first[x];k!=-1;k=edge[k].next)
    {
      int y=edge[k].y;
      if(y!=fa) F2[x][d]=min(F2[x][d],C[d]+sum[d]);
    }
  }
  for(int i=0;i<=19;i++) G[x]=min(G[x],F1[x][i]);
}
int main()
{
  scanf("%d%d",&N,&C[0]);
  for(int i=1;i<N;i++) scanf("%d",&C[i]);
  
  PHI(); len=0; memset(first,-1,sizeof(first));
  for(int i=2;i<=N;i++) ins(phi[i],i);
  
  memset(F1,63,sizeof(F1)); memset(F2,63,sizeof(F2)); memset(G,63,sizeof(G)); memset(T,63,sizeof(T));
  Dfs(1,0);
  return printf("%d
",G[1]),0;
}

/*
3 3
1 2
*/
View Code

 Express

这道题贼6 bzoj三倍经验

首先我们考虑选一个点的情况 肯定要选重心 而重心就是所有子节点的子树到达重心的最大的花费最小(到后面可以简单的证明一下)

然后的话我们考虑两个点的情况 两个点他们所管辖的区域一定是一个联通块 这个很显然

那么两个联通块之间 有且只有一条边相连 所以的话我们枚举每一条边 在两个端点的左右边分别找重心

当然树这么大怎么找 我们其实可以知道 重心只会在重链上 与层数有关

对于还要乘于个距离 我们先求出以1为根的所有乘上距离的花费ans(怎么做很简单吧)

然后我们考虑砍掉一条边 当然ans要减去砍完这条边失去了多少费用

然后目前的费用就是砍完后两边树的费用 分别以y和1为根的树

找重心我以1为根的树为例 sum[i]为i下面所有的权值和 (没乘花费)

那么的话转移就是 x->y ans-=sum[y]; 表示y子树下面的距离都要减1 然后ans+=sum[1]-sum[y] 表示y上面的子树的距离都加1

然后一切就说通了 合起来就是ans+=sum[1]-2*sum[y] 所以的话最重的孩子sum[y](最重的孩子也就是sum最大的)如果大于的话sum[1]才能转移 不然的话就比最少花费要多

所以整一棵树我们不用扫一遍然后来维护所用权值乘上边权 只要维护权值就好了 乘上的边权在转移树的重心的时候搞一下就好了

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdlib>
#include<cmath>
#include<climits>
#define Maxn 50010
using namespace std;
typedef long long LL;
struct node
{
  LL x,y,next;
}edge[Maxn*2]; LL len,first[Maxn];
void ins(LL x,LL y){len++; edge[len].x=x; edge[len].y=y; edge[len].next=first[x]; first[x]=len;}

LL N; LL son1[Maxn],son2[Maxn]; LL size[Maxn]; LL Val[Maxn]; LL dep[Maxn]; LL fa[Maxn];
void Dfs(LL x,LL f)
{
  size[x]+=Val[x];
  for(LL k=first[x];k!=-1;k=edge[k].next)
  {
    LL y=edge[k].y;
    if(y!=f)
    {
      dep[y]=dep[x]+1; fa[y]=x;
      Dfs(y,x); size[x]+=size[y];
      if(size[son1[x]]<size[y]) son2[x]=son1[x],son1[x]=y;
      else if(size[son2[x]]<size[y]) son2[x]=y;
    }
  }
}
int main()
{
  len=0; memset(first,-1,sizeof(first));
  scanf("%lld",&N); for(LL i=1;i<N;i++){LL x,y; scanf("%lld%lld",&x,&y); ins(x,y); ins(y,x);}
  for(LL i=1;i<=N;i++) scanf("%lld",&Val[i]);
  Dfs(1,0); LL S=0; for(LL i=2;i<=N;i++) S+=size[i]; LL Minx=LLONG_MAX;
  for(LL k=1;k<=len;k+=2)
  {
    LL x=edge[k].x; LL y=edge[k].y;
    if(dep[x]<dep[y]) swap(x,y);
    
    LL sum=S; LL u; LL v;
    for(u=fa[x];u;u=fa[u]) size[u]-=size[x],sum-=size[x];
    
    u=1; 
    while(1)
    {
      if(size[son1[u]]>size[son2[u]]&&son1[u]!=x) v=son1[u];
      else v=son2[u];
      if(size[v]*2>size[1]){sum+=size[1]-2*size[v]; u=v;}
      else break;
    }
    
    u=x;
    while(1)
    {
      v=son1[u];
      if(size[v]*2>size[x]){sum+=size[x]-2*size[v]; u=v;}
      else break;
    }
    
    Minx=min(Minx,sum);
    for(u=fa[x];u;u=fa[u]) size[u]+=size[x];
  }
  return printf("%lld
",Minx),0;
}
/*
5
1 2
1 3
3 4
3 5
5 7 6 5 4
N<=50000 W<=100
*/
View Code
原文地址:https://www.cnblogs.com/wohenshuai/p/5883778.html