2017多校第10场 HDU 6178 Monkeys 贪心,或者DP
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6178
题意:给出一棵有n个节点的树,现在需要你把k只猴子放在节点上,每个节点最多放一只猴子,且要求每只猴子必有一只另外的猴子通过一条边与它相连,问最少用多少条边能达到这个要求。
解法:利用贪心的思维,显然我们应该先选择性价比最高的,即一条边连接两个点的情况。计算出这样的边的条数ans,如果ans*2>=k,结果就是(k+1)/2,否则剩下来没有安排的猴子每一只需要多一条边,即结果为ans+k-2 * ans。我的方法是用类拓扑排序的方法,找连接两个度数为1且没用被用过的点的边数,每次找到一条边后注意更新度数。
#include <bits/stdc++.h> using namespace std; typedef long long LL; struct FastIO { static const int S = 1310720; int wpos; char wbuf[S]; FastIO() : wpos(0) {} inline int xchar() { static char buf[S]; static int len = 0, pos = 0; if(pos == len) pos = 0, len = fread(buf, 1, S, stdin); if(pos == len) exit(0); return buf[pos ++]; } inline unsigned long long xuint() { int c = xchar(); unsigned long long x = 0; while(c <= 32) c = xchar(); for(; '0' <= c && c <= '9'; c = xchar()) x = x * 10 + c - '0'; return x; } inline long long xint() { long long s = 1; int c = xchar(), x = 0; while(c <= 32) c = xchar(); if(c == '-') s = -1, c = xchar(); for(; '0' <= c && c <= '9'; c = xchar()) x = x * 10 + c - '0'; return x * s; } inline void xstring(char *s) { int c = xchar(); while(c <= 32) c = xchar(); for(; c > 32; c = xchar()) * s++ = c; *s = 0; } inline double xdouble() { bool sign = 0; char ch = xchar(); double x = 0; while(ch <= 32) ch = xchar(); if(ch == '-') sign = 1, ch = xchar(); for(; '0' <= ch && ch <= '9'; ch = xchar()) x = x * 10 + ch - '0'; if(ch == '.') { double tmp = 1; ch = xchar(); for(; ch >= '0' && ch <= '9'; ch = xchar()) tmp /= 10.0, x += tmp * (ch - '0'); } if(sign) x = -x; return x; } inline void wchar(int x) { if(wpos == S) fwrite(wbuf, 1, S, stdout), wpos = 0; wbuf[wpos ++] = x; } inline void wint(long long x) { if(x < 0) wchar('-'), x = -x; char s[24]; int n = 0; while(x || !n) s[n ++] = '0' + x % 10, x /= 10; while(n--) wchar(s[n]); } inline void wstring(const char *s) { while(*s) wchar(*s++); } inline void wdouble(double x, int y = 6) { static long long mul[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000LL, 100000000000LL, 1000000000000LL, 10000000000000LL, 100000000000000LL, 1000000000000000LL, 10000000000000000LL, 100000000000000000LL}; if(x < -1e-12) wchar('-'), x = -x; x *= mul[y]; long long x1 = (long long) floorl(x); if(x - floor(x) >= 0.5) ++x1; long long x2 = x1 / mul[y], x3 = x1 - x2 * mul[y]; wint(x2); if(y > 0) { wchar('.'); for(size_t i = 1; i < y && x3 * mul[i] < mul[y]; wchar('0'), ++i); wint(x3); } } ~FastIO() { if(wpos) fwrite(wbuf, 1, wpos, stdout), wpos = 0; } } io; const int maxn = 100010; struct edge{ int to,next; }E[maxn*2]; int head[maxn],edgecnt; void init(){ edgecnt=0; memset(head,-1,sizeof(head)); } void add(int u, int v){ E[edgecnt].to=v,E[edgecnt].next=head[u],head[u]=edgecnt++; } int du[maxn]; bool vis[maxn]; int main() { int T,n,k; T = io.xuint(); while(T--) { init(); memset(vis,0,sizeof(vis)); n = io.xuint(); k = io.xuint(); for(int i=2; i<=n; i++){ int x; x = io.xuint(); add(i, x); add(x, i); du[x]++; du[i]++; } queue<int>q; for(int i=1; i<=n; i++){ if(du[i] == 1){ q.push(i); } } int ans = 0; while(!q.empty()) { int u = q.front(); q.pop(); for(int i=head[u]; i+1; i=E[i].next){ int v = E[i].to; if(!vis[u]&&!vis[v]){ vis[u]=1; vis[v]=1; ans++; } du[v]--; if(du[v]==1){ q.push(v); } } } if(ans*2>=k){ printf("%d\n", (k+1)/2); } else{ ans = ans+k-2*ans; printf("%d\n", ans); } } return 0; }
我还在网上看到了另外一种利用树形DP解决这个题的方法,觉得挺妙的,他的方法是这样的。http://blog.csdn.net/my_sunshine26/article/details/77586785
我们尽量要使一个点只匹配一个点,很容易想到二分匹配中的最大匹配数,而最大匹配数即为最小点覆盖数,可用树形DP解决。
我们用dp[i][1]表示选择i这个顶点(初始化dp[root][1]为1)的最小点覆盖数,那么它的儿子节点son[i]可以被选择也可以不被选择。
状态转移方程为 dp[i][1]+=min(dp[son[i]][0],dp[son[i]][1]);
用dp[i][0]表示不选择i这个顶点(初始化dp[root][0]为0)的最小点覆盖数,显然它的儿子节点son[i]就必须被选择了。
状态转移方程为 dp[i][0]+=dp[son[i]][1];
最终求出最小点覆盖数(最大匹配数)即为min(dp[1][0],dp[1][1]),也就求出了ans,然后跟上面的步骤相同。