Monkeys
Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 153428/153428 K (Java/Others)
Total Submission(s): 1328 Accepted Submission(s): 435
Problem Description
There is a tree having N vertices. In the tree there are K monkeys (K <= N). A vertex can be occupied by at most one monkey. They want to remove some edges and leave minimum edges, but each monkey must be connected to at least one other monkey through the remaining edges.
Print the minimum possible number of remaining edges.
Print the minimum possible number of remaining edges.
Input
The first line contains an integer T (1 <= T <= 100), the number of test cases.
Each test case begins with a line containing two integers N and K (2 <= K <= N <= 100000). The second line contains N-1 space-separated integers a1,a2,…,aN−1, it means that there is an edge between vertex ai and vertex i+1 (1 <= ai <= i).
Each test case begins with a line containing two integers N and K (2 <= K <= N <= 100000). The second line contains N-1 space-separated integers a1,a2,…,aN−1, it means that there is an edge between vertex ai and vertex i+1 (1 <= ai <= i).
Output
For each test case, print the minimum possible number of remaining edges.
Sample Input
2
4 4
1 2 3
4 3
1 1 1
Sample Output
2
2
Source
Recommend
思路:贪心+二分图最大匹配(树形DP)
这题O(n)做竟然卡读入优化。。。。。。。。。。。。。。。。。。。。。。用一般的读入优化竟然TLE。
从大佬哪里求得黑科技,样例输不进去竟然AC了:
namespace IO
{
const int U=40*1024*1024;
char buf[U];
int ptr,sz;
void begin()
{
ptr=0;
sz=fread(buf,1,U,stdin);
}
template<typename T>
inline bool scan_d (T&t)
{
while(ptr<sz&&buf[ptr]!='-'&&(buf[ptr]<'0'||buf[ptr]>'9')) ptr++;
if(ptr>=sz) return false;
bool sgn=false;
if(buf[ptr]=='-') sgn=true,ptr++;
for(t=0;ptr<sz&&'0'<=buf[ptr]&&buf[ptr]<='9';ptr++)
t=t*10+buf[ptr]-'0';
if(sgn) t=-t;
return true;
}
}
using namespace IO;
①很显然,如果我们最终的答案是一个包含K个点的整个联通块的话,显然答案就是K-1.如果我们是两个共包含K个点的联通块的话,显然答案会对应减少。
所以我们希望分部的情况是尽可能多的联通块,那么理应我们希望将结果分成若干个两两相连的小联通块。
②我们希望构成尽可能多的这样两两相连的小联通块(只用一条边去连接)的话,很显然是需要跑最大二分匹配数。我们知道最小点覆盖==最大二分匹配数,而直接建图跑二分图匈牙利匹配的话,时间复杂度很爆炸,我们知道树形dp可以O(n)求树上的最小点覆盖问题,所以我们直接跑树形Dp即可。
③如果我们最小点覆盖数为ans个,那么分情况讨论即可:
如果k<=ans*2,那么结果就是k/2
如果k>ans*2,那么结果就是k-ans*2+ans(多出来的点直接往上加即可)
如果k是奇数,那么答案再加1.
#include<vector> #include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define MAXN 100010 using namespace std; int T,n,k,tot; int dp[MAXN][2]; vector<int>vec[MAXN]; namespace IO { const int U=40*1024*1024; char buf[U]; int ptr,sz; void begin() { ptr=0; sz=fread(buf,1,U,stdin); } template<typename T> inline bool scan_d (T&t) { while(ptr<sz&&buf[ptr]!='-'&&(buf[ptr]<'0'||buf[ptr]>'9')) ptr++; if(ptr>=sz) return false; bool sgn=false; if(buf[ptr]=='-') sgn=true,ptr++; for(t=0;ptr<sz&&'0'<=buf[ptr]&&buf[ptr]<='9';ptr++) t=t*10+buf[ptr]-'0'; if(sgn) t=-t; return true; } } using namespace IO; void dfs(int fa,int now){ dp[now][0]=0; dp[now][1]=1; for(int i=0;i<vec[now].size();i++) if(vec[now][i]!=fa){ dfs(now,vec[now][i]); dp[now][0]+=dp[vec[now][i]][1]; dp[now][1]+=min(dp[vec[now][i]][0],dp[vec[now][i]][1]); } } int main(){ IO::begin(); scan_d(T); while(T--){ tot=0; memset(dp,0,sizeof(dp)); scan_d(n);scan_d(k); for(int i=1;i<=n;i++) vec[i].clear(); for(int i=1;i<n;i++){ int x; scan_d(x); vec[x].push_back(i+1); vec[i+1].push_back(x); } dfs(0,1); int ans=min(dp[1][0],dp[1][1]); if(ans*2>=k) printf("%d ",(k+1)/2); if(ans*2<k) printf("%d ",ans+k-2*ans); } }