寒假Day10:树形dp+二分图匹配(匈牙利算法)练习
树形dp
数的最小点覆盖问题
对于树形dp我的理解是:在一棵树上做动态规划
HDU1054-Strategic Game(题目链接点击题目即可)
- 题意:给出n个节点 节点编号0~n-1 ,每个节点站一个哨兵,每个哨兵观察到与自己相邻的两条边,问最少需要多少个哨兵才能观察到所有边
- 题面:
Bob enjoys playing computer games, especially strategic games, but sometimes he cannot find the solution fast enough and then he is very sad. Now he has the following problem. He must defend a medieval city, the roads of which form a tree. He has to put the minimum number of soldiers on the nodes so that they can observe all the edges. Can you help him?
Your program should find the minimum number of soldiers that Bob has to put for a given tree.
The input file contains several data sets in text format. Each data set represents a tree with the following description:
the number of nodes
the description of each node in the following format
node_identifier:(number_of_roads) node_identifier1 node_identifier2 ... node_identifier
or
node_identifier:(0)
The node identifiers are integer numbers between 0 and n-1, for n nodes (0 < n <= 1500). Every edge appears only once in the input data.
For example for the tree:
the solution is one soldier ( at the node 1).
The output should be printed on the standard output. For each given input data set, print one integer number in a single line that gives the result (the minimum number of soldiers). An example is given in the following table:
- 样例:
Sample Input 4 0:(1) 1 1:(2) 2 3 2:(0) 3:(0) 5 3:(3) 1 4 2 1:(1) 0 2:(0) 0:(0) 4:(0) Sample Output 1 2
- 可以把所有的点黑白染色,然后连边求最大匹配。以下是我用了dp过的。
- 开的数组:dp[N][2]:该数组:针对当前节点 选或者不选
dp[i][0]:不选
dp[i][1]:选
- 状态转移方程(是关键):
for(int i=0;i<v[root].size();i++) { dp[root][0]+=dp[v[root][i]][1];//若当前节点不选,则子节点必须选 dp[root][1]+=min(dp[v[root][i]][1],dp[v[root][i]][0]);//当前节点选,子节点(选/不选) }
AC代码:
1 //给出n个节点 节点编号0~n-1 2 //每个节点站一个哨兵,每个哨兵观察到与自己相邻的两条边 3 //问最少需要多少个哨兵才能观察到所有边 4 5 #include<string.h> 6 #include<iostream> 7 #include<stdio.h> 8 #include<algorithm> 9 #include<queue> 10 #include<vector> 11 #include<map> 12 #include<cmath> 13 using namespace std; 14 #define inf 0x3f3f3f3f 15 #define inff 0x3f3f3f3f3f3f3f3f 16 const int N=1550; 17 #define mod 998244353 18 typedef long long ll; 19 20 int n; 21 vector<int>v[N]; 22 int f[N],dp[N][2]; 23 //dp针对当前节点 选或者不选 24 //dp[i][0];//不选 25 //dp[i][1];//选 26 void dfs(int root) 27 { 28 dp[root][0]=0;//当前根节点不选 29 dp[root][1]=1;//当前根节点选 30 for(int i=0;i<v[root].size();i++) 31 dfs(v[root][i]); 32 for(int i=0;i<v[root].size();i++) 33 { 34 dp[root][0]+=dp[v[root][i]][1];//若当前节点不选,则子节点必须选 35 dp[root][1]+=min(dp[v[root][i]][1],dp[v[root][i]][0]);//当前节点选,子节点(选/不选) 36 } 37 } 38 39 int main() 40 { 41 while(~scanf("%d",&n)) 42 { 43 memset(f,-1,sizeof(f));//因为节点编号0~n-1 44 for(int i=1;i<=n;i++) 45 { 46 int root,node,son; 47 scanf("%d:(%d)",&root,&node); 48 v[root].clear();//记得清空 49 for(int j=1;j<=node;j++) 50 { 51 scanf("%d",&son); 52 v[root].push_back(son);//单向边 53 f[son]=root; 54 } 55 } 56 int x=0; 57 while(f[x]!=-1) 58 x=f[x];//找到整棵树的根节点 59 dfs(x); 60 int minn=min(dp[x][0],dp[x][1]); 61 printf("%d\n",minn); 62 } 63 return 0; 64 }
二分图匹配(匈牙利算法)练习
1、HDU - 2444 The Accomodation of Students
题面:
There are a group of students. Some of them may know each other, while others don't. For example, A and B know each other, B and C know each other. But this may not imply that A and C know each other. Now you are given all pairs of students who know each other. Your task is to divide the students into two groups so that any two students in the same group don't know each other.If this goal can be achieved, then arrange them into double rooms. Remember, only paris appearing in the previous given set can live in the same room, which means only known students can live in the same room. Calculate the maximum number of pairs that can be arranged into these double rooms. Input For each data set: The first line gives two integers, n and m(1<n<=200), indicating there are n students and m pairs of students who know each other. The next m lines give such pairs. Proceed to the end of file. Output If these students cannot be divided into two groups, print "No". Otherwise, print the maximum number of pairs that can be arranged in those rooms. Sample Input 4 4 1 2 1 3 1 4 2 3 6 5 1 2 1 3 1 4 2 5 3 6 Sample Output No 3
题意:给出n个顶点,m个对应情况,问该图是否是二分图。是,输出最大匹配数;否,输出No。
思路:bfs判断是否是二分图,匈牙利算法dfs判断最大匹配数。
AC代码:
1 #include<string.h> 2 #include<iostream> 3 #include<stdio.h> 4 #include<algorithm> 5 #include<queue> 6 #include<vector> 7 #include<map> 8 #include<cmath> 9 using namespace std; 10 #define inf 0x3f3f3f3f 11 #define inff 0x3f3f3f3f3f3f3f3f 12 const int N=220; 13 #define mod 998244353 14 typedef long long ll; 15 16 int n,m; 17 vector<int>v[N]; 18 int col[N];//标记顶点颜色0/1/-1 19 int f[N]; 20 bool book[N]; 21 22 bool bfs()//用bfs 判断一幅图是否是二分图 23 { 24 memset(col,0,sizeof(col)); 25 //染色后的状态只有1和-1 26 //所以染色的初始状态为全部清空为0 27 queue<int> Q; 28 Q.push(1);//先让第一个点入队 29 col[1]=1;//给第一个点染色为1 30 while(!Q.empty()) 31 { 32 int p=Q.front(); 33 Q.pop(); 34 for(int i=0; i<v[p].size(); i++) 35 { 36 //找与第一个点相邻的点 37 int x=v[p][i]; 38 if(col[x]==0)//如果未被染色 39 { 40 col[x]=-col[p];//就染成与当前点相反的颜色 41 Q.push(x); 42 } 43 else//如果已经被染色了且和当前节点染色相同,则确定不是二分图 44 { 45 if(col[p]==col[x]) 46 return 0; 47 } 48 } 49 } 50 return 1; 51 } 52 53 bool dfs(int x) 54 { 55 for(int i=0; i<v[x].size(); i++) 56 { 57 int s=v[x][i]; 58 if(book[s]==0) 59 { 60 book[s]=1; 61 if(f[s]==-1||dfs(f[s])) 62 { 63 f[s]=x; 64 return 1; 65 } 66 } 67 } 68 69 return 0; 70 } 71 72 int main() 73 { 74 ios::sync_with_stdio(false); 75 while(cin>>n>>m) 76 { 77 // for(int i=0;i<n;i++) //wa 78 for(int i=1; i<=n; i++) //i=0 ac 79 v[i].clear();//vector二维数组的清空 80 int x,y; 81 for(int i=1; i<=m; i++) 82 { 83 cin>>x>>y; 84 v[x].push_back(y); 85 v[y].push_back(x);//双向边 86 } 87 int w=bfs(); 88 if(w==0) 89 { 90 cout<<"No"<<endl; 91 continue; 92 } 93 int ans=0; 94 memset(f,-1,sizeof(f)); 95 // for(int i=0;i<n;i++) 96 for(int i=1; i<=n; i++) 97 { 98 memset(book,0,sizeof(book)); 99 w=dfs(i); 100 if(w) 101 ans++; 102 } 103 ans/=2; 104 cout<<ans<<endl; 105 } 106 return 0; 107 }
2、HDU - 1083 Courses
题面:
Consider a group of N students and P courses. Each student visits zero, one or more than one courses. Your task is to determine whether it is possible to form a committee of exactly P students that satisfies simultaneously the conditions: . every student in the committee represents a different course (a student can represent a course if he/she visits that course) . each course has a representative in the committee Your program should read sets of data from a text file. The first line of the input file contains the number of the data sets. Each data set is presented in the following format: P N Count1 Student1 1 Student1 2 ... Student1 Count1 Count2 Student2 1 Student2 2 ... Student2 Count2 ...... CountP StudentP 1 StudentP 2 ... StudentP CountP The first line in each data set contains two positive integers separated by one blank: P (1 <= P <= 100) - the number of courses and N (1 <= N <= 300) - the number of students. The next P lines describe in sequence of the courses . from course 1 to course P, each line describing a course. The description of course i is a line that starts with an integer Count i (0 <= Count i <= N) representing the number of students visiting course i. Next, after a blank, you'll find the Count i students, visiting the course, each two consecutive separated by one blank. Students are numbered with the positive integers from 1 to N. There are no blank lines between consecutive sets of data. Input data are correct. The result of the program is on the standard output. For each input data set the program prints on a single line "YES" if it is possible to form a committee and "NO" otherwise. There should not be any leading blanks at the start of the line. An example of program input and output: Input 2 3 3 3 1 2 3 2 1 2 1 1 3 3 2 1 3 2 1 3 1 1 Output YES NO
题意:有p门课程,n个学生,每个学生在一个committee代表一门课程,求学生和课程的最大匹配,若最大匹配等于p,输出YES,否则输出NO。
思路:匈牙利dfs模板套一下即可。
注意:单向边
AC代码:
这个用vector写的,比较耗时,一千多秒
1 #include<string.h> 2 #include<iostream> 3 #include<stdio.h> 4 #include<algorithm> 5 #include<queue> 6 #include<vector> 7 #include<map> 8 #include<cmath> 9 using namespace std; 10 #define inf 0x3f3f3f3f 11 #define inff 0x3f3f3f3f3f3f3f3f 12 const int N=330; 13 #define mod 998244353 14 typedef long long ll; 15 16 int n,p; 17 vector<int>v[N]; 18 int col[N];//标记顶点颜色0/1/-1 19 int f[N]; 20 bool book[N]; 21 22 23 bool dfs(int x) 24 { 25 for(int i=0; i<v[x].size(); i++) 26 { 27 int s=v[x][i]; 28 if(book[s]==0) 29 { 30 book[s]=1; 31 if(f[s]==-1||dfs(f[s])) 32 { 33 f[s]=x; 34 return 1; 35 } 36 } 37 } 38 return 0; 39 } 40 41 int main() 42 { 43 ios::sync_with_stdio(false); 44 int t; 45 cin>>t; 46 while(t--) 47 { 48 cin>>p>>n; 49 for(int i=1; i<=p; i++) 50 v[i].clear(); 51 for(int i=1; i<=p; i++) 52 { 53 int x; 54 cin>>x; 55 for(int j=1; j<=x; j++) 56 { 57 int y; 58 cin>>y; 59 v[i].push_back(y); 60 //v[y].push_back(i); 61 } 62 } 63 int ans=0; 64 memset(f,-1,sizeof(f)); 65 for(int i=1; i<=p; i++) 66 { 67 memset(book,0,sizeof(book)); 68 int w=dfs(i); 69 if(w) 70 ans++; 71 } 72 // ans/=2; 73 if(ans==p) 74 cout<<"YES"<<endl; 75 else 76 cout<<"NO"<<endl; 77 } 78 return 0; 79 }
这个用数组写的,只有三百多秒,也是裸题了
1 #include<stdio.h> 2 #include<iostream> 3 #include<queue> 4 #include<string.h> 5 using namespace std; 6 #define inf 0x3f3f3f3f 7 8 const int N=350; 9 int n,p,e[N][N],match[N]; 10 bool book[N]; 11 12 int dfs(int x) 13 { 14 for(int i=1; i<=n; i++) 15 { 16 if(book[i]==0&&e[x][i]) 17 { 18 book[i]=1; 19 if(match[i]==0||dfs(match[i])) 20 { 21 match[i]=x; 22 return 1; 23 } 24 } 25 } 26 return 0; 27 } 28 29 int main() 30 { 31 int t; 32 scanf("%d",&t); 33 while(t--) 34 { 35 scanf("%d %d",&p,&n); 36 memset(e,0,sizeof(e)); 37 memset(match,0,sizeof(match)); 38 for(int i=1; i<=p; i++)//学生编号 39 { 40 int num,classs; 41 scanf("%d",&num); 42 for(int j=1; j<=num; j++) 43 { 44 scanf("%d",&classs); 45 e[i][classs]=1; 46 } 47 } 48 int ans=0; 49 for(int i=1; i<=p; i++) 50 { 51 memset(book,0,sizeof(book));//注意清空位置 52 if(dfs(i)) 53 ans++; 54 } 55 if(ans==p) 56 printf("YES\n"); 57 else 58 printf("NO\n"); 59 } 60 return 0; 61 }
3、HDU4185 Oil Skimming
题面:
Thanks to a certain "green" resources company, there is a new profitable industry of oil skimming. There are large slicks of crude oil floating in the Gulf of Mexico just waiting to be scooped up by enterprising oil barons. One such oil baron has a special plane that can skim the surface of the water collecting oil on the water's surface. However, each scoop covers a 10m by 20m rectangle (going either east/west or north/south). It also requires that the rectangle be completely covered in oil, otherwise the product is contaminated by pure ocean water and thus unprofitable! Given a map of an oil slick, the oil baron would like you to compute the maximum number of scoops that may be extracted. The map is an NxN grid where each cell represents a 10m square of water, and each cell is marked as either being covered in oil or pure water. Input The input starts with an integer K (1 <= K <= 100) indicating the number of cases. Each case starts with an integer N (1 <= N <= 600) indicating the size of the square grid. Each of the following N lines contains N characters that represent the cells of a row in the grid. A character of '#' represents an oily cell, and a character of '.' represents a pure water cell. Output For each case, one line should be produced, formatted exactly as follows: "Case X: M" where X is the case number (starting from 1) and M is the maximum number of scoops of oil that may be extracted. Sample Input 1 6 ...... .##... .##... ....#. ....## ...... Sample Output Case 1: 3
题意:最多有多少个##,竖着也可以
思路:还是老样子,套dfs匈牙利算法的模板
注意:里面需要给每个#进行编号,目的是方便存边,每一个#对应一个编号,便于配对,否则每一个#都得跟着x和y坐标,不利于存边
具体实现步骤:
- 找到所有的#给它们编号
int num=0; for(int i=0; i<n; i++) { scanf("%s",a[i]); for(int j=0; j<n; j++) { if(a[i][j]=='#') id[i][j]=++num; } }
- bfs去寻找上下左右的 #
void bfs(int x,int y,int idd) { for(int i=0; i<4; i++) { int tx=x+to[i][0]; int ty=y+to[i][1]; if(tx>=0&&tx<n&&ty>=0&&ty<n&&id[tx][ty]!=0) { v[idd].push_back(id[tx][ty]); v[id[tx][ty]].push_back(idd); } } }
- 对每一个#进行寻找
for(int i=0; i<n; i++) { for(int j=0; j<n; j++) { if(id[i][j]!=0) { bfs(i,j,id[i][j]); } } }
AC代码:
1 #include<string.h> 2 #include<iostream> 3 #include<stdio.h> 4 #include<algorithm> 5 #include<queue> 6 #include<vector> 7 #include<map> 8 #include<cmath> 9 using namespace std; 10 #define inf 0x3f3f3f3f 11 #define inff 0x3f3f3f3f3f3f3f3f 12 const int N=660; 13 #define mod 998244353 14 typedef long long ll; 15 16 int n; 17 vector<int>v[N]; 18 int f[N],id[N][N]; 19 bool book[N]; 20 char a[N][N]; 21 int to[4][2]= {{1,0},{-1,0},{0,1},{0,-1}}; 22 23 24 void bfs(int x,int y,int idd) 25 { 26 for(int i=0; i<4; i++) 27 { 28 int tx=x+to[i][0]; 29 int ty=y+to[i][1]; 30 if(tx>=0&&tx<n&&ty>=0&&ty<n&&id[tx][ty]!=0) 31 { 32 v[idd].push_back(id[tx][ty]); 33 v[id[tx][ty]].push_back(idd);//加和不加都得/2且第一组数据都==3 34 } 35 } 36 } 37 38 int dfs(int x) 39 { 40 for(int i=0; i<v[x].size(); i++) 41 { 42 int s=v[x][i]; 43 if(!book[s]) 44 { 45 book[s]=1; 46 if(f[s]==-1||dfs(f[s])) 47 { 48 f[s]=x; 49 return 1; 50 } 51 } 52 } 53 return 0; 54 } 55 56 int main() 57 { 58 int t,tt=1; 59 scanf("%d",&t); 60 while(t--) 61 { 62 scanf("%d",&n); 63 //预处理:id:给每一个#油田进行编号, 64 memset(id,0,sizeof(id)); 65 int num=0; 66 for(int i=0; i<n; i++) 67 { 68 scanf("%s",a[i]); 69 for(int j=0; j<n; j++) 70 { 71 if(a[i][j]=='#') 72 id[i][j]=++num; 73 } 74 } 75 76 for(int i=1; i<=num; i++) 77 v[i].clear(); 78 79 for(int i=0; i<n; i++) 80 { 81 for(int j=0; j<n; j++) 82 { 83 if(id[i][j]!=0) 84 { 85 bfs(i,j,id[i][j]); 86 } 87 } 88 } 89 int ans=0; 90 memset(f,-1,sizeof(f)); 91 for(int i=1; i<=num; i++) 92 { 93 memset(book,0,sizeof(book)); 94 ans+=dfs(i); 95 } 96 ans/=2; 97 printf("Case %d: %d\n",tt++,ans); 98 } 99 return 0; 100 }
二分图匹配代码注意点:
- 存边的时候需要判断是否需要存入双向边,如果是双向边的话最后的结果需要除以2,因为会有重边;
- vector数组存边的时候需要进行清空,根据题目判断是从1~N还是0~N-1,要是不确定直接从0~N好了;
- f 数组记得清空。
待解决
- 字体设置的代码出问题了,颜色就这个样子了。。乱糟糟的。。
- 匈牙利算法:不知道为什么下面不带注释的是正确的,改成带注释的版本就是错误的???
bool dfs(int x) { for(int i=0; i<v[x].size(); i++) { int s=v[x][i]; if(book[s]==0) { book[s]=1; if(f[s]==-1||dfs(f[s])) { f[s]=x; return 1; } } // if(v[x][i]&&!book[i]) // { // book[i]=1; // if(f[i]==-1||dfs(f[i])) // { // f[i]=x; // return 1; // } // } } return 0; }
- HDU4185中,看下面代码,不知道为什么都等于3???
if(tx>=0&&tx<n&&ty>=0&&ty<n&&id[tx][ty]!=0) { v[idd].push_back(id[tx][ty]); v[id[tx][ty]].push_back(idd);//加和不加都得/2且第一组数据都==3 }
- HDU4185中,找两个#不知道为什么双向边除以二可以过,但是单向边不除以2不能过???
- HDU1083中,只能用单向边过的原因,emm,我觉得是因为一个学生可以对应多门课程,可是这样说的话一个课程也可以对应多个学生,为什么不可以双向边???