HDU
来源:互联网 发布:淘宝店铺链接在哪复制 编辑:程序博客网 时间:2024/06/07 01:32
传送门
// 一棵树, 上面都有一个点权, 问有多少条路径满足进过的点的类型刚好有k种,
// 首先这个肯定是点分治, 但是难点和POJ那题的不同处就是这个是状态, 那么就不能像POJ那题进行排序选点了, 所以我们应该要换一种比较高效的方法. 首先是k只有10, 那么我们进行状压, 那么我们要求的就是路径上这条路与( | )起来等于( 1<< k) - 1, 那么我们肯定不能n方的找出两条路径来判断是否满足这个条件, 而应该是利用高位前缀和的思想.
因为我们要求的是某一条路径和已知的路径或起来的值, 即是已知路径中的二进制表示中只要为0的地方是1就行了. 所以就用到了高位前缀和, 比如dp[100] = dp[100] + dp[101] + dp[110] + dp[111]; 这样我们就可以直接算出答案从而优化复杂度. 细节请看代码.
AC Code
const int maxn = 5e4+5;int n, cnt, head[maxn], k, vis[maxn], root, maxx, dis[maxn];int num, tot, siz[maxn], mv[maxn];int a[maxn];struct node { int to, w, next;} e[maxn<<1];void add(int u, int v, int w){ e[cnt] = (node){v,w,head[u]}; head[u] = cnt++;}void getroot(int u, int fa){ siz[u] = 1, mv[u] = 0; for (int i = head[u]; ~i; i = e[i].next) { int to = e[i].to; if (to == fa || vis[to]) continue; getroot(to, u); siz[u] += siz[to]; mv[u] = max(mv[u], siz[to]); } mv[u] = max(mv[u], tot - siz[u]); if (mv[u] < mv[root]) root = u;}int dp[1030]; //这个不能开太大,否则会T.void getdis(int u,int fa,int dep){ dp[dep]++; dis[++num] = dep; for (int i = head[u]; ~i; i = e[i].next) { int to = e[i].to; if (to == fa || vis[to]) continue; getdis(to, u, dep | (1 << a[to])); }}ll ans;ll cal(int u,int f){ num = 0; Fill(dp,0); getdis(u,-1,f | (1 << a[u])); for(int i=0;i<k;i++){ for(int j = 0 ; j < (1<<k) ; j++) { if(!((1<<i) & j)) dp[j] += dp[j | (1 << i)]; } } ll res = 0; for(int i=1;i<=num;i++){ int tmp = dis[i]; tmp ^= (1<<k)-1; res += dp[tmp]; } return res;}void work(int u){ vis[u] = 1; ans += cal(u, 1 << a[u]); for (int i = head[u]; ~i; i = e[i].next) { int to = e[i].to; if (vis[to]) continue; ll tmp = cal(to, 1 << a[u]); ans -= tmp; mv[root=0] = tot = siz[to]; getroot(to, -1); work(root); }}void solve(){ while(~scanf("%d%d",&n,&k)){ cnt = 0 ; Fill(head,-1); Fill(vis,0); for(int i=1;i<=n;i++) { scanf("%d",&a[i]); a[i]--; } for (int i = 1; i < n; i++) { int u, v, w; scanf("%d%d",&u,&v); add(u, v, 0); add(v, u, 0); } ans = 0; mv[root=0] = tot = n; getroot(1, -1); work(root); cout << ans << endl; }}