UZASTOPNI
建议管理大大评紫。理由:巧妙的树形DP。
题意是说给定一棵有根树,需要从中选出一些节点符合:
-
每个选中节点的父亲也被选中
-
任意选中节点在原树对应的子树内所有选中的节点点权连续。
-
选中的所有节点不能有权值相同的。
求方案数,其中两个方案不同当且仅当它们选出的点的点权集合不同。
由于点权的值域不大,可以考虑以值域为下标。用 \(f[x][l][r]\) 来代表子树 \(x\) 选出值域完全覆盖 \([l,r]\) 是否可行,然后发现似乎开不下。想到子树 \(x\) 一定会选出点权为 \(a_x\) 的点,可以把上述状态转移成 \(f[x][l][a_x]\) 和 \(f[x][a_x][r]\) ,去掉无用状态就是 \(f[x][l]\) 和 \(f[x][r]\)。
考虑如何转移。由于数据范围很小可以直接枚举区间断点,然后看分割出来的两个区间是否都是可行的。
有一些需要注意的细节。假如不对孩子加以排序,第二个样例都无法通过,原因是假如当前的 \(a_i=1\) ,有一个孩子是 \(2\) ,另一个是 \(3\) 。假如先用后面的孩子去更新当前结点的答案,那么显然是无法更新的,毕竟 \([1,2]\) 和 \([1,3]\) 都无法用那个孩子凑成。但事实上假如先用前面的孩子去更新就不会出现错误,得出结论应当先把所有孩子按和当前结点点权差的绝对值从小到大排序。由于卡空间要用 vetor 。
统计答案直接看树根的值两边各有多少可能的选择,乘起来即可。
还有一些细节,具体看代码。
#include<bits/stdc++.h>
//#define feyn
const int N=10010;
const int M=110;
using namespace std;
inline void read(int &wh){
wh=0;int f=1;char w=getchar();
while(w<'0'||w>'9'){if(w=='-')f=-1;w=getchar();}
while(w>='0'&&w<='9'){wh=wh*10+w-'0';w=getchar();}
wh*=f;return;
}
struct edge{
int t,next;
}e[N];
int head[N],esum;
inline void add(int fr,int to){
e[++esum]=(edge){to,head[fr]};head[fr]=esum;
}
int m,a[N];
bool f[N][M],g[M];
inline bool ok(int wh,int l,int r){//子树wh是否可以选出区间[l,r]
if(a[wh]<l||a[wh]>r)return false;//根节点是一定会选的,如果根节点不在区间内必然不合法
return f[wh][l]&&f[wh][r]; //返回左右两边是否都可行
}
int now;
inline int ff(int wh){return wh<0?-wh:wh;}
inline bool cmp(int s1,int s2){
return ff(a[s1]-a[now])<ff(a[s2]-a[now]);//按差的绝对值排序
}
void solve(int wh){
f[wh][a[wh]]=true;
vector<int>son;
for(int i=head[wh],th;i;i=e[i].next){
solve(th=e[i].t);son.push_back(th);//记录所有孩子
}
now=wh;sort(son.begin(),son.end(),cmp);//排序
for(int i=0;i<son.size();i++){
int th=son[i];
for(int j=1;j<=100;j++){
g[j]=f[wh][j];if(j==a[wh])continue;
for(int k=min(j,a[wh])+1;k<=max(j,a[wh]);k++){
if(j<a[wh])g[j]=g[j]||(f[wh][k]&&ok(th,j,k-1));
//区间[j,a[wh]]被分成了[j,k-1]和[k,a[wh]]
else g[j]=g[j]||(f[wh][k-1]&&ok(th,k,j));
//区间[a[wh],j]被分成了[a[wh],k-1]和[k,j]
}
}
for(int j=1;j<=100;j++)f[wh][j]=g[j];//用辅助数组中的值更新
}
}
signed main(){
#ifdef feyn
freopen("in.txt","r",stdin);
#endif
read(m);int s1,s2;
for(int i=1;i<=m;i++)read(a[i]);//读入点权
for(int i=1;i<m;i++){
read(s1);read(s2);add(s1,s2);//加边
}
solve(1);//递归进行树形DP
int x=1,y=1;
for(int i=1;i<a[1];i++)x+=f[1][i];
for(int i=a[1]+1;i<=100;i++)y+=f[1][i];
printf("%d\n",x*y);
return 0;
}
一如既往,万事胜意