题解 P1623 【[CEOI2007]树的匹配Treasury】
xtx1092515503
2020-12-01 21:24:23
个人觉得树上背包的做法更为易懂,并且只需要高精度加法和乘法。
------------
我们令 $f[i,j,0/1]$ 表示 $i$ 的子树中有 $j$ 个匹配,且 $i$ 本身没有/有匹配的方案数。再令 $g[i,0/1]$ 表示没有/有匹配时的最大匹配数。显然,当合并父亲 $x$ 和儿子 $y$ 时,就直接拿 $f[x]$ 和 $f[y]$ 做一下背包即可。
注意,当父亲和儿子均没有匹配时(即 $f[x,?,0]$ 与 $f[y,?,0]$),它可以直接转移到 $f[x,?+?+1,1]$,即在父子间产生一对匹配(当然,也可以选择不用这一对匹配)。其余情况下,以父亲是否有匹配为准。
这里先贴出使用```long long```的 $60$ 分代码:
```cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,g[1010][2];
ll f[1010][510][2],h[1010][2];
//f[i,j,0/1]:the number of situations when there're j matchings in subtree i and i itself isn't/is matched; and g is the maximal matchings. h is an array for assistance.
vector<int>v[1010];
bool occ[1010];
void dfs(int x){
f[x][0][0]=1;
for(auto y:v[x]){
dfs(y);
int tmp0=g[x][0]+max(g[y][0],g[y][1]),tmp1=max(g[x][1]+max(g[y][0],g[y][1]),g[x][0]+g[y][0]+1);
for(int i=0;i<=tmp0;i++)h[i][0]=0;
for(int i=0;i<=tmp1;i++)h[i][1]=0;
for(int i=0;i<=g[x][0];i++)for(int j=0;j<=g[y][0];j++)h[i+j][0]+=f[x][i][0]*f[y][j][0],h[i+j+1][1]+=f[x][i][0]*f[y][j][0];
for(int i=0;i<=g[x][1];i++)for(int j=0;j<=g[y][0];j++)h[i+j][1]+=f[x][i][1]*f[y][j][0];
for(int i=0;i<=g[x][0];i++)for(int j=0;j<=g[y][1];j++)h[i+j][0]+=f[x][i][0]*f[y][j][1];
for(int i=0;i<=g[x][1];i++)for(int j=0;j<=g[y][1];j++)h[i+j][1]+=f[x][i][1]*f[y][j][1];
for(int i=0;i<=tmp0;i++)f[x][i][0]=h[i][0];
for(int i=0;i<=tmp1;i++)f[x][i][1]=h[i][1];
g[x][0]=tmp0;
g[x][1]=tmp1;
}
}
int main(){
scanf("%d",&n);
for(int i=1,x,y,z;i<=n;i++){
scanf("%d%d",&x,&y);
while(y--)scanf("%d",&z),v[x].push_back(z),occ[z]=true;
}
for(int i=1;i<=n;i++){
if(occ[i])continue;
dfs(i);
int mx=max(g[i][0],g[i][1]);
ll ret=0;
if(mx==g[i][0])ret+=f[i][g[i][0]][0];
if(mx==g[i][1])ret+=f[i][g[i][1]][1];
printf("%d\n%lld\n",mx,ret);
}
return 0;
}
```
剩下的部分需要高精度。但明显,上述算法本身复杂度已经达到 $O(n^2)$,假如再套上高精度不大可能过去(根据极不靠谱的估算,高精度最大可能会有 $2^{333}\approx10^{100}$ 的位数,因为还要用乘法,所以就更不靠谱了)。当然,有胆大的可以尝试用FFT优化背包的卷积和高精度的卷积,但 $100,1000$ 这么点大的卷积可能用不用FFT效果都没啥区别。
那怎么办呢?
观察到我们只需要求最大的方案数。这意味着我们只需要记录最大值附近的几个DP值即可——剩余的DP值无论怎么搞都不可能最终成为最大值。于是这样搞正常DP的复杂度就被优化到了 $O(n)$。套上高精度,上限约是 $1000\times 100^2=10^8$ 的。但是很松,所以跑得很快。
代码:
```cpp
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,g[1010][2];
struct Wint:vector<int>{
Wint(){clear();}
Wint(int x){clear();while(x)push_back(x%10),x/=10;}
void operator ~(){
for(int i=0;i+1<size();i++)(*this)[i+1]+=(*this)[i]/10,(*this)[i]%=10;
while(!empty()&&back()>9){
int tmp=back()/10;
back()%=10;
push_back(tmp);
}
while(!empty()&&!back())pop_back();
}
void operator +=(const Wint &x){
// print(),putchar('+'),x.print();
if(size()<x.size())resize(x.size());
for(int i=0;i<x.size();i++)(*this)[i]+=x[i];
~*this;
// putchar('='),print(),putchar('\n');
}
friend Wint operator *(const Wint &x,const Wint &y){
// x.print(),putchar('*'),y.print();
Wint z;
if(!x.size()||!y.size())return z;
z.resize(x.size()+y.size()-1);
for(int i=0;i<x.size();i++)for(int j=0;j<y.size();j++)z[i+j]+=x[i]*y[j];
~z;
// putchar('='),z.print(),putchar('\n');
return z;
}
void print()const{
if(empty()){putchar('0');return;}
for(int i=size()-1;i>=0;i--)putchar('0'+(*this)[i]);
}
}f[1010][510][2],h[1010][2];
//f[i,j,0/1]:the number of situations when there're j matchings in subtree i and i itself isn't/is matched; and g is the maximal matchings. h is an array for assistance.
vector<int>v[1010];
bool occ[1010];
void dfs(int x){
f[x][0][0]=1;
for(auto y:v[x]){
dfs(y);
int tmp0=g[x][0]+max(g[y][0],g[y][1]),tmp1=max(g[x][1]+max(g[y][0],g[y][1]),g[x][0]+g[y][0]+1);
for(int i=0;i<=tmp0;i++)h[i][0]=0;
for(int i=0;i<=tmp1;i++)h[i][1]=0;
for(int i=max(0,g[x][0]-1);i<=g[x][0];i++)for(int j=max(0,g[y][0]-1);j<=g[y][0];j++)h[i+j][0]+=f[x][i][0]*f[y][j][0],h[i+j+1][1]+=f[x][i][0]*f[y][j][0];
for(int i=max(0,g[x][1]-1);i<=g[x][1];i++)for(int j=max(0,g[y][0]-1);j<=g[y][0];j++)h[i+j][1]+=f[x][i][1]*f[y][j][0];
for(int i=max(0,g[x][0]-1);i<=g[x][0];i++)for(int j=max(0,g[y][1]-1);j<=g[y][1];j++)h[i+j][0]+=f[x][i][0]*f[y][j][1];
for(int i=max(0,g[x][1]-1);i<=g[x][1];i++)for(int j=max(0,g[y][1]-1);j<=g[y][1];j++)h[i+j][1]+=f[x][i][1]*f[y][j][1];
for(int i=max(0,tmp0-1);i<=tmp0;i++)f[x][i][0]=h[i][0];
for(int i=max(0,tmp1-1);i<=tmp1;i++)f[x][i][1]=h[i][1];
g[x][0]=tmp0;
g[x][1]=tmp1;
}
}
int main(){
scanf("%d",&n);
for(int i=1,x,y,z;i<=n;i++){
scanf("%d%d",&x,&y);
while(y--)scanf("%d",&z),v[x].push_back(z),occ[z]=true;
}
for(int i=1;i<=n;i++){
if(occ[i])continue;
dfs(i);
int mx=max(g[i][0],g[i][1]);
Wint ret;
if(mx==g[i][0])ret+=f[i][g[i][0]][0];
if(mx==g[i][1])ret+=f[i][g[i][1]][1];
printf("%d\n",mx);
ret.print();
}
return 0;
}
```