老司机的彩虹桥
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;
}