上次说了说树形dp的入门
那么这次该来一点有难度的题目了:
UVA10859 Placing Lampposts
给定一个n个点m条边的无向无环图,在尽量少的节点上放灯,使得所有边都与灯相邻(被灯照亮)。
在灯的总数最小的前提下,被两盏灯同时照亮的边数应该尽可能大。
输入格式
第一行输入T,为数据组数。
每组数据第一行输入n,m,分别为该组数据中图的点数和边数。
以下m行,输入各边的两端点u,v。
输出格式
输出共T行。
对每组数据,一行输出三个数,最小灯数、被两盏灯同时照亮的边数、只被一盏灯照亮的边数。
n<=1000
有向无环图说白了就是一个森林(可以自己画图看看),第一问这不就是裸的树形dp求最大独立集吗?在每个森林上跑一遍树形dp就行。不过第二问第三问倒有点意思,怎么维护两边都放灯的道路的数量呢?这里介绍一个十分巧妙的方法,由于n<=1000,我们就可以把一个节点的权值设为比1000大的数,然后在转移的时候,如果这条路的两端节点没有都选,那么就+1,代表有多少只被一盏灯照亮的路,最后的答案除以k就是第一问,mod k就是第三问,用m减第三问的答案就是第二问。
void dfs(int x) { dp[x][1]=k;//这里的k我们设为大于1000的数 dp[x][0]=0; d[x]=1; for(int i=last[x];i;i=g[i].next) { int v=g[i].to; if(d[v]) continue; dfs(v); dp[x][1]+=min(dp[v][0]+1,dp[v][1]); dp[x][0]+=dp[v][1]+1;//如果只被一盏灯照亮就加上1,目的是和被两盏灯同时照亮的边区分,同时也保证了被两盏灯同时照亮的边数应该尽可能大,毕竟我们取最小值。
}
}
经过这样一番神奇的操作,我们就成功的切掉了这道看似有点神仙的题目。
说了这么多,怎么能没有代码呢?

#include<iostream> #include<cstdio> #include<string> #include<cmath> #include<cstring> #include<queue> #include<stack> #include<algorithm> #define maxn 3005 using namespace std; struct edge { int next; int to; }g[maxn]; inline int read() { char c=getchar(); int res=0,x=1; while(c<'0'||c>'9') { if(c=='-') x=-1; c=getchar(); } while(c>='0'&&c<='9') { res=res*10+(c-'0'); c=getchar(); } return x*res; } int t,n,m,num,aa,bb,ans; int k=3000; int last[maxn],dp[maxn][2],d[maxn]; inline void add(int from,int to) { g[++num].next=last[from]; g[num].to=to; last[from]=num; } void dfs(int x) { dp[x][1]=k; dp[x][0]=0; d[x]=1; for(int i=last[x];i;i=g[i].next) { int v=g[i].to; if(d[v]) continue; dfs(v); dp[x][1]+=min(dp[v][0]+1,dp[v][1]); dp[x][0]+=dp[v][1]+1; } } int main() { t=read(); while(t--) { n=read();m=read(); num=0;ans=0; memset(last,0,sizeof(last)); memset(dp,0,sizeof(dp)); memset(d,0,sizeof(d)); for(int i=1;i<=m;i++) { aa=read();bb=read(); add(aa,bb); add(bb,aa); } for(int i=1;i<=n;i++) { if(!d[i]) { dfs(i); ans+=min(dp[i][1],dp[i][0]); } } printf("%d %d %d ",ans/k,m-(ans%k),ans%k); } }
下面再来看这样的一道简(shen)单(xian)题
UVA1220 Hali-Bula的晚会 Party at Hali-Bula
公司里有n(n<=200)个人形成一个树状结构,即除了老板之外每个员工都有唯一的直属上司。要求选尽量多的人,但不能同时选择一个人和他的直属上司。问:最多能选多少人,以及在人数最多的前提下方案是否唯一。
输入:第一行一个数n;第二行输入老板的名字;以下的n-1行中,每行是一位员工的名字和其直属上司的名字(英文单词,长度为1到100),两个名字之间有空格隔开,'0'为输入结束的标识符。
输出:一行,输出一个数字,表示最大的访客数量。并再同一行输出单词'Yes'或'No',代表目前方案是否唯一。
这个的第一问好像有点简单的样子,但是这第二问好像有点毒瘤啊。我们不妨从状态转移上入手,
dp[x][1]+=dp[v][0]; dp[x][0]+=max(dp[v][1],dp[v][0]);
不难发现,如果 dp[v][1]==dp[v][0] 那么不就会出现两种方式了吗,因此我们用c数组来维护一下方案书是否唯一就行了,我们先判断孩子的方案数是否唯一,再用孩子去更新父亲,因为如果孩子的方案数不唯一,那么由这个孩子转移后的父亲肯定方案数也不唯一,这样就可以愉快的树形dp了。
像这样:
if(dp[v][0]>dp[v][1]&&c[v][0]) { c[x][0]=1; } if(dp[v][1]>dp[v][0]&&c[v][1]) { c[x][0]=1; } if(dp[v][1]==dp[v][0]) { c[x][0]=1; } if(c[v][0]) { c[x][1]=1; }
最后怎么少得了完整ac代码呢?

1 #include<iostream> 2 #include<cstdio> 3 #include<string> 4 #include<cmath> 5 #include<cstring> 6 #include<queue> 7 #include<stack> 8 #include<algorithm> 9 #include<map> 10 #define maxn 2005 11 using namespace std; 12 13 struct edge 14 { 15 int next; 16 int to; 17 }g[maxn]; 18 19 inline int read() 20 { 21 char c=getchar(); 22 int res=0,x=1; 23 while(c<'0'||c>'9') 24 { 25 if(c=='-') 26 x=-1; 27 c=getchar(); 28 } 29 while(c>='0'&&c<='9') 30 { 31 res=res*10+(c-'0'); 32 c=getchar(); 33 } 34 return x*res; 35 } 36 37 int n; 38 string aa,bb,root; 39 int cnt; 40 int num; 41 int last[maxn],dp[maxn][2],d[maxn],c[maxn][2]; 42 map<string,int>a; 43 44 inline void add(int from,int to) 45 { 46 g[++num].next=last[from]; 47 g[num].to=to; 48 last[from]=num; 49 } 50 51 void dfs(int x) 52 { 53 d[x]=1; 54 dp[x][1]=1; 55 dp[x][0]=0; 56 for(int i=last[x];i;i=g[i].next) 57 { 58 int v=g[i].to; 59 if(!d[v]) 60 { 61 dfs(v); 62 dp[x][1]+=dp[v][0]; 63 dp[x][0]+=max(dp[v][1],dp[v][0]); 64 if(dp[v][0]>dp[v][1]&&c[v][0]) 65 { 66 c[x][0]=1; 67 } 68 if(dp[v][1]>dp[v][0]&&c[v][1]) 69 { 70 c[x][0]=1; 71 } 72 if(dp[v][1]==dp[v][0]) 73 { 74 c[x][0]=1; 75 } 76 if(c[v][0]) 77 { 78 c[x][1]=1; 79 } 80 } 81 } 82 } 83 84 int main() 85 { 86 while(1) 87 { 88 n=read(); 89 if(n==0) break; 90 cnt=0;num=0; 91 memset(last,0,sizeof(last)); 92 memset(dp,0,sizeof(dp)); 93 memset(d,0,sizeof(d)); 94 memset(c,0,sizeof(c)); 95 a.clear(); 96 for(int i=1;i<=n;i++) 97 { 98 if(i==1) 99 { 100 cin>>root; 101 a[root]=++cnt; 102 } 103 else 104 { 105 cin>>aa>>bb; 106 if(!a[aa]) 107 { 108 a[aa]=++cnt; 109 } 110 if(!a[bb]) 111 { 112 a[bb]=++cnt; 113 } 114 add(a[aa],a[bb]); 115 add(a[bb],a[aa]); 116 } 117 } 118 dfs(1); 119 printf("%d ",max(dp[1][1],dp[1][0])); 120 if(dp[1][0]==dp[1][1]||(dp[1][0]<dp[1][1]&&c[1][1])||(dp[1][0]>dp[1][1]&&c[1][0])) 121 printf("No "); 122 else printf("Yes "); 123 } 124 }
No man or woman is worth your tears, and the one who is, won't make you cry.
--snowy
2019-01-15 18:48:21