题意:
鲍勃喜欢玩战略游戏。现在有n个城市,他们构成了一棵树。鲍勃可以在某些城市派一个士兵守护,该士兵可以瞭望到所有与该城市相连的边。问鲍勃最少要派遣多少个士兵,才能把所有的边都瞭望到。
首先是树形dp的解法:
我们可以这样定义状态f[i]表示以i为子树的根所需建的最少的塔数
有两种状态f[i][0]表示不建塔,f[i][1]表示建塔
那么可以很显然地得到:
如果i不建塔,那么i的儿子j必须建塔,于是f[i][0]=sum(f[i][1])
如果i建塔,那么i的儿子j可建可不建,于是f[i][1]=min(f[j][0],f[j][1])+1;
通过递归自下而上来实现,代码如下:
#include<iostream> #include<cmath> #include<cstring> #include<cstdlib> #include<cstdio> #include<algorithm> const int MAXN=10000; using namespace std; int k,num,n,a,b,cnt,fir[MAXN+5],F[MAXN+5][2]; struct node{int e,next;}h[(MAXN<<1)+5]; struct node1{ int ch,next; }tree[MAXN+5]; bool vis[MAXN+5]; inline void Clear(){ memset(h,0,sizeof h); memset(fir,0,sizeof fir); cnt=0; memset(F,0,sizeof F); memset(tree,0,sizeof tree); memset(vis,0,sizeof vis); } void dfs(int s){ vis[s]=1; for(int i=fir[s];i;i=h[i].next) { if(!vis[h[i].e]) { tree[h[i].e].next=tree[s].ch; tree[s].ch=h[i].e; dfs(h[i].e); } } } int tree_dp(int root,int flag){ if(F[root][flag]>-1) return F[root][flag]; if(!tree[root].ch) { if(flag) return F[root][flag]=1; else return F[root][flag]=0; } if(flag) { int sum=1; for(int i=tree[root].ch;i;i=tree[i].next) sum+=min(tree_dp(i,0),tree_dp(i,1)); return F[root][flag]=sum; } else { int sum=0; for(int i=tree[root].ch;i;i=tree[i].next) sum+=tree_dp(i,1); return F[root][flag]=sum; } } inline void Read(int &Ret){ char ch;bool flag=0; for(;ch=getchar(),ch<'0'||ch>'9';) if(ch=='-') flag=1; for(Ret=ch-'0';ch=getchar(),'0'<=ch&&ch<='9';Ret=Ret*10+ch-'0'); flag&&(Ret=-Ret); } int main() { while(scanf("%d",&n)!=EOF) { Clear(); for(int i=1;i<=n;i++) { scanf("%d",&a); getchar(); getchar(); scanf("%d",&k); a++; getchar(); for(int j=1;j<=k;j++) { scanf("%d",&b); b++; F[a][0]=F[a][1]=F[b][0]=F[b][1]=-1; h[++cnt].e=a; h[cnt].next=fir[b]; fir[b]=cnt; h[++cnt].e=b; h[cnt].next=fir[a]; fir[a]=cnt; } } int s=1; dfs(s); tree_dp(s,1); tree_dp(s,0); printf("%d\n",min(F[s][0],F[s][1])); } }
然后是贪心解法:
我总觉得贪心解法有点神奇,但是又是如此的有道理,贪心策略是:对于图中度数为1的点来说,在它上面放士兵很显然是
并不优的,但是呢,对于其相接的那个点,放士兵一定是最优的,这个是比较显然的,不证了。所以说,方法就是,通过度
数为1的节点,删除节点,又再在删去节点后的树中继续找度为1的节点,不断循环,直到不存在度为1的节点即可
代码:(当时没想出来,参考了一下网上的代码,跑得比树形dp快)
#include<iostream> #include<cstdlib> #include<cstdio> #include<cmath> #include<cstring> #include<vector> #include<algorithm> #include<queue> const int MAXN=1505; using namespace std; int edge[MAXN]; queue<int> Q; vector<int> tree[MAXN]; int ans,N,a,b,k; bool vis[MAXN]; int main() { while(scanf("%d",&N)!=EOF) { memset(vis,0,sizeof vis);ans=0; memset(edge,0,sizeof edge); for(int i=1;i<=N;i++) { scanf("%d:(%d)",&a,&k);a++; for(int j=1;j<=k;j++) { scanf("%d",&b); b++; tree[a].push_back(b); tree[b].push_back(a); edge[a]++; edge[b]++; } } if(N==1){putchar('1'); putchar(10); continue;} for(int i=1;i<=N;i++) if(tree[i].size()==1) Q.push(i); while(!Q.empty()) { int s=Q.front(); Q.pop(); int e=tree[s][0]; //对于这个决策,肯定是选e更优 if(vis[e]) continue; ans++; vis[e]=1; int len=tree[e].size(); for(int i=0;i<len;i++) { edge[tree[e][i]]--; if(vis[tree[e][i]]) continue; if(edge[tree[e][i]]==1) Q.push(tree[e][i]); } } printf("%d\n",ans); for(int i=1;i<=N;i++) tree[i].clear(); } }
二分匹配的解法容我再想想。。。