题意:
给n对朋友安排住所,每人住在一个房子中,每个房子仅住一人,所有2n个房屋形成一棵树,边权表示相邻房屋之间的距离,分别求每对朋友所在房子距离之和的最小值和最大值 ( n ≤ 1 0 5 ) (n\le 10^5) (n≤105)。
解法:
考虑每条边的贡献,由树的性质,每条边连接着两个连通块 C A C_A CA和 C B C_B CB。
若求最小值,每条边最多被计算一次,假设该边被计算了两次以上,即存在 u A 、 v A ∈ C A , u B 、 v B ∈ C B u_A、v_A\in C_A,u_B、v_B\in C_B uA、vA∈CA,uB、vB∈CB且 u A u_A uA与 u B u_B uB配对, v A v_A vA与 v B v_B vB配对。若变换配对关系,使 u A u_A uA与 v A v_A vA配对, u B u_B uB与 v B v_B vB配对,则该边可少计算两次而其他边至少不会再被多计算。当两连通块均有奇数个结点时,该边必被计算一次。因此,应优先将位于相同连通块的两点配对,该边对答案的贡献为 w ∗ ( ∣ C A ∣ m o d 2 ) w*(|C_A|\mod2) w∗(∣CA∣mod2)。若求最大值,同理,应优先将位于不同连通块的两点配对,则该边对答案的贡献为 w ∗ m i n ( ∣ C A ∣ , ∣ C B ∣ ) w*min(|C_A|,|C_B|) w∗min(∣CA∣,∣CB∣)。
通过一次DFS可求得每棵子树的大小,从而求得每条边连接的两个连通块大小。
复杂度: O ( n ) O(n) O(n)。
#include
#define ll long long
using namespace std;
const int maxn=2e5+5;
struct edge
{
int v,id;
edge(int v,int id):v(v),id(id){}
};
vector<edge>e[maxn];
void link(int u,int v,int id)
{
e[u].push_back(edge(v,id));
e[v].push_back(edge(u,id));
}
int siz[maxn],cnt[maxn],cost[maxn],k;
void dfs(int u,int f,int id)
{
siz[u]=u>1;
for(edge x:e[u]) if(x.v!=f)
{
dfs(x.v,u,x.id);
siz[u]+=siz[x.v];
}
cnt[id]=min(siz[u],k-siz[u]);
}
int main()
{
int t,u,v,w;
cin>>t;
while(t--)
{
cin>>k,k<<=1;
for(int i=1;i<=k;i++) e[i].clear();
for(int i=1;i<k;i++)
cin>>u>>v>>w,link(u,v,i),cost[i]=w;
dfs(1,0,0);
ll mnv=0,mxv=0;
for(int i=1;i<k;i++)
mnv+=1LL*cost[i]*(cnt[i]&1),mxv+=1LL*cost[i]*cnt[i];
cout<<mnv<<" "<<mxv<<endl;
}
}
题意:
给定一棵n个结点的树,每个节点上两个权值a和b,将树划分成m个连通块,求所有连通块中满足 ∑ a > ∑ b \sum a>\sum b ∑a>∑b的块的最大数 ( 1 ≤ m ≤ n ≤ 3 ∗ 1 0 3 ) (1\le m\le n\le3*10^3) (1≤m≤n≤3∗103)。
解法:
首先可将每个点的两个权值合并为一个:w=a-b,则问题转化为求满足 ∑ w > 0 \sum w>0 ∑w>0的最大块数。考虑树形DP: d p [ u ] [ i ] dp[u][i] dp[u][i]表示将以u为根的树划分为i个连通块的答案,而满足条件的块数与根所在的连通块的 ∑ w \sum w ∑w均应作为考虑因素,但无法将 ∑ w \sum w ∑w的值作为状态。于是考虑贪心:首先应保证满足条件的块数最多,再保证 ∑ w \sum w ∑w最大,因为当状态转移时,即使 ∑ w \sum w ∑w足够大也只会使满足条件的块数增加1。综上,用结构体封装上述两因素,dp值则表示除去根所在的连通块以外其他满足条件的最大块数及根所在的连通块的 ∑ w \sum w ∑w(根所在的连通块可能再向上合并,而其他块已经确定了)。
对一棵子树的根结点u,考虑其所有儿子v,进行树上背包:u与v所在的连通块合并或不合并,则有如下转移方程:
不合并: d p [ u ] [ i + j ] = m a x ( n o d e ( d p [ u ] [ i ] . c n t + d p [ v ] [ j ] . c n t + ( d p [ v ] [ j ] . s u m > 0 ) , d p [ u ] [ i ] . s u m ) ) dp[u][i+j]=max(node(dp[u][i].cnt+dp[v][j].cnt+(dp[v][j].sum>0),dp[u][i].sum)) dp[u][i+j]=max(node(dp[u][i].cnt+dp[v][j].cnt+(dp[v][j].sum>0),dp[u][i].sum))
合并: d p [ u ] [ i + j − 1 ] = m a x ( n o d e ( d p [ u ] [ i ] . c n t + d p [ v ] [ j ] . c n t , d p [ u ] [ i ] . s u m + d p [ v ] [ j ] . s u m ) ) dp[u][i+j-1]=max(node(dp[u][i].cnt+dp[v][j].cnt,dp[u][i].sum+dp[v][j].sum)) dp[u][i+j−1]=max(node(dp[u][i].cnt+dp[v][j].cnt,dp[u][i].sum+dp[v][j].sum))
( 1 ≤ i ≤ s i z [ u ] , 1 ≤ j ≤ s i z [ v ] ) (1\le i\le siz[u],1\le j \le siz[v]) (1≤i≤siz[u],1≤j≤siz[v])
其中cnt为块数,sum为 ∑ w \sum w ∑w,siz[u]为子树u的大小。
复杂度: O ( n 2 ) O(n^2) O(n2)。
#include
#define ll long long
using namespace std;
const int maxn=3e3+5;
const ll inf=1e18;
ll w[maxn],x;
int siz[maxn];
vector<int>e[maxn];
struct node
{
int cnt; ll sum;
node(){}
node(int c,ll s):cnt(c),sum(s){}
}dp[maxn][maxn],tmp[maxn][maxn];
bool operator <(const node& x,const node& y)
{ return x.cnt<y.cnt||(x.cnt==y.cnt&&x.sum<y.sum); }
void link(int u,int v)
{ e[u].push_back(v),e[v].push_back(u); }
void dfs(int u,int f)
{
siz[u]=1,dp[u][1]=node(0,w[u]);
for(int v:e[u]) if(v!=f)
{
dfs(v,u);
for(int i=1;i<=siz[u]+siz[v];i++)
tmp[u][i]=node(0,-inf);
for(int i=1;i<=siz[u];i++)
for(int j=1;j<=siz[v];j++)
{
int cnt=dp[u][i].cnt+dp[v][j].cnt;
tmp[u][i+j]=max(tmp[u][i+j],
node(cnt+(dp[v][j].sum>0),dp[u][i].sum));
tmp[u][i+j-1]=max(tmp[u][i+j-1],
node(cnt,dp[u][i].sum+dp[v][j].sum));
}
siz[u]+=siz[v];
for(int i=1;i<=siz[u];i++)
dp[u][i]=tmp[u][i];
}
}
int main()
{
int t,n,m,u,v;
cin>>t;
while(t--)
{
cin>>n>>m;
for(int i=1;i<=n;i++)
cin>>w[i],e[i].clear();
for(int i=1;i<=n;i++)
cin>>x,w[i]=x-w[i];
for(int i=1;i<n;i++)
cin>>u>>v,link(u,v);
dfs(1,0),printf("%d\n",dp[1][m].cnt+(dp[1][m].sum>0));
}
}