第一题:最长链
题目描述:给定一棵有n个节点的树,求每个节点到其他节点的最大距离
输入:第一行是一个自然数n(n≤10000),接下来 (n−1)行描述: 第i行包含两个自然数, 表示编号为i的节点连接到的节点编号和这条网线的长度..距离总长不会超过10^9.每行中的两个数字用空格隔开.
输出:包含n行.第i行表示对于离编号为i的节点最远的节点与该节点的距离Si(1≤i≤n).
成绩:AC
题解:g[x][0]和g[x][1]表示以1为根的树中,x子树中的最长链和次长链(不能有相同的边)dp[x][0],dp[x][1]表示整棵树中过x的最远距离和次远距离(也不能有相同的边)。
(方程式情况难以描述,就不打了23333333)
P.s:法二:树的直径。
分析:前一天刚讲过,还是写得很快的~\(≧▽≦)/~啦啦啦。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<ctime> #include<stack> #include<algorithm> #include<queue> #include<vector> #include<map> using namespace std; const int N=10000+10; inline void getint(int&num){ char c;int flag=1;num=0; while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1; while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();} num*=flag; } struct bian{ int v,w,next; }arr[N<<1]; int n,a,w,cnt,fir[N],g[N][2],dp[N][2],tmp[7]; inline void link(int a,int b,int w){ arr[++cnt].v=a,arr[cnt].w=w; arr[cnt].next=fir[b],fir[b]=cnt; } void work(int x,int pre){ for(int i=fir[x];i;i=arr[i].next) if(arr[i].v!=pre){ work(arr[i].v,x); if(g[arr[i].v][0]+arr[i].w>g[x][0]) g[x][1]=g[x][0],g[x][0]=g[arr[i].v][0]+arr[i].w; else if(g[arr[i].v][0]+arr[i].w>g[x][1]) g[x][1]=g[arr[i].v][0]+arr[i].w; } } void dfs(int x,int pre,int w){ tmp[1]=tmp[2]=tmp[3]=tmp[4]=0; if(dp[pre][0]==g[x][0]+w){ tmp[1]=g[x][0],tmp[2]=g[x][1]; tmp[3]=dp[pre][1]+w; sort(tmp+1,tmp+4); dp[x][0]=tmp[3],dp[x][1]=tmp[2]; } else if(dp[pre][1]==g[x][0]+w){ tmp[1]=g[x][0],tmp[2]=g[x][1]; tmp[3]=dp[pre][0]+w; sort(tmp+1,tmp+4); dp[x][0]=tmp[3],dp[x][1]=tmp[2]; } else{ tmp[1]=g[x][0],tmp[2]=g[x][1]; tmp[3]=dp[pre][0]+w; tmp[4]=dp[pre][1]+w; sort(tmp+1,tmp+5); dp[x][0]=tmp[4],dp[x][1]=tmp[3]; } for(int i=fir[x];i;i=arr[i].next) if(arr[i].v!=pre) dfs(arr[i].v,x,arr[i].w); } int main(){ getint(n); for(int i=2;i<=n;i++){ getint(a),getint(w); link(i,a,w),link(a,i,w); } work(1,0),dfs(1,0,0); for(int i=1;i<=n;i++) printf("%d\n",dp[i][0]); }
第二题:比赛转播
题目描述:一个电视网络计划转播一场重要的足球比赛。网络中的传输点和接收点(即用户)可以表示一棵树。这棵树的根是一个传输点,它将转播比赛。树的叶节点是可能要接受这场比赛的用户(他当然可以选择不看比赛,这样就不要交款)。其他非根节点,非叶节点的中间节点为数据的中转站。将一个信号从一个传输点传到另一个传输点的花费是给定的。整个转播的费用就是每一个传输费用的总和。每一个用户(叶节点)都准备付一定的钱来看这场比赛。电视网络公司要决定是否要给这个用户提供电视信号。例如:给一个节点传输信息的花费太大,而他愿意的付款也很少时,网络公司可能选择不给他转播比赛。写一个程序,找到一个传输方案使最多的用户能看到转播比赛,且转播的费用不超过所有接收信号用户的交款。
输入:输入文件的第一行包含两个整数N和M(2<=N<=3000,1<=M<=N-1)。N,M表示分别表示树的节点数和想观看比赛的用户数。树的根节点用1表示,中间节点的标号为2~N-M,用户的节点标号为N-M+1~N。接下来的N-M行表示传输点的信息(依次是节点1,2……):K A1 C1 A2 C2 ……Ak Ck 表示一个传输点将信号传给K个用户,每一个包含两个数A和C,A表示传输点或用户的节点号,C表示传输的花费。最后一行含有用户的数据,有M个整数表示他们看这场比赛愿意的付费。
输出格式:仅一行包含一个整数,最大的用户数。
成绩:AC
题解:dp[i][j]表示i子树中选j户的最大收益(多叉树转二叉树)
P.s:法二:背包。
分析:由于背包法理解得不是很清楚(~@_@~),只好多叉树转二叉树,然而明明昨天还弄了半天没调出来,今天可能状态来了吧。。。(2333333333)(qwq。。。。2333333333333333333~\(≧▽≦)/~啦啦啦)
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<ctime> #include<stack> #include<algorithm> #include<queue> #include<vector> #include<map> using namespace std; const int N=3000+10; inline void getint(int&num){ char c;int flag=1;num=0; while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1; while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();} num*=flag; } struct bian{ int v,next; }arr[N<<1]; struct node{ int l,r; }tree[N<<1]; int T,n,m,cnt,a,w,fir[N],A[N],son[N],sum[N],dp[N][N]; bool vis[N][N]; inline void link(int a,int b){ arr[++cnt].v=a,arr[cnt].next=fir[b],fir[b]=cnt; } void Son(int x){ if(!x) return ; Son(tree[x].l),Son(tree[x].r); son[x]=son[tree[x].l]+son[tree[x].r]+(x>n-m); } void Sum(int x){ if(!x) return ; Sum(tree[x].l),Sum(tree[x].r); sum[x]=sum[tree[x].l]+sum[tree[x].r]+A[x]; } void dfs(int x,int k){ if(vis[x][k]) return ; vis[x][k]=1; int tmp=-0x7f7f7f7f; if(k==son[x]) tmp=sum[x]; else if(!k) tmp=0; else if(!tree[x].l&&!tree[x].r) tmp=A[x]; else if(!tree[x].l){ if(son[tree[x].r]>=k) dfs(tree[x].r,k),tmp=max(tmp,dp[tree[x].r][k]); if(son[tree[x].r]>=k-1&&x>n-m) dfs(tree[x].r,k-1),tmp=max(tmp,dp[tree[x].r][k-1]+A[x]); } else if(!tree[x].r){ if(son[tree[x].l]>=k) dfs(tree[x].l,k),tmp=max(tmp,dp[tree[x].l][k]+A[x]); } else{ if(son[tree[x].r]>=k) dfs(tree[x].r,k),tmp=max(tmp,dp[tree[x].r][k]); for(int i=1;i<=k;i++){ if(son[tree[x].l]<i||son[tree[x].r]<k-i) continue ; dfs(tree[x].l,i),dfs(tree[x].r,k-i); tmp=max(tmp,dp[tree[x].l][i]+dp[tree[x].r][k-i]+A[x]); } } dp[x][k]=tmp; } int main(){ getint(n),getint(m); for(int i=1;i<=n-m;i++){ getint(T); while(T--){ getint(a),getint(A[a]); link(a,i),A[a]=-A[a]; } } for(int i=n-m+1;i<=n;i++) getint(w),A[i]+=w; for(int i=1;i<=n;i++){ tree[i].l=arr[fir[i]].v; int now=tree[i].l; for(int j=arr[fir[i]].next;j;j=arr[j].next) tree[now].r=arr[j].v,now=arr[j].v; } Son(1),Sum(1); for(int i=1;i<=m;i++) dfs(1,i); for(int i=m;i>=0;i--) if(dp[1][i]>=0){ printf("%d\n",i);return 0; } putchar(48),putchar(10); return 0; }
第三题:叶子的颜色(color)
题目描述:给一棵m个结点的无根树,你可以选择一个度数大于1的结点作为根,然后给一些结点(根、内部结点和叶子均可)着以黑色或白色。你的着色方案应该保证根结点到每个叶子的简单路径上都至少包含一个有色结点(哪怕是这个叶子本身)。
对于每个叶结点u,定义c[u]为从根结点到u的简单路径上最后一个有色结点的颜色。给出每个c[u]的值,设计着色方案,使得着色结点的个数尽量少。
输入:第一行包含两个正整数m, n,其中n是叶子的个数,m是结点总数。结点编号为1,2,…,m,其中编号1,2,… ,n是叶子。以下n行每行一个0或1的整数(0表示黑色,1表示白色),依次为c[1],c[2],…,c[n]。以下m-1行每行两个整数a,b(1<=a < b <= m),表示结点a和b有边相连。
输出:仅一个数,即着色结点数的最小值。
成绩:。。。。。。。。。。(TAT)
题解:因为显然染色染在越上面,影响的结点越多,答案就越优,所以从根开始向下,他的子树中黑色多就染黑色,白色多就染白色(一样都可以用2表示)一边dfs一边记录颜色,
Dp[i]等于儿子dp的和减去子树中与i同色的个数(包括2)(i染色了他的子树就不用染了)。
分析:其实第一反应就是差不多的贪心,但是后来不知道为什么否掉了((⊙ o ⊙ )!),后来一直在分情况dp(本来分情况dp只需要分当前节点的染色情况就行了,然而我一直在纠结不能与父辈节点染同样的颜色,就用了一维来表示最近染了色父辈节点的颜色,然后绕着绕着把自己重新绕进去了(⊙ o ⊙ )!&(⊙o⊙),不知道自己在搞什么鬼)
P.s:dp法: dp[i][0]=sigma(min(dp[j][0]-1,dp[j][1],dp[j][2]))+1
dp[i][1]=sigma(min(dp[j][0],dp[j][1]-1,dp[j][2]))+1
dp[i][2]=sigma(min(dp[j][0],dp[j][1],dp[j][2]));
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<ctime> #include<stack> #include<algorithm> #include<queue> #include<vector> #include<map> using namespace std; const int N=10000+10; inline void getint(int&num){ char c;int flag=1;num=0; while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1; while(c>='0'&&c<='9'){num=num*10+c-48;c=getchar();} num*=flag; } struct bian{ int v,next; }arr[N<<1]; int n,m,cnt,a,b,sum[2],dp[N],fir[N],c[N]; bool vis[N]; inline void link(int a,int b){ arr[++cnt].v=a,arr[cnt].next=fir[b],fir[b]=cnt; } void dfs(int x,int pre){ if(c[x]!=-1) dp[x]=1; else{ int tot[3]; tot[0]=tot[1]=tot[2]=0; for(int i=fir[x];i;i=arr[i].next) if(arr[i].v!=pre) dfs(arr[i].v,x),tot[c[arr[i].v]]++,dp[x]+=dp[arr[i].v]; if(tot[0]==tot[1]) c[x]=2; else c[x]=tot[0]>tot[1]?0:1; dp[x]-=max(tot[0],tot[1])+tot[2]-1; } } int main(){ getint(n),getint(m); for(int i=1;i<=n;i++) c[i]=-1; for(int i=1;i<=m;i++) getint(c[i]),sum[c[i]]++; for(int i=1;i<n;i++){ getint(a),getint(b); link(a,b),link(b,a); } if(!sum[0]||!sum[1]){ printf("1\n");return 0; } dfs(n,-1),printf("%d\n",dp[n]); return 0; }
总结:一二题很简单,写得也挺快的,(~\(≧▽≦)/~啦啦啦)然后第三题时剩的时间也很多,最后深深陷入无法自拔(⊙o⊙)233333,做作业一定要有效率(说不定就撞原题了233-QwQ-)如果一个思路太绕太久没有头绪,最好尽快换一个(QnQ&TAT),dp的考试里也是可以贪心的(2333333),总之第二题过了还是很开心的~\(≧▽≦)/~啦啦啦(忠心祝愿这种神奇的状态多来几次\(^o^)/~)
又及:清明节快乐!