CF1399E1+E2.Weights Division(优先队列+贪心+二分)
题意:
给出一颗带边权的树,一次操作可以把一条边的边权减半,询问最少操作数使得从根节点到所有叶子节点的路径长度之和小于等于给定值S。
题解:
首先考虑单边对答案的贡献是这条边自己的权值乘它下面的叶子节点数量,所以先用一遍DFS处理出每条边下面的叶子节点数量。
接下来考虑用优先队列贪心的枚举这个过程。最优解一定是每次把收益最大的边减半,所以我们把一条边的边权和它下面的叶子节点数量作为一个结构体放进优先队列,排序规则是把它减半给答案带来的贡献,这里注意向下取整的问题,比赛时因为这个一直没过。减半后把新的版本的边入队,这样就实现了一个贪心的过程。
#include<bits/stdc++.h> using namespace std; const int maxn=2e5+100; typedef long long ll; int t; int n; ll s; struct node { int u,v; ll w; int cost; int nxt; }edge[maxn*2]; ll lst[maxn];//表示每个节点和他父亲的边权 int lst_c[maxn]; int dfn[maxn]; int dfo[maxn]; int num[maxn]; int head[maxn]; int tot; void addedge (int u,int v,ll w) { edge[tot].u=u; edge[tot].v=v; edge[tot].w=w; //edge[tot].cost=cost; edge[tot].nxt=head[u]; head[u]=tot++; edge[tot].u=v; edge[tot].v=u; edge[tot].w=w; //edge[tot].cost=cost; edge[tot].nxt=head[v]; head[v]=tot++; } ll d[maxn]; int cnt=0; int ans=0; void dfs (int u,int pre) { int f=0; for (int i=head[u];i!=-1;i=edge[i].nxt) { int v=edge[i].v; if (v==pre) continue; f=1;break; } if (!f) { cnt++; num[u]=1; return; } dfn[u]=cnt; for (int i=head[u];i!=-1;i=edge[i].nxt) { int v=edge[i].v; if (v==pre) continue; lst[v]=edge[i].w; //lsc_c[v]=edge[i].cost; dfs(v,u); } num[u]=cnt-dfn[u]; } struct e { ll w; ll num; bool operator < (const e &r) const { return (w-w/2)*num<(r.w-r.w/2)*r.num; } }; int main () { scanf("%d",&t); while (t--) { scanf("%d%lld",&n,&s); for (int i=1;i<=n;i++) head[i]=-1; tot=0; ans=0; cnt=0; for (int i=1;i<n;i++) { int u,v; ll w; int cost; scanf("%d%d%lld",&u,&v,&w); addedge(u,v,w); } dfs(1,0); priority_queue<e> q; ll sum=0; //for (int i=1;i<=n;i++) printf("%d %lld\n",num[i],lst[i]); for (int i=1;i<=n;i++) q.push({lst[i],num[i]}),sum+=lst[i]*(num[i]); while (sum>s&&!q.empty()) { e u=q.top(); q.pop(); sum-=(u.w-u.w/2)*u.num; q.push({u.w/2,u.num}); ans++; } //for (int i=1;i<=n;i++) printf("%d\n",num[i]); printf("%d\n",ans); } }
题意:
E2在E1的基础上把边分为两种,一种边操作一次花费1,一种边操作一次花费2,询问最小花费。
题解:
把花费1和2的两种边分开考虑,分别对两种边执行E1的过程,可以预处理出每种边贪心i步之后,可以得到的最优解,然后对第一种边枚举步数,第二种边二分步数即可得到答案。
#include<bits/stdc++.h> using namespace std; const int maxn=2e5+100; typedef long long ll; int t,n; ll s; struct node { int u,v; ll w; int cc; int nxt; }edge[maxn*2]; ll lst[maxn]; int lst_c[maxn]; int dfn[maxn]; int dfo[maxn]; int num[maxn]; int head[maxn]; int tot; int cnt; void addedge (int u,int v,ll w,int cc) { edge[tot].u=u; edge[tot].v=v; edge[tot].w=w; edge[tot].cc=cc; edge[tot].nxt=head[u]; head[u]=tot++; edge[tot].u=v; edge[tot].v=u; edge[tot].w=w; edge[tot].cc=cc; edge[tot].nxt=head[v]; head[v]=tot++; } int ans=0; void dfs (int u,int pre) { int f=0; for (int i=head[u];i!=-1;i=edge[i].nxt) { int v=edge[i].v; if (v==pre) continue; f=1;break; } if (!f) { cnt++; num[u]=1; return; } dfn[u]=cnt; for (int i=head[u];i!=-1;i=edge[i].nxt) { int v=edge[i].v; if (v==pre) continue; lst[v]=edge[i].w; lst_c[v]=edge[i].cc; dfs(v,u); } num[u]=cnt-dfn[u]; } struct e { ll w; int num; bool operator < (const e &r) const { return (w-w/2)*num<(r.w-r.w/2)*r.num; } }; ll c[3][maxn*50];//分别表示两种边贪心做i步之后还剩多少 int main () { scanf("%d",&t); while (t--) { scanf("%d%lld",&n,&s); for (int i=0;i<=n;i++) head[i]=-1,c[0][i]=c[1][i]=c[2][i]=0; tot=0; ans=0; cnt=0; for (int i=1;i<n;i++) { int u,v; ll w; int cc; scanf("%d%d%lld%d",&u,&v,&w,&cc); addedge(u,v,w,cc); } dfs(1,0); priority_queue<e> q[3]; for (int i=1;i<=n;i++) { q[lst_c[i]].push({lst[i],num[i]}); c[lst_c[i]][0]+=lst[i]*num[i]; } int cnt1=1,cnt2=1; while (!q[1].empty()) { e u=q[1].top(); q[1].pop(); c[1][cnt1]=c[1][cnt1-1]-(u.w-u.w/2)*u.num; cnt1++; if (u.w/2)q[1].push({u.w/2,u.num}); } while (!q[2].empty()) { e u=q[2].top(); q[2].pop(); c[2][cnt2]=c[2][cnt2-1]-(u.w-u.w/2)*u.num; cnt2++; if (u.w/2)q[2].push({u.w/2,u.num}); } ans=1e9; for (int i=0;i<cnt1;i++) { int l=0,r=cnt2-1; int u=-1; while (l<=r) { int mid=(l+r)>>1; if (c[1][i]+c[2][mid]<=s) { u=mid; r=mid-1; } else { l=mid+1; } } if (u==-1) continue; ans=min(ans,i+2*u); } printf("%d\n",ans); } }