首先说明一下,我是一个刚刚进入oi界不到半年的蒟蒻——半年来,我是仅仅凭着兴趣和决心,在校队的一群dalao中瑟瑟发抖。我做的很多题目都是“苟”出来的,就比如说这次模拟赛中的“联合权值”,仅凭暴力只坑了40分。
现在就从这道题目开始分析吧。题目如下:
标题: 联合权值
详情:
输入格式:
第一行包含 1 个整数 n。 接下来 n-1 行,每行包含 2 个用空格隔开的正整数 u、v,表示编号为 u 和编号为 v 的点 之间有边相连。 最后 1 行,包含 n 个正整数,每两个正整数之间用一个空格隔开,其中第 i 个整数表示 图 G 上编号为 i 的点的权值为 Wi。
输出格式:
输出共 1 行,包含 2 个整数,之间用一个空格隔开,依次为图 G 上联合权值的最大值 和所有联合权值之和。由于所有联合权值之和可能很大,输出它时要对 10007 取余。
限制: 对于 30%的数据,1 < ? ≤ 100;
对于 60%的数据,1 < ? ≤ 2000;
对于 100%的数据,1 < ? ≤ 200,000,0 < ?! ≤ 10,000。
样例:
输入
5
1 2
2 3
3 4
4 5
1 5 2 3 1 0
1 2
2 3
3 4
4 5
1 5 2 3 1 0
输出
20 74
解释
本例输入的图如上所示,距离为 2 的有序点对有(1,3)、(2,4)、(3,1)、(3,5)、(4,2)、(5,3)。 其联合权值分别为 2、15、2、20、15、20。其中最大的是 20,总和为 74。
-----------------华丽的分界线---------------------
我自己做的(40分的)(超时60%)代码如下:
1 #include <cstdio> 2 #define N 200001 3 struct enode { 4 int to; 5 enode *next; 6 }; 7 8 struct vnode { 9 int form; 10 enode *first; 11 } v[N]; 12 13 int main() { 14 int k,i,j,w,n,xx[N],maxx=0,sum=0,tmp; 15 scanf("%d",&n); 16 for (k=1;k<=n-1;k++) { 17 scanf("%d%d",&i,&j); 18 enode *p=new enode(); 19 p->to=j; 20 p->next=v[i].first; 21 v[i].first=p; 22 enode *q=new enode(); 23 q->to=i; 24 q->next=v[j].first; 25 v[j].first=q; 26 } 27 for (i=1;i<=n;i++) scanf("%d",&xx[i]); 28 for (i=1;i<=n;i++) { 29 for (enode *qq=v[i].first;qq!=NULL;qq=qq->next) { 30 for (enode *pp=v[qq->to].first;pp!=NULL;pp=pp->next) { 31 if (i!=pp->to) { 32 //printf("%d %d\n",i,pp->to); 33 tmp=xx[i]*xx[pp->to]; 34 if (maxx<tmp) maxx=tmp; 35 sum+=tmp; 36 } 37 } 38 } 39 } 40 printf("%d %d",maxx,sum); 41 return 0; 42 }
可见,该代码不仅硬套模板,而且花了大半的时间重复搜索,这也就是它只得了20分的原因。
那么,正确的解法如何呢?
因为题目中的图是一棵树,所以可以用广搜来搜索每个节点的父节点和子节点,将其两两匹配,再将所有的子节点两两匹配。以数据
5
1 2
2 3
3 4
4 5
1 5 2 3 1 0
1 2
2 3
3 4
4 5
1 5 2 3 1 0
为例
gw.x,表示节点编号 | 1 | 2 | 3 | 4 | 5 |
gw.f,表示父节点 | 0 | 1 | 2 | 3 | 4 |
顺序是,1-3,、2-4、3-5
然后将值乘起来,算得结果(60分无误)
不过这道题的优化可以使用dp,思路如下 (怎么竟然是dp我的天啊)
伪代码:
先存节点:
1 -2 -3
2 -1 -4 -5
3 -1 -6 -7
4 -2
5 -2
6 -3
7 -3
边:2-3, 1-4, 1-5, 4-5, 1-6, 1-7, 2-7
两两比较,在n个顶点找最大值与次大值,时间复杂度O(n^2)
——但是,若第一顶点就有n个顶点,那么这个时间复杂度就不成立;事实上,时间复杂度不可能为n^2,而是没有那么复杂。
综上,这tm就是一道彻彻底底的数学题!根本就和图论没有半毛钱关系!!!
标程如下:
1 #include<map> 2 #include<set> 3 #include<cmath> 4 #include<stack> 5 #include<queue> 6 #include<cstdio> 7 #include<vector> 8 #include<cstring> 9 #include<cstdlib> 10 #include<iostream> 11 #include<algorithm> 12 #define mod 10007 13 #define pi acos(-1) 14 #define inf 0x7fffffff 15 #define ll long long 16 using namespace std; 17 ll read() 18 { 19 ll x=0,f=1;char ch=getchar(); 20 while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} 21 while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} 22 return x*f; 23 } 24 int n; 25 ll w[200005],mx[200005],sum[200005]; 26 ll ans1[200005],ans2[200005]; 27 vector<int>e[200005]; 28 void dp(int x,int fa) 29 { 30 for(int i=0;i<e[x].size();i++) 31 { 32 int y=e[x][i]; 33 if(y==fa)continue; 34 dp(y,x); 35 mx[x]=max(mx[x],w[y]); 36 sum[x]=(sum[x]+w[y])%mod; 37 ans1[x]=max(ans1[x],w[x]*mx[y]); 38 ans1[x]=max(ans1[x],ans1[y]); 39 ans2[x]=(ans2[x]+w[x]*sum[y])%mod; 40 ans2[x]=(ans2[x]+ans2[y])%mod; 41 } 42 ll t1=0,t2=0; 43 for(int i=0;i<e[x].size();i++) 44 { 45 int y=e[x][i]; 46 if(y==fa)continue; 47 ans1[x]=max(ans1[x],t1*w[y]); 48 ans2[x]=(ans2[x]+t2*w[y])%mod; 49 t1=max(t1,w[y]); 50 t2=(t2+w[y])%mod; 51 } 52 } 53 int main() 54 { 55 n=read(); 56 for(int i=1;i<n;i++) 57 { 58 int u=read(),v=read(); 59 e[u].push_back(v); 60 e[v].push_back(u); 61 } 62 for(int i=1;i<=n;i++) 63 { 64 w[i]=read(); 65 w[i]%=mod; 66 } 67 dp(1,0); 68 cout<<ans1[1]<<' '<<ans2[1]*2%mod<<endl;; 69 return 0; 70 }
这道题由老师给出的标程,更加简洁:
1 #include<cstdio> 2 #include<vector> 3 #define modd 10007 4 using namespace std; 5 6 int w[200006],n,x,y,max1,k1,k2,l,tot,s[200006],sum; 7 vector<int> a[200006]; 8 9 int main() 10 { 11 scanf("%d",&n); 12 for(int i=1;i<n;i++) 13 { 14 scanf("%d%d",&x,&y); 15 a[x].push_back(y); 16 a[y].push_back(x); 17 } 18 for(int i=1;i<=n;i++) scanf("%d",&w[i]); 19 for(int j=1;j<=n;j++) 20 if(a[j].size()>1) 21 { 22 k1=k2=0; 23 for(int i=0;i<a[j].size();i++) 24 { 25 if(w[a[j][i]]>k1) 26 { 27 k2=k1;k1=w[a[j][i]]; //k2=k1一定要写 28 } 29 else if(w[a[j][i]]>k2) k2=w[a[j][i]]; 30 s[j]+=w[a[j][i]];s[j]%=modd; 31 } 32 l=k1*k2; 33 if(l>max1) 34 { 35 max1=l;continue; 36 } 37 } 38 for(int i=1;i<=n;i++) 39 if(a[i].size()>1) 40 for(int j=0;j<a[i].size();j++) 41 { 42 sum+=w[a[i][j]]*(s[i]-w[a[i][j]]); 43 sum%=modd; 44 } 45 printf("%d %d\n",max1,sum); 46 return 0; 47 }
网上某dalao的回答:动规题无误。思路:
考虑树形dp:
一个结点距离相差2的点要不然是儿子的儿子,不然是兄弟
先考虑第一部分
只要记录一个结点儿子的权值和sum[x],以及权值的最大值mx[x]
ans1x=max{mxyi∗wx}
ans2x=∑sumyi∗wx
总之,dp什么的都去死吧。
最怕dp ( Difficult Problem )