noip 模拟赛 老司机的彩虹桥

老司机的彩虹桥

rainbow.cpp/.c/.pas

题目描述:

自从老司机有了好多好多小司姬之后,老司机就造了一个好大好大的房子;

因为老司机非常的6,这个房子不在地上而在天上!

我们可以将这个房子抽象成n片云朵和n-1条彩虹,每一条彩虹上都住着一个小司姬,当然了,所有云朵是由这些彩虹连通的树哦;

现在老司机想去探望所有老司机的小司姬,但是麻烦的是,他并不能进到小司姬的房间里——也就是不能通过彩虹桥来移动;

所以每次他只能到一个云朵上,然后探望和那个云朵相连的彩虹桥上的小司姬;

老司机想知道,他最少要去多少个云朵上才能探望所有的小司姬;

老司机还想知道,有多少个云朵是在所有的最优方案中他都不会上去的;

老司机还想知道,他究竟有多少种最优方案来探望小司姬们呢?

如果你的回答让老司机满意的话,他也许会邀请你去看小司姬哦;

输入(rainbow.in)

第一行一个整数n,代表有n个云朵;

然后n-1行,每行有两个整数x,y;

表示编号为x与y的云朵之间有一个彩虹桥;

输出(rainbow.out)

输出有三行,每行有一个数字;

第一行:老司机最少到多少个云朵上去探望小司姬;

第二行:有多少个云朵老司机在最优方案中不会上去;

第三行:老司机有多少种最优方案来探望小司姬们;

注意到第三问的方案数可能很大,所以取模5462617输出,前两问不取模

 

rainbow0.in

rainbow0.out

5

1 2

1 3

2 4

2 5

2

2

2

 

数据范围:

30%的数据满足n<=1000;

100%的数据满足n<=100000;

评分标准:

本题分三个问题给分;

第一问每个测试点4,第二问每个测试点3,第三问每个测试点3

相应位置的数字正确会获得相应得分;

如果输出不足或超过三个数字则不给分;

题解:

这题问好多啊...

第一问很简单,记dp[x][2]表示x点选或不选的两种状态(0表示不选,1表示选)中最小的选点数,那么我们有:

dp[x][0]=∑dp[to][1];

dp[x][1]=∑min(dp[to][1],dp[to][0]);

证明:记x点字节点为to,那么如果x选用,则与他相连的所有子节点可用可不用,我们加进去就好

如果x不选用,那么与他相连的所有子节点都必须选用,否则不合法,因为这两点之间的边未被覆盖,所以必须加上dp[to][1]

那么我们顺便就更新第三问:

记f[x][2]表示x点选或不选两种状态中总的方案数,那么我们有:

f[x][0]=∏f[to][1];

f[x][1]=∏f[to][0](或f[to][1],与dp大小有关,取相应dp值中小者所对应的值)

这也很好YY,一个节点的方案数就是他所有子节点方案数的乘积,按对应位置转移即可。

但是注意一点,如果一个to的两种dp值相等,那么就说明这个to两个dp值取哪个对更新dp[x][1]无影响,无影响的时候总方案数f[x][1]就要乘上(f[to][1]+f[to][0]),即两种状态的方案数之和。

最后按dp[1]的状态取对应的f[1]状态即可,同样特判dp[1][0]=dp[1][1]的情况

最难的是第二问:

第二问的思路大体如下:

首先我们有一个思想,就是我们可以以每个节点作为根节点求出这个点必选的最优情况,看看是否能达到全局最优解。

然而这样做的时间复杂度是O(n^2),显然会TLE,所以我们需要一些O(n)的方法,那么我们考虑是否能通过两次dfs解决这个问题。

第一次dfs是求出第一问和第三问的答案,我们主要要应用第一问的答案。

第二次,我们产生这样一种思路:既然我们不能以每个节点为根跑一遍树形dp,那么我们就对每一个点假设他是根,那么我们就需要一个状态F[x][2],表示x节点的上半部分树在x的不同状态下所需的最小代价,那么x节点必选的的代价就是F[x][1]+f[x][1](即x的上半部分+x的下半部分(即子树))

于是我们有:

F[x][0]=F[fa][1]+f[fa][1]-min(f[x][0],f[x][1])

F[x][1]=min(F[x][0],F[fa][0]+f[fa][0]-f[x][1])

证明:对一个点,如果这个点不选,那么这个点的父节点就必须选(F[fa][1]+f[fa][1]),但是这个点的子节点部分是无需统计的(显然并不在上半部分),所以我们减去用来更新f[fx][1]的一个状态,即min(f[x][0],f[x][1])

如果这个点选,那么这个点的父节点可以选,那就是F[x][0]的状态,而如果这个点的父节点不选,那么就是F[fa][0]+f[fa][0],但是要减掉这个点被选后这棵子树的状态f[x][1]

最后比较F[x][1]+f[x][1]与全局最优解ans的大小即可

#include <cstdio>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <stack>
#include <ctime>
#define mode 5462617
#define ll long long
using namespace std;
struct Edge
{
  int next;
  int to;
}edge[200005];
int n;
int cnt=1;
int head[100005];
int dp[100005][2];
ll f[100005][2];
ll F[100005][2];
int ans;
int tot=0;
void init()
{
  memset(head,-1,sizeof(head));
  cnt=1;
}
void add(int l,int r)
{
  edge[cnt].next=head[l];
  edge[cnt].to=r;
  head[l]=cnt++;
}
void dfs(int x,int fx)
{
  f[x][0]=f[x][1]=1;
  for(int i=head[x];i!=-1;i=edge[i].next)
    {
      int to=edge[i].to;
      if(to==fx)
    {
      continue;
    }
      dfs(to,x);
      if(dp[to][1]<dp[to][0])
      {
          dp[x][1]+=dp[to][1];
          f[x][1]*=f[to][1];
          f[x][1]%=mode;
      }else if(dp[to][1]>dp[to][0])
      {
          dp[x][1]+=dp[to][0];
          f[x][1]*=f[to][0];
          f[x][1]%=mode;
      }else
      {
          dp[x][1]+=dp[to][0];
          f[x][1]*=(f[to][0]+f[to][1])%mode;
          f[x][1]%=mode;
      }
      dp[x][0]+=dp[to][1];
        f[x][0]*=f[to][1];
        f[x][0]%=mode;
    }
}
void dfs2(int x,int fx)
{
    if(x!=1) 
    {
        F[x][0]=F[fx][1]+dp[fx][1]-min(dp[x][0],dp[x][1]);//x点不选,其以上的树的代价 
        F[x][1]=min(F[x][0],F[fx][0]+dp[fx][0]-dp[x][1]);//x点选,其以上的树的代价 
    }
    if(dp[x][1]+F[x][1]!=ans)
    {
        tot++;//不合法 
    }
    for(int i=head[x];i!=-1;i=edge[i].next)
    {
        int to=edge[i].to;
        if(to==fx)
        {
            continue;
        }
        dfs2(to,x);
    }
}
int main()
{
  freopen("rainbow.in","r",stdin);
  freopen("rainbow.out","w",stdout);
  scanf("%d",&n);
  init();
  for(int i=1;i<n;i++)
    {
      int x,y;
      scanf("%d%d",&x,&y);
      add(x,y);
      add(y,x);
      dp[i][1]=1;
    }
  dp[n][1]=1;
  dfs(1,1);
  if(dp[1][0]<dp[1][1])
  {

    ans=dp[1][0];
    dfs2(1,1);
      printf("%d %d %I64d\n",dp[1][0],tot,f[1][0]);

  }else if(dp[1][0]==dp[1][1])
  {
      ans=dp[1][0];
      dfs2(1,1);
    printf("%d %d %I64d\n",dp[1][0],tot,f[1][1]+f[1][0]);
  }else
  {
      ans=dp[1][1];
      dfs2(1,1);
   printf("%d %d %I64d\n",dp[1][1],tot,f[1][1]);
  }
  return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值