第三周 8.2-8.8
8.2
昨天BC的时候。遇到了一个回文串算法。
比赛的时候抄了个板子(然WA)。
赛后重新学了一下算回文子串的Manacher算法。
Link:文库
核心在于当以一个字符为中心的串包含在已经处理过的串中时。
可以藉由对称性减少很多判断。
据说是O(n)的。复杂度不会算。
自己码了个板子。
getchar()读串小心坑。
头尾加$^是防越界。
1 # include <iostream> 2 # include <cstdio> 3 # include <cstring> 4 # include <algorithm> 5 using namespace std; 6 const int maxn=100+5; 7 char s[maxn*2]; 8 int p[maxn*2]; 9 10 int main(void) 11 { 12 // while(getchar()!='\n'); 13 int cnt=0;//读串 14 s[0]='$'; 15 while((s[2*(++cnt)]=getchar())!='\n'); 16 17 int len=2*cnt-1;//补# 18 for(int i=1;i<=len;i+=2) s[i]='#'; 19 s[len+1]='^'; 20 21 // cout<<"s: "; 22 // for(int i=1;i<=len;i++) cout<<s[i]<<" "; cout<<endl; 23 24 int mx=0,id;//以id为中心的回文串向右延伸最长 mx=id+p[id] 25 for(int i=1;i<=len;i++) 26 { 27 if(mx>i) p[i]=min(p[2*id-i],mx-i); 28 else p[i]=1; 29 while(s[i+p[i]]==s[i-p[i]]) p[i]++; 30 if(p[i]+i>mx) { mx=p[i]+i; id=i; } 31 } 32 33 // cout<<"p: "; 34 // for(int i=1;i<=len;i++) cout<<p[i]<<" "; cout<<endl; 35 36 return 0; 37 }
HDU 5340 Three Palindromes
还是觉得这个很坑。
一开始自己写的有bug。
发现后先写Manacher。后面按题解写pre。suf。
不懂压位。直接暴。
结果超慢。人多的时候直接T一片。
最让我不能忍的是别人写的纯暴稳过。
想了想。照题解那样枚举中间。两端点都在变。必然慢阿。
那些直接暴两端的。有一边已经定了。情况少很多。
在这题上浪费很多时间了。不改了。纯当学Manacher了。
1 # include <iostream> 2 # include <cstdio> 3 # include <cstring> 4 # include <algorithm> 5 using namespace std; 6 const int maxn=20000+5; 7 int p[maxn*2]; 8 char s[maxn*2]; 9 bool pre[maxn*2],suf[maxn*2]; 10 11 int main(void) 12 { 13 int T ;cin>>T; 14 getchar(); 15 while(T--) 16 { 17 int cnt=0; 18 s[0]='$'; 19 while((s[2*(++cnt)]=getchar())!='\n'); 20 int len=2*cnt-1; 21 for(int i=1;i<=len;i+=2) s[i]='#'; 22 s[len+1]='^'; 23 int mx=0,id; 24 for(int i=1;i<=len;i++) 25 { 26 if(mx>i) p[i]=min(p[2*id-i],mx-i); 27 else p[i]=1; 28 while(s[i+p[i]]==s[i-p[i]]) p[i]++; 29 if(p[i]+i>mx) { mx=p[i]+i; id=i; } 30 } 31 for(int i=1;i<=len;i++) 32 { 33 if(i%2) pre[i]=(p[i/2+1]==i/2+1)? 1:0 ; 34 else pre[i]=0; 35 } 36 for(int i=1;i<=len;i++) 37 { 38 if(i%2) suf[i]=(p[(i+len)/2]==(len-i)/2+1)? 1:0; 39 else suf[i]=0; 40 } 41 int ok=0; 42 pre[1]=0; suf[len]=0; 43 for(int i=4;i<len-2;i++) 44 { 45 for(int j=1;j<=p[i];j++) 46 if(pre[i-j]&&suf[i+j]){ok=1; break;} 47 if(ok) break; 48 } 49 puts(ok?"Yes":"No"); 50 } 51 return 0; 52 }
继续背包。
Vijos P1313 金明的预算方案
依赖背包。
在最多只有一层依赖关系的情况下。
可以把一组主件下的附件先0-1背包一遍。
然后再分组0-1。
但是这个题目的约束很强。每个主件只有最多两个附件。
枚举所有组合。直接分组0-1即可。
1 # include <iostream> 2 # include <cstdio> 3 # include <cstring> 4 using namespace std; 5 int dp[4000]; 6 7 struct stuff 8 { 9 int pri,sig; 10 }M[100],P1[100],P2[100]; 11 12 struct set 13 { 14 stuff a[4]; 15 } S[100]; 16 17 int main(void) 18 { 19 int N,m; scanf("%d%d",&N,&m); 20 N/=10; 21 for(int i=1;i<=m;i++) 22 { 23 int v,p,q; 24 scanf("%d%d%d",&v,&p,&q); 25 v/=10; p*=v; 26 if(!q){M[i].pri=v;M[i].sig=p;} 27 else 28 { 29 if(!P1[q].sig) {P1[q].pri=v;P1[q].sig=p;} 30 else {P2[q].pri=v;P2[q].sig=p;} 31 } 32 } 33 int cnt=0; 34 for(int i=1;i<=m;i++) 35 { 36 if(M[i].sig) 37 { 38 int k=0; 39 S[++cnt].a[0].pri=M[i].pri; 40 S[cnt].a[0].sig=M[i].sig; 41 if(P1[i].sig) 42 { 43 S[cnt].a[++k].pri=M[i].pri+P1[i].pri; 44 S[cnt].a[k].sig=M[i].sig+P1[i].sig; 45 } 46 if(P2[i].sig) 47 { 48 S[cnt].a[++k].pri=M[i].pri+P2[i].pri; 49 S[cnt].a[k].sig=M[i].sig+P2[i].sig; 50 } 51 if(P1[i].sig&&P2[i].sig) 52 { 53 S[cnt].a[++k].pri=M[i].pri+P1[i].pri+P2[i].pri; 54 S[cnt].a[k].sig=M[i].sig+P1[i].sig+P2[i].sig; 55 } 56 } 57 } 58 for(int i=1;i<=cnt;i++) 59 for(int j=N;j>=0;j--) 60 for(int k=0;k<4;k++) 61 if(S[i].a[k].sig&&S[i].a[k].pri<=j) 62 dp[j]=max(dp[j],dp[j-S[i].a[k].pri]+S[i].a[k].sig); 63 printf("%d\n",10*dp[N]); 64 return 0; 65 }
HDU 3449 Consumer
还是刚才的依赖背包。还是一层关系。
但是这里的附件数目多了。
箱子就看成没有价值加成的主件。
数据正好是一组一组读的。所以连存数据的数组都不用。
直接读来就按上面的0-1搞就出来了。
1 # include <iostream> 2 # include <cstdio> 3 # include <cstring> 4 # include <algorithm> 5 using namespace std; 6 # define maxn 100010 7 int dp[maxn],tem[maxn]; 8 9 int main(void) 10 { 11 int n,w; 12 while((scanf("%d%d",&n,&w))!=EOF) 13 { 14 memset(dp,0,sizeof(dp)); 15 for(int i=1;i<=n;i++) 16 { 17 int p,m; scanf("%d%d",&p,&m); 18 memcpy(tem,dp,sizeof(tem)); 19 for(int j=1;j<=m;j++) 20 { 21 int c,v; scanf("%d%d",&c,&v); 22 for(int k=w;k>=c;k--) 23 tem[k]=max(tem[k],tem[k-c]+v); 24 } 25 for(int k=w;k>=p;k--) 26 dp[k]=max(dp[k],tem[k-p]); 27 } 28 printf("%d\n",dp[w]); 29 } 30 return 0; 31 }
具有多层依赖关系的复杂背包就是树DP的内容了。
背包看到这里感觉算有一些初步认识了。
背包九讲最后还提了一个泛化物品。有点抽象。
之前所有的约束。包括0-1、完全、多重、分组、依赖……
都可以理解成一个泛化物品。
后面还有一些背包的变形。见过其中的一部分。也不打算再看了。
因为多校1遇见了许多树DP。而且紫薯也有专门这个章节。
炜哥在自家VJ挂了些题。于是后面打算做点树DP。
那么就开始吧。
HDU 1054 Strategic Game
树的最小点覆盖问题。
一个点只有取1和不取0两种状态。
取的话临点随意。不取的话临点必取。
给的是有向边了。从根dfs一遍出来。
根取或者不取的最小值即为答案。
1 # include <iostream> 2 # include <cstdio> 3 # include <cstring> 4 # include <algorithm> 5 # include <vector> 6 using namespace std; 7 # define maxn 2000 8 int dp[maxn][2],ind[maxn]; 9 vector <int> edge[maxn]; 10 11 void dfs(int x) 12 { 13 dp[x][0]=0; dp[x][1]=1; 14 for(int i=0;i<edge[x].size();i++) 15 { 16 int son=edge[x][i]; 17 dfs(son); 18 dp[x][0]+=dp[son][1]; 19 dp[x][1]+=min(dp[son][0],dp[son][1]); 20 } 21 return; 22 } 23 24 int main(void) 25 { 26 int n; 27 while((scanf("%d",&n))!=EOF) 28 { 29 memset(ind,0,sizeof(ind)); 30 for(int i=0;i<n;i++) 31 { 32 int x,y; scanf("%d",&x); 33 getchar();getchar(); 34 scanf("%d",&y); 35 getchar(); 36 edge[x].clear(); 37 while(y--) 38 { 39 int to; scanf("%d",&to); 40 edge[x].push_back(to); 41 ind[to]++; 42 } 43 } 44 for(int i=0;i<n;i++) 45 if(!ind[i]) 46 { 47 dfs(i); 48 printf("%d\n",min(dp[i][0],dp[i][1])); 49 break; 50 } 51 } 52 return 0; 53 }
HDU 2376 Average distance
显然每条边对答案的贡献是边权乘两边节点数。
这题的关键是把问题转化为节点统计。
用cnt[i]记下以i为根的子树节点。
对于父节点i到子节点j的边权为d的边。
对答案的贡献是cnt[j]*(n-cnt[j])*d。
可以在计点的时候直接把sum给做掉了。
只是如果用整型算sum的话要LL的。
中间过程要(LL)。
1 # include <iostream> 2 # include <cstdio> 3 # include <vector> 4 using namespace std; 5 typedef long long LL; 6 typedef pair<int,int> pii; 7 # define maxn 10100 8 int n,cnt[maxn]; 9 LL sum[maxn]; 10 vector<pii> edge[maxn]; 11 12 void dfs(int pos,int father) 13 { 14 int tot=0; 15 cnt[pos]=1; sum[pos]=0; 16 for(int i=0;i<edge[pos].size();i++) 17 { 18 pii tem=edge[pos][i]; 19 int to=tem.first,d=tem.second; 20 if(to==father) continue; 21 dfs(to,pos); 22 sum[pos]+=sum[to]; 23 sum[pos]+=(LL)d*(LL)cnt[to]*(LL)(n-cnt[to]); 24 tot+=cnt[to]; 25 } 26 cnt[pos]+=tot; 27 return; 28 } 29 30 int main(void) 31 { 32 int T ; cin>>T; 33 while(T--) 34 { 35 scanf("%d",&n); 36 for(int i=0;i<n;i++) edge[i].clear(); 37 for(int i=1;i<n;i++) 38 { 39 int a,b,d; 40 scanf("%d%d%d",&a,&b,&d); 41 edge[a].push_back(pii(b,d)); 42 edge[b].push_back(pii(a,d)); 43 } 44 dfs(0,-1); 45 printf("%lf\n",(double)sum[0]*2/(n*(n-1))); 46 } 47 return 0; 48 }
POJ 1655 Balancing Act
敲完代码。发现自家VJ交不了。
去搜这道题的题号的时候。看见别人blog的标题。
才发现这就是紫薯上第二个问题。树的重心。
就是节点统计一下。做过上面那个题这个题差不多。
感觉唯一要注意的是去掉一个节点后。森林中的树不仅有它的子树。还有父节点连的树。
在所有子树的节点数cnt[i]和父节点连的树节点数n-cnt[pos]中取最大值。
再与ans取最小。记下位置即可。
1 # include <iostream> 2 # include <cstdio> 3 # include <algorithm> 4 # include <vector> 5 using namespace std; 6 # define maxn 20020 7 int n,ans,p,cnt[maxn]; 8 vector<int> edge[maxn]; 9 10 void dfs(int pos,int father) 11 { 12 cnt[pos]=1; 13 int tem=0; 14 for(int i=0;i<edge[pos].size();i++) 15 { 16 int next=edge[pos][i]; 17 if(next==father) continue; 18 dfs(next,pos); 19 cnt[pos]+=cnt[next]; 20 tem=max(tem,cnt[next]); 21 } 22 if(ans>max(tem,n-cnt[pos])) 23 { 24 ans=max(tem,n-cnt[pos]); 25 p=pos; 26 } 27 return; 28 } 29 30 int main(void) 31 { 32 int T; cin>>T; 33 while(T--) 34 { 35 scanf("%d",&n); 36 for(int i=1;i<=n;i++) edge[i].clear(); 37 for(int i=1;i<n;i++) 38 { 39 int a,b; scanf("%d%d",&a,&b); 40 edge[a].push_back(b); 41 edge[b].push_back(a); 42 } 43 ans=maxn; dfs(1,0); 44 printf("%d %d\n",p,ans); 45 } 46 return 0; 47 }
8.3
继续树dp。
POJ 1935 Journey
把要经过的点称作m点。
比较容易的是把问题转化成从k出发走完所有m点再回k。然后减去最远的m点距离。
要思考的是怎么表示状态。
对于每个点t。
以它为根走过子树中所有m点的最小距离为dp[t][0]。
以及走过所有m点回根的距离dp[t][1]。
从k开始dfs。
如果t的一个孩子r的子树中有m点。
就给dp[t][1]+=dp[r][1]+2*d
同时记录下t所有孩子中走完回和走完不回差距最大的。
最后dp[t][0]=dp[t][1]减那段最大的差值。
意思就是前面的点都走完回。最后再走那个方向。走完就不回了。省的距离最大。
最后dp[k][0]就是答案。
跑完竟然要800ms。以为自己想法太复杂。
搜了一下看见别人有写一样思路的。拷进去跑一下200ms。
我对比了一下。只有存图不一样。
吓得我默默收起STL二件套。掏出了链表板子……
1 # include <iostream> 2 # include <cstdio> 3 # include <cstring> 4 # include <algorithm> 5 # include <vector> 6 using namespace std; 7 typedef pair<int,int> pii; 8 # define maxn 50010 9 int dp[maxn][2]; 10 bool vis[maxn]; 11 vector<pii> edge[maxn]; 12 13 void dfs(int pos,int father) 14 { 15 int Max=0; 16 for(int i=0;i<edge[pos].size();i++) 17 { 18 pii tem=edge[pos][i]; 19 int to=tem.first,d=tem.second; 20 if(to==father) continue; 21 dfs(to,pos); 22 if(dp[to][0]||vis[to]) 23 { 24 dp[pos][1]+=dp[to][1]+2*d; 25 Max=max(Max,dp[to][1]-dp[to][0]+d); 26 } 27 } 28 dp[pos][0]=dp[pos][1]-Max; 29 return; 30 } 31 32 int main(void) 33 { 34 int n,k; 35 while((scanf("%d%d",&n,&k))!=EOF) 36 { 37 memset(vis,0,sizeof(vis)); 38 memset(dp,0,sizeof(dp)); 39 for(int i=1;i<n;i++) 40 { 41 int a,b,d; 42 scanf("%d%d%d",&a,&b,&d); 43 edge[a].push_back(pii(b,d)); 44 edge[b].push_back(pii(a,d)); 45 } 46 int j; scanf("%d",&j); 47 for(int i=1;i<=j;i++) 48 { 49 int m; scanf("%d",&m); 50 vis[m]=1; 51 } 52 dfs(k,0); 53 printf("%d\n",dp[k][0]); 54 } 55 return 0; 56 }
HDU 1520 Anniversary party
类似于之前做的独立集。考虑取或者不取两种状态。
但是有的点是会负权的。所以并不是取得时候一定比不取大。
hdu上此题输入极其恶心。
因为不支持多文件。其实有多case他都没说。
而且明明是树了。还要T条边读到0为止。
1 # include <iostream> 2 # include <cstdio> 3 # include <cstring> 4 # include <algorithm> 5 using namespace std; 6 # define maxn 6100 7 int cnt,headlist[maxn]; 8 int con[maxn],ind[maxn],dp[maxn][2]; 9 10 struct node 11 { 12 int to,pre; 13 } edge[maxn]; 14 15 void add(int from,int to) 16 { 17 cnt++; 18 edge[cnt].pre=headlist[from]; 19 edge[cnt].to=to; 20 headlist[from]=cnt; 21 } 22 23 void dfs(int pos) 24 { 25 dp[pos][0]=0; 26 dp[pos][1]=con[pos]; 27 int tem=headlist[pos]; 28 while(tem) 29 { 30 int to=edge[tem].to; 31 dfs(to); 32 dp[pos][0]+=max(dp[to][1],dp[to][0]); 33 dp[pos][1]+=dp[to][0]; 34 tem=edge[tem].pre; 35 } 36 return; 37 } 38 39 int main(void) 40 { 41 int N; 42 while((scanf("%d",&N))!=EOF) 43 { 44 for(int i=1;i<=N;i++) scanf("%d",con+i); 45 memset(headlist,0,sizeof(headlist)); 46 memset(ind,0,sizeof(ind)); 47 cnt=0; 48 int from,to; 49 while(scanf("%d%d",&to,&from)) 50 { 51 if(!to&&!from) break; 52 add(from,to); 53 ind[to]++; 54 } 55 for(int i=1;i<=N;i++) 56 { 57 if(!ind[i]) 58 { 59 dfs(i); 60 printf("%d\n",max(dp[i][0],dp[i][1])); 61 break; 62 } 63 } 64 } 65 return 0; 66 }
HDU 2196 Computer
研究了很长时间。总算是把这个问题弄懂了。
这个是紫薯树DP的最后一个问题。树上最长路径。也算是一个常见的经典问题。
首先对于单纯的求树上最远点的问题。紫薯提供了两种方法。
(为了叙述方便。按照紫薯上边权均为1。)
第一种是定义以i为根的子树中的节点中与i的最大距离为d(i)。
递推得到d(i)=max{d(j)}+1。其中j是i的直接子节点。
那么对于每个节点i。求出所有子节点的d(j)。
找到最大的两个u与v。那么d(u)+d(v)+2就是经过i的最长路。
第二种是随便找个点u。深搜一遍。找到u的最远点v。
再从v深搜一遍。找到v的最远点w。则v与w距离就是最长路。
下面回到原来的问题。
与上述的简单问题相比。这个问题有两点变化。
第一点是边权加上了。但是这个对问题影响并不大。
第二点是要求找出每个点的最远点。这就导致原来的算法行不通了。
那么下面也对这个问题讲两种做法。
第一种。
我们考虑对于每个点i。它通往最远点的路径无非两种。
一种是通过它的某一个孩子节点。另一种是通过它的父亲节点。
我们先对每个点定义三种状态。
对于i的所有孩子节点1,2,3,...,j。如果我们选择往这个方向延伸。
那么它一定有一个往下走的最远距离。可以记为d1,d1,d3,...,dj。
dp[i][0]表示d1,d1,d3,...,dj中的最大值。
dp[i][1]表示d1,d1,d3,...,dj中的次大值。(作用后面讲)
dp[i][2]表示通过父亲节点能走的距离的最大值。
注意。我们上面说过了答案只有两种情况。
只要我们求出所有的dp[i][0]和dp[i][2]。其二者最大值就是每个点的最长路。
那么我们选择一个根节点。通过一次dfs。就能先更新出dp[i][0]与dp[i][1]。
转移方程是这样的。对于i的每个孩子j。
先更新dp[i][1]=max(dp[i][1],dp[j][0]+d)。
这时如果dp[i][1]>dp[i][0]。两个swap。
(这只是一个好看的写法。当然也可以拿dp[j][0]先和dp[i][0]比。再和dp[i][1]比。)
到这里已经求出了dp[i][0]。相当于任务已经完成了一半。下面就要求每个点的dp[i][2]。
我们来讨论一下子节点j通过父节点i的最长路有哪些情况。
很容易想到。如果已经知道了父节点的最长路。
那么父节点的最长路加上子节点到父节点的距离就是子节点通过父节点的最长路。
然而特别要注意。这是有例外的。那就是父节点的最长路正好通过了这个子节点的情况。
为了避免这种情况。我们分类讨论一下。
如果父节点的最长路通过了子节点。即满足dp[i][0]==dp[j][0]+dist(i,j)。
那么从子节点j出发经过父节点i的最长路有两种情况。
一种是它通过了父节点i的父节点father(i)。这条路的长度就是dp[i][2]+dist(i,j)。
另一种是通过了父节点i的除了j以外的子节点。那么在其他子节点中当然也要取能走最远的一个。
那么它也就是我们刚才保存的。仅次于j的次大值了。这条路的长度是dp[i][1]+dist(i,j)。
经过重重讨论。我们终于得到了dp[j][2]的表达式。
dp[j][2]=max(dp[i][2],dp[i][0]-dist(i,j)==dp[j][0]?dp[i][1]:dp[i][0])+dist(i,j)。
写出来看起来好像也没那么复杂拉。不过要把这其中的所有关系理清楚也并不那么容易的。
1 # include <iostream> 2 # include <cstdio> 3 # include <cstring> 4 # include <algorithm> 5 using namespace std; 6 # define maxn 10100 7 int cnt,headlist[maxn],dp[maxn][3]; 8 9 struct node 10 { 11 int to,val,pre; 12 } edge[maxn]; 13 14 void add(int from,int to,int val) 15 { 16 cnt++; 17 edge[cnt].pre=headlist[from]; 18 edge[cnt].to=to; 19 edge[cnt].val=val; 20 headlist[from]=cnt; 21 return; 22 } 23 24 void dfs1(int pos) 25 { 26 int tem=headlist[pos]; 27 while(tem) 28 { 29 node e=edge[tem]; 30 int next=e.to,d=e.val; 31 dfs1(next); 32 dp[pos][1]=max(dp[pos][1],dp[next][0]+d); 33 if(dp[pos][0]<dp[pos][1]) swap(dp[pos][0],dp[pos][1]); 34 tem=e.pre; 35 } 36 return; 37 } 38 39 void dfs2(int pos,int fa,int d) 40 { 41 dp[pos][2]=max(dp[fa][2],dp[fa][0]-d==dp[pos][0]?dp[fa][1]:dp[fa][0])+d; 42 int tem=headlist[pos]; 43 while(tem) 44 { 45 node e=edge[tem]; 46 int next=e.to,d=e.val; 47 dfs2(next,pos,d); 48 tem=e.pre; 49 } 50 return; 51 } 52 53 int main(void) 54 { 55 int N; 56 while((scanf("%d",&N))!=EOF) 57 { 58 cnt=0; 59 memset(dp,0,sizeof(dp)); 60 memset(headlist,0,sizeof(headlist)); 61 for(int i=2;i<=N;i++) 62 { 63 int from,d; 64 scanf("%d%d",&from,&d); 65 add(from,i,d); 66 } 67 dfs1(1); 68 dfs2(1,0,0); 69 for(int i=1;i<=N;i++) printf("%d\n",max(dp[i][0],dp[i][2])); 70 } 71 return 0; 72 }
第二种方法建立在一个定理的基础上。过程就好想很多。
定义:树上距离最远的两点路径叫做树的直径。
定理:树上任意点的最远点必定是直径的两个端点之一。
证明的话。反证。假设一点的最远点p不是直径。
那么p到直径两端点的距离至少有一个大于直径。与直径的定义矛盾。
那么从这个定理出发我们得到这样的做法。
先从任意一点出发dfs一次找到最远点必为一个端点记为u。
从u出发dfs一次。路上记下所有点到u的距离。找到最远点v为直径的另一端点。
再从v出发dfs一次。路上记下所有点到v的距离。
那么所有点的最长路两者取最大即可。
1 # include <iostream> 2 # include <cstdio> 3 # include <cstring> 4 # include <algorithm> 5 using namespace std; 6 # define maxn 10100 7 int tem,u,v,cnt,headlist[maxn],dp[maxn][3]; 8 9 struct node 10 { 11 int to,val,pre; 12 } edge[maxn*2]; 13 14 void add(int from,int to,int val) 15 { 16 cnt++; 17 edge[cnt].pre=headlist[from]; 18 edge[cnt].to=to; 19 edge[cnt].val=val; 20 headlist[from]=cnt; 21 return; 22 } 23 24 void dfs1(int pos,int fa) 25 { 26 if(dp[pos][0]>tem) 27 { 28 tem=dp[pos][0]; 29 u=pos; 30 } 31 for(int i=headlist[pos];i;i=edge[i].pre) 32 { 33 int to=edge[i].to,d=edge[i].val; 34 if(to==fa) continue; 35 dp[to][0]=dp[pos][0]+d; 36 dfs1(to,pos); 37 } 38 return; 39 } 40 41 void dfs2(int pos,int fa) 42 { 43 if(dp[pos][1]>tem) 44 { 45 tem=dp[pos][1]; 46 v=pos; 47 } 48 for(int i=headlist[pos];i;i=edge[i].pre) 49 { 50 int to=edge[i].to,d=edge[i].val; 51 if(to==fa) continue; 52 dp[to][1]=dp[pos][1]+d; 53 dfs2(to,pos); 54 } 55 return; 56 } 57 58 void dfs3(int pos,int fa) 59 { 60 for(int i=headlist[pos];i;i=edge[i].pre) 61 { 62 int to=edge[i].to,d=edge[i].val; 63 if(to==fa) continue; 64 dp[to][2]=dp[pos][2]+d; 65 dfs3(to,pos); 66 } 67 return; 68 } 69 70 int main(void) 71 { 72 int N; 73 while((scanf("%d",&N))!=EOF) 74 { 75 cnt=0; 76 memset(dp,0,sizeof(dp)); 77 memset(headlist,0,sizeof(headlist)); 78 for(int i=2;i<=N;i++) 79 { 80 int j,d; 81 scanf("%d%d",&j,&d); 82 add(j,i,d); 83 add(i,j,d); 84 } 85 tem=-1; dfs1(1,0); 86 tem=-1; dfs2(u,0); 87 dfs3(v,0); 88 for(int i=1;i<=N;i++) printf("%d\n",max(dp[i][1],dp[i][2])); 89 } 90 return 0; 91 }
8.4
POJ 1849 Two
都没看出这是一个直径题呢QAQ。
因为两个车遍历整棵树回原点的话走过的是所有边权和的两倍。
但是不用回原点。所以让他们留在最远的位置就好了。
最远的就是直径的端点。
所有边权和的两倍减直径就是答案。
1 # include <iostream> 2 # include <cstdio> 3 # include <cstring> 4 # include <algorithm> 5 using namespace std; 6 # define maxn 100100 7 int u,Max,cnt,headlist[maxn],dp[maxn][2]; 8 9 struct node 10 { 11 int to,val,pre; 12 } edge[maxn*2]; 13 14 void add(int from,int to,int val) 15 { 16 cnt++; 17 edge[cnt].pre=headlist[from]; 18 edge[cnt].to=to; 19 edge[cnt].val=val; 20 headlist[from]=cnt; 21 return; 22 } 23 24 void dfs1(int pos,int fa) 25 { 26 if(dp[pos][0]>Max) 27 { 28 Max=dp[pos][0]; 29 u=pos; 30 } 31 for(int i=headlist[pos];i;i=edge[i].pre) 32 { 33 int to=edge[i].to,d=edge[i].val; 34 if(to==fa) continue; 35 dp[to][0]=dp[pos][0]+d; 36 dfs1(to,pos); 37 } 38 return; 39 } 40 41 void dfs2(int pos,int fa) 42 { 43 Max=max(Max,dp[pos][1]); 44 for(int i=headlist[pos];i;i=edge[i].pre) 45 { 46 int to=edge[i].to,d=edge[i].val; 47 if(to==fa) continue; 48 dp[to][1]=dp[pos][1]+d; 49 dfs2(to,pos); 50 } 51 return; 52 } 53 54 int main(void) 55 { 56 int N,S; 57 while((scanf("%d%d",&N,&S))!=EOF) 58 { 59 cnt=0; 60 memset(headlist,0,sizeof(headlist)); 61 memset(dp,0,sizeof(dp)); 62 int sum=0; 63 for(int i=1;i<N;i++) 64 { 65 int a,b,c; 66 scanf("%d%d%d",&a,&b,&c); 67 sum+=c; 68 add(a,b,c); 69 add(b,a,c); 70 } 71 Max=0; dfs1(S,0); 72 Max=0; dfs2(u,0); 73 printf("%d\n",2*sum-Max); 74 } 75 return 0; 76 }
HDU 4003 Find Metal Mineral
在做上一题的时候看到了这题。据说是上一题的加强版。
结果还是不会做嘛。原来这个可以变成树形分组背包做。
于是成为了遇见的第一个树形背包。
定义每个节点i投放j个机器人到子树完成任务的最小距离为dp[i][j]。
这里的投放是指放下去就不回来的机器人。
也就是说如果只用一个机器人遍历子树再回来的话。就是dp[i][0]。
每个节点都有投放0个(用一个遍历再回来),投放1个,…… ,投放k个。这些情况。
把这些情况都看作物品。那么每个节点只能取一件物品。就成了分组背包。
但是这里还有一个限制。就是必须取一件。
为了防止出现一件不取的情况。可以先把dp[son][0]放进去。
只要后面有更好的情况。就会自动把它取出来。
放dp[son][0]是有好处的。因为如果投放t个机器人。它对距离的贡献是dp[son][t]+dist*t。
而取dp[son][0]的贡献是dp[son][0]+dist*2。正好是一个特例。
树形加背包整个dp完成以后。dp[s][k]即为答案。
1 # include <iostream> 2 # include <cstdio> 3 # include <cstring> 4 # include <algorithm> 5 using namespace std; 6 # define maxn 10100 7 int N,S,K,cnt,headlist[maxn],dp[maxn][11]; 8 9 struct node 10 { 11 int to,val,pre; 12 } edge[maxn*2]; 13 14 void add(int from,int to,int val) 15 { 16 cnt++; 17 edge[cnt].pre=headlist[from]; 18 edge[cnt].to=to; 19 edge[cnt].val=val; 20 headlist[from]=cnt; 21 return; 22 } 23 24 void dfs(int pos,int fa) 25 { 26 for(int i=headlist[pos];i;i=edge[i].pre) 27 { 28 int to=edge[i].to,d=edge[i].val; 29 if(to==fa) continue; 30 dfs(to,pos); 31 for(int j=K;j>=0;j--) 32 { 33 dp[pos][j]=dp[pos][j]+dp[to][0]+2*d; 34 for(int k=1;k<=j;k++) 35 dp[pos][j]=min(dp[pos][j],dp[pos][j-k]+dp[to][k]+k*d); 36 } 37 } 38 return; 39 } 40 41 int main(void) 42 { 43 while((scanf("%d%d%d",&N,&S,&K))!=EOF) 44 { 45 cnt=0; 46 memset(headlist,0,sizeof(headlist)); 47 memset(dp,0,sizeof(dp)); 48 for(int i=1;i<N;i++) 49 { 50 int x,y,w; 51 scanf("%d%d%d",&x,&y,&w); 52 add(x,y,w); 53 add(y,x,w); 54 } 55 dfs(S,0); 56 printf("%d\n",dp[S][K]); 57 } 58 return 0; 59 }
下午打多校。
现在我们打多校基本是这个状态的。
开始前:今天有多校耶!千万不能弃疗了!
第一小时:先来切几个水题吧!
第二小时:唔……看看题……说不定有能做的呢……
第三小时:别人都出了些啥题阿……怎么都没有思路呢……来来来大家讨论一下……
第四小时:终于讨论出结果了!!!!!!我们还是弃疗吧。
第五小时:不行不行我还要抢救一下。万一出了呢。
赛后看题解:你看吧。题目也没有那么难吗。差一点点就出了。要是没有弃疗说不定就做出来了。所以下次千万不要弃疗啊!
不说了。补题了。
8.5
补了两个题。其他先放了。
继续树dp。
POJ 2486 Apple Tree
这题做完回过来想过程感觉是不容易的。而且网上就一个版本解答。
首先很容易想到的是在树上分组背包。但是涉及一个去完回不回的问题。于是要再加一个01标记。
dp[i][k][1/0]表示在节点i花费k回/不回的最大收益。
那么初始化所有点的收益是它的苹果数。
回的情况状态转移是容易的。
dp[pos][j][1]=max(dp[pos][j][1],dp[pos][j-k-2][1]+dp[to][k][1])
因为只有在子树上回了。在父节点才能回。
不好想的是不回的情况。不回的情况是这样的。
首先可以先走很多子树。但是每次都回父节点。
1.dp[pos][j][0]=max(dp[pos][j][0],dp[to][k][1]+dp[pos][j-k-2][0])
在最后一次走的时候。走到子树就不回了。
2.dp[pos][j][0]=max(dp[pos][j][0],dp[pos][j-k-1][1]+dp[to][k][0])
能想象的到。在更新的过程中中间会有很多方式1.表示走子树后回。
此时即便有些节点以方式2.更新了答案。最后也会被新的答案替代掉。
而最后一次以方式2.更新的才是这个节点的最大收益。
不回的收益一定比会大。最后答案是dp[1][K][0]。
1 # include <iostream> 2 # include <cstdio> 3 # include <cstring> 4 # include <algorithm> 5 using namespace std; 6 # define maxn 110 7 int cnt,headlist[maxn]; 8 int N,K,a[maxn],dp[maxn][2*maxn][2]; 9 10 struct node 11 { 12 int to,pre; 13 } edge[maxn*2]; 14 15 void add(int from,int to) 16 { 17 cnt++; 18 edge[cnt].pre=headlist[from]; 19 edge[cnt].to=to; 20 headlist[from]=cnt; 21 return; 22 } 23 24 void dfs(int pos,int fa) 25 { 26 for(int i=headlist[pos];i;i=edge[i].pre) 27 { 28 int to=edge[i].to; 29 if(to==fa) continue; 30 dfs(to,pos); 31 for(int j=K;j>=0;j--) 32 { 33 for(int k=0;k<=j-1;k++) 34 { 35 dp[pos][j][0]=max(dp[pos][j][0],dp[pos][j-k-1][1]+dp[to][k][0]); 36 if(j>=k+2) dp[pos][j][0]=max(dp[pos][j][0],dp[to][k][1]+dp[pos][j-k-2][0]); 37 if(j>=k+2) dp[pos][j][1]=max(dp[pos][j][1],dp[pos][j-k-2][1]+dp[to][k][1]); 38 } 39 } 40 } 41 return; 42 } 43 44 int main(void) 45 { 46 while((scanf("%d%d",&N,&K))!=EOF) 47 { 48 cnt=0; 49 memset(headlist,0,sizeof(headlist)); 50 for(int i=1;i<=N;i++) scanf("%d",a+i); 51 for(int i=1;i<=N;i++) 52 for(int j=0;j<=K;j++) 53 dp[i][j][0]=dp[i][j][1]=a[i]; 54 for(int i=1;i<N;i++) 55 { 56 int A,B; 57 scanf("%d%d",&A,&B); 58 add(A,B); 59 add(B,A); 60 } 61 dfs(1,0); 62 printf("%d\n",dp[1][K][0]); 63 } 64 return 0; 65 }
POJ 3417 Network
好难想。
把每个节点(除根)和它父节点方向的边联系起来。
就能在节点上传递边的信息。
再把问题转化为每条边可以被几个环覆盖的问题。
要先做LCA。再树DP。见题解。
然而最大的坑在于。新加的边有自环……
1 # include <iostream> 2 # include <cstdio> 3 # include <cstring> 4 # include <vector> 5 using namespace std; 6 # define maxn 100010 7 int dp[maxn]; 8 int cnt,headlist[maxn]; 9 int father[maxn],vis[maxn]; 10 vector <int> query[maxn]; 11 12 struct node 13 { 14 int to,pre; 15 } edge[maxn*2]; 16 17 void add(int from,int to) 18 { 19 cnt++; 20 edge[cnt].pre=headlist[from]; 21 edge[cnt].to=to; 22 headlist[from]=cnt; 23 return; 24 } 25 26 int Find(int x) 27 { 28 return father[x]==x?x:father[x]=Find(father[x]); 29 } 30 31 void LCA(int pos,int fa) 32 { 33 for(int i=headlist[pos];i;i=edge[i].pre) 34 { 35 int to=edge[i].to; 36 if(fa==to) continue; 37 LCA(to,pos); 38 father[to]=pos; 39 } 40 vis[pos]=1; 41 for(int i=0;i<query[pos].size();i++) 42 { 43 int to=query[pos][i]; 44 if(vis[to]) 45 { 46 dp[to]++; dp[pos]++; 47 dp[Find(to)]-=2; 48 } 49 } 50 return; 51 } 52 53 void dfs(int pos,int fa) 54 { 55 for(int i=headlist[pos];i;i=edge[i].pre) 56 { 57 int to=edge[i].to; 58 if(fa==to) continue; 59 dfs(to,pos); 60 dp[pos]+=dp[to]; 61 } 62 return; 63 } 64 65 int main(void) 66 { 67 int N,M; 68 while((scanf("%d%d",&N,&M))!=EOF) 69 { 70 cnt=0; 71 memset(dp,0,sizeof(dp)); 72 memset(vis,0,sizeof(vis)); 73 memset(headlist,0,sizeof(headlist)); 74 for(int i=1;i<=N;i++) father[i]=i; 75 for(int i=1;i<=N;i++) query[i].clear(); 76 for(int i=1;i<N;i++) 77 { 78 int a,b; 79 scanf("%d%d",&a,&b); 80 add(a,b); 81 add(b,a); 82 } 83 for(int i=1;i<=M;i++) 84 { 85 int a,b; 86 scanf("%d%d",&a,&b); 87 query[a].push_back(b); 88 query[b].push_back(a); 89 } 90 LCA(1,0); 91 dfs(1,0); 92 int ans=0; 93 for(int i=2;i<=N;i++) 94 { 95 if(dp[i]==0) ans+=M; 96 else if(dp[i]==1) ans++; 97 } 98 printf("%d\n",ans); 99 } 100 return 0; 101 }
下午打多校。感觉对一些简单的问题不敏感。
没什么长进。补题。
8.7
昨天多校的1003spj错了。
导致比赛时错误方法过了。
赛后看还是写不出。
觉得基础差。先不补了。
花点时间过下紫薯7、8章基础部分。
HDU 2553 N皇后问题
简单回溯。紫薯方法。
1 # include <iostream> 2 # include <cstdio> 3 using namespace std; 4 int tot,ans[11],vis[3][21]; 5 6 void search(int n,int cur) 7 { 8 if(cur==n+1) {tot++;return;} 9 for(int i=1;i<=n;i++) 10 { 11 if(!vis[0][i]&&!vis[1][cur+i]&&!vis[2][cur-i+n]) 12 { 13 vis[0][i]=vis[1][cur+i]=vis[2][cur-i+n]=1; 14 search(n,cur+1); 15 vis[0][i]=vis[1][cur+i]=vis[2][cur-i+n]=0; 16 } 17 } 18 return; 19 } 20 21 int main(void) 22 { 23 for(int i=1;i<=10;i++) 24 { 25 tot=0; 26 search(i,1); 27 ans[i]=tot; 28 } 29 int N; 30 while(~scanf("%d",&N)&&N) 31 printf("%d\n",ans[N]); 32 return 0; 33 }
UVA 129 Krypton Factor
回溯时注意减少不必要的判断。
输出格式注意下。小心PE。
1 # include <iostream> 2 # include <cstdio> 3 # include <cstring> 4 using namespace std; 5 int n,L,ans[100],cnt; 6 7 void ans_print(int cur) 8 { 9 for(int i=1;i<=cur;i++) 10 { 11 printf("%c",'A'+ans[i]); 12 if(i%64==0&&i!=cur) printf("\n"); 13 else if(i%4==0&&i!=cur) printf(" "); 14 } 15 printf("\n%d\n",cur); 16 return; 17 } 18 19 void dfs(int cur) 20 { 21 for(int i=0;i<L;i++) 22 { 23 ans[cur]=i; 24 int ok=1; 25 for(int j=cur-1;j>0;j-=2) 26 if(!memcmp(ans+j,ans+(j+cur+1)/2,(cur-j+1)/2*sizeof(int))) 27 {ok=0; break;} 28 if(!ok) continue; 29 cnt++; 30 if(cnt==n) {ans_print(cur);return;} 31 dfs(cur+1); 32 if(cnt==n) return; 33 } 34 return; 35 } 36 37 int main(void) 38 { 39 while(~scanf("%d%d",&n,&L)&&n&&L) 40 cnt=0,dfs(1); 41 return 0; 42 }
8.8
UVA 140 Bandwidth
主要是练习一下剪枝。
输入格式有点点恶。
1 # include <iostream> 2 # include <cstdio> 3 # include <cstring> 4 # include <algorithm> 5 # include <vector> 6 using namespace std; 7 vector <int> edge[30]; 8 int node,Min,vis[30],tem[10],ans[10]; 9 char s[100]; 10 11 void ans_print(void) 12 { 13 for(int i=1;i<=node;i++) 14 printf("%c ",ans[i]+'A'); 15 printf("-> %d\n",Min); 16 return; 17 } 18 19 void dfs(int cur,int dis) 20 { 21 if(cur==node+1) 22 { 23 if(dis<Min) 24 { 25 Min=dis; 26 memcpy(ans,tem,sizeof(ans)); 27 } 28 return; 29 } 30 for(int i=0;i<26;i++) 31 { 32 if(!vis[i]) 33 { 34 tem[cur]=i; 35 int tem_dis=0; 36 for(int j=0;j<edge[i].size();j++) 37 { 38 int to=edge[i][j]; 39 if(vis[to]>0) tem_dis=max(tem_dis,cur-vis[to]); 40 if(tem_dis>=Min) break; 41 } 42 if(tem_dis>=Min) continue; 43 vis[i]=cur; 44 dfs(cur+1,max(dis,tem_dis)); 45 vis[i]=0; 46 } 47 } 48 return; 49 } 50 51 int main(void) 52 { 53 while(~scanf("%s",s)) 54 { 55 if(s[0]=='#') break; 56 for(int i=0;i<26;i++) edge[i].clear(); 57 int len=strlen(s),pos=s[0]-'A'; 58 memset(vis,-1,sizeof(vis)); 59 vis[pos]=0; node=1; 60 for(int i=2;i<len;i++) 61 { 62 if(s[i]==':') continue; 63 if(s[i]==';') 64 { 65 pos=s[i+1]-'A'; 66 if(vis[pos]) node++; 67 vis[pos]=0; 68 i+=2; 69 continue; 70 } 71 if(vis[s[i]-'A']) node++; 72 vis[s[i]-'A']=0; 73 edge[pos].push_back(s[i]-'A'); 74 edge[s[i]-'A'].push_back(pos); 75 } 76 Min=10; 77 dfs(1,0); 78 ans_print(); 79 } 80 return 0; 81 }
晚上BC。
HDU 5366 The mook jong
想复杂了。官方题解很简便。
我是分成三种状态。
dp[n][1]表示最右边的桩在最右的格子。
dp[n][2]表示最右边的桩在倒数第二个格子。
dp[n][3]表示最右边的桩既不在最右也不在倒数第二个。
转移见代码- -。
1 # include <iostream> 2 # include <cstdio> 3 # include <cstring> 4 # include <algorithm> 5 using namespace std; 6 typedef long long LL; 7 LL dp[61][4],sum[61]; 8 9 int main(void) 10 { 11 dp[1][1]=1; 12 dp[2][1]=dp[2][2]=1; 13 dp[3][1]=dp[3][2]=dp[3][3]=1; 14 sum[1]=1;sum[2]=2;sum[3]=3; 15 for(int i=4;i<=60;i++) 16 { 17 dp[i][1]=dp[i-1][3]+1; 18 dp[i][2]=dp[i-1][1]; 19 dp[i][3]=dp[i-1][3]+dp[i-1][2]; 20 sum[i]=dp[i][1]+dp[i][2]+dp[i][3]; 21 } 22 int n; 23 while(~scanf("%d",&n)) 24 printf("%I64d\n",sum[n]); 25 return 0; 26 }
待补。