Link
题目描述
给一棵树,你可以匹配有边相连的两个点,问你这棵树的最大匹配是多少,并且计算出有多少种最大匹配。
解法
容易想到将边的匹配转换到点上面,用 (dp_{u,0/1}) 表示以节点 (u) 为根节点的子树中,不选/选 (u) 节点来匹配得到的最大匹配数。
那么当 (u) 没有被选时,子节点选不选都无所谓
我们令
则有
若选择 (u),则只有被选的那条边的那个子节点必不能选,其他节点选不选无所谓,则
第一问这样做已经可以了,难点在于第二问——记录方案。
由第一问,我们得到启发,用 (g_{u,0/1}) 表示以节点 (u) 为根节点的子树中,不选/选 (u) 节点来匹配得到的最大匹配数的方案数。
若不选择 (u) 节点,则由乘法原理将所有的子节点对应的方案的方案数乘起来即可
令
则
而对于 (g_{u,1}) 我们需要在更新 (dp_{u,1}) 的时候同时更新它。假设我们当前选的边为 (u o v) ,那么除了 (v) 之外的其它子节点都可以任意选,而不选 (v) 节点对应的方案数为 (g_{v,0}),那么总的方案数为 (g_{u,0}/h_v imes g_{v,0})。若 (dp_{u,1}<dp_{u,0}-f_v+dp_{v,0}+1),即
我们找到了一个更大的匹配,那么直接将 (g_{u,1}) 更新为 (g_{u,0}/h_v imes g_{v,0});若 (dp_{u,1}=dp_{u,0}-f_v+dp_{v,0}+1),即我们找到了一个和最大匹配方案数相同的方案,那么将 (g_{u,1}) 累加上这个值即可。
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
ll ret=dp[u][0]-f[v]+dp[v][0]+1;
ll tmp=g[u][0]/h[v]*g[v][0];
if(dp[u][1]<ret) dp[u][1]=ret,g[u][1]=tmp;
else if(dp[u][1]==ret) g[u][1]+=tmp;
}
Tips
我们发现最后的方案数超过了 (long long),需要些高精。然而方程中带了除法QAQ,此时需要一些转换。观察方程,发现所求值是一个总积除上中间一个数的形式,实际上可以把其转换为一个前缀积和一个后缀积相乘,完美的避开了除法。(如果你不想再重载减法的话,也可以把其转换为前缀和加后缀和)
#include<stdio.h>
#include<string.h>
#include<vector>
using namespace std;
#define N 1007
inline int read(){
int x=0,flag=1; char c=getchar();
while(c<'0'||c>'9'){if(c=='-')flag=0;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();}
return flag? x:-x;
}
inline int max(int x,int y){return x>y? x:y;}
struct BigNum{
int n,s[207];
BigNum(int x=0){
n=0;
memset(s,0,sizeof(s));
while(x) s[++n]=x%10,x/=10;
}
BigNum operator *(const BigNum B){
BigNum c;
c.n=B.n+n-1;
for(int i=1;i<=n;i++)
for(int j=1;j<=B.n;j++){
c.s[i+j-1]+=s[i]*B.s[j];
c.s[i+j]+=c.s[i+j-1]/10;
c.s[i+j-1]%=10;
}
while(c.s[c.n+1]) c.n++;
while(c.n>=1&&(!c.s[c.n])) c.n--;
return c;
}
BigNum operator +(const BigNum B){
BigNum c;
c.n=max(B.n,n);
for(int i=1;i<=c.n;i++){
c.s[i]+=s[i]+B.s[i];
c.s[i+1]=c.s[i]/10;
c.s[i]%=10;
}
while(c.s[c.n+1]) c.n++;
while(c.n>=1&&(!c.s[c.n])) c.n--;
return c;
}
bool operator <(const BigNum B){
if(n!=B.n) return n<B.n;
for(int i=n;i>=1;i--)
if(s[i]!=B.s[i]) return s[i]<B.s[i];
return 0;
}
bool operator ==(BigNum B){
return !(((*this)<B)|(B<(*this)));
}
void print(){
if(!n) putchar('0');
else for(int i=n;i>=1;i--) putchar(s[i]+'0');
putchar('
');
}
}f[N],g[N][2],h[N],dp[N][2],pre[N],suf[N],pres[N],sufs[N];
int n;
bool vis[N];
vector<int> G[N];
void dfs(int u){
vis[u]=1,g[u][0]=BigNum(1);
for(int i=0;i<G[u].size();i++){
int v=G[u][i];
if(!vis[v]) dfs(v);
dp[u][0]=dp[u][0]+f[v];
g[u][0]=g[u][0]*h[v];
}
pre[0]=BigNum(1);
pres[0]=BigNum(0);
for(int i=0;i<G[u].size();i++)
pres[i+1]=pres[i]+f[G[u][i]],
pre[i+1]=pre[i]*h[G[u][i]];
suf[G[u].size()+1]=BigNum(1);
sufs[G[u].size()+1]=BigNum(0);
for(int i=G[u].size()-1;~i;i--)
sufs[i+1]=sufs[i+2]+f[G[u][i]],
suf[i+1]=suf[i+2]*h[G[u][i]];
for(int i=0;i<G[u].size();i++){
int v=G[u][i];
BigNum r(1);
BigNum ret=pres[i]+sufs[i+2]+dp[v][0]+r;
BigNum tmp=pre[i]*suf[i+2]*g[v][0];
if(dp[u][1]<ret) dp[u][1]=ret,g[u][1]=tmp;
else if(dp[u][1]==ret) g[u][1]=g[u][1]+tmp;
}
if(dp[u][0]<dp[u][1]) f[u]=dp[u][1],h[u]=g[u][1];
else if(dp[u][0]==dp[u][1]) f[u]=dp[u][0],h[u]=g[u][0]+g[u][1];
else f[u]=dp[u][0],h[u]=g[u][0];
}
signed main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
n=read();
for(int i=1;i<=n;i++){
int u=read(),op=read();
for(int i=1;i<=op;i++)
G[u].push_back(read());
}
BigNum ans(0),ret(1);
for(int i=1;i<=n;i++){
if(!vis[i]) dfs(i);
if(ans<f[i]) ans=f[i],ret=h[i];
}
ans.print(),ret.print();
}