P4149 [IOI2011]Race 点分治

思路: 点分治

提交:5次

题解:

刚开始用排序+双指针写的,但是调了一晚上,总是有两个点过不了,第二天发现原因是排序时的\(cmp\)函数写错了:如果对于路径长度相同的,我们从小往大按边数排序,当双指针出现\(==k\)时,即我们应先左移右指针,否则答案可能会变劣(仔细想一想);若反着排序,应该先右移左指针。

#include<bits/stdc++.h>
#define R register int
using namespace std;
namespace Luitaryi {
template<class I> inline I g(I& x) { x=0; register I f=1;
  register char ch; while(!isdigit(ch=getchar())) f=ch=='-'?-1:f;
  do x=x*10+(ch^48); while(isdigit(ch=getchar())); return x*=f;
} const int N=2e5+10,Inf=1e+9;
int n,K,cnt,sum,rt,tot,ans=N; bool vis[N];
int vr[N<<1],nxt[N<<1],fir[N],w[N<<1],sz[N],d[N],f[N],b[N],mx[N],mem[N];
inline void add(int u,int v,int ww) {
  vr[++cnt]=v,nxt[cnt]=fir[u],w[cnt]=ww,fir[u]=cnt;
  vr[++cnt]=u,nxt[cnt]=fir[v],w[cnt]=ww,fir[v]=cnt;
}
inline void getsz(int u,int fa) { sz[u]=1,mx[u]=0;
  for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
    if(v==fa||vis[v]) continue;
    getsz(v,u),sz[u]+=sz[v];
    mx[u]=max(mx[u],sz[v]);
  } mx[u]=max(mx[u],sum-sz[u]);
  if(mx[u]<mx[rt]) rt=u;
}
inline void getdis(int u,int fa) { mem[++tot]=u;
  for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
    if(v==fa||vis[v]) continue;
    d[v]=d[u]+w[i],f[v]=f[u]+1,b[v]=b[u];
    if(d[v]<=K) getdis(v,u);
  }
}
inline bool cmp(const int& a,const int& b) {
  return d[a]<d[b]||(d[a]==d[b]&&f[a]>f[b]);
}
inline void solve(int u,int fa) {
  tot=0,vis[u]=true; mem[++tot]=u,d[u]=f[u]=0,b[u]=u;
  for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
    if(v==fa||vis[v]) continue;
    d[v]=w[i],f[v]=1,b[v]=v; getdis(v,u);
  } sort(mem+1,mem+tot+1,cmp);
//  for(R i=1;i<=tot;++i) cout<<mem[i]<<" ";  cout<<endl;
  R l=1,r=tot; while(l<r) {
    if(d[mem[l]]+d[mem[r]]>K) --r;
    else if(d[mem[l]]+d[mem[r]]<K) ++l;
    else if(b[mem[l]]==b[mem[r]]) {
//      if(d[mem[r]]==d[mem[r-1]]) --r;
//      else ++l;
      if(d[mem[l]]==d[mem[l+1]]) ++l;
      else --r;
    } else {
      ans=min(ans,f[mem[l]]+f[mem[r]]);
//      if(d[mem[r]]==d[mem[r-1]]) --r;
//      else ++l;
      if(d[mem[l]]==d[mem[l+1]]) ++l;
      else --r;
    }
  } for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
    if(v==fa||vis[v]) continue;
    sum=sz[v],rt=0,mx[rt]=Inf;
    getsz(v,u); getsz(rt,-1); solve(rt,u);
  }
}
inline void main() {
  g(n),g(K); for(R i=1,u,v,w;i<n;++i) g(u),g(v),g(w),++u,++v,add(u,v,w);
  sum=n,mx[0]=Inf; getsz(1,-1),getsz(rt,-1); solve(rt,-1);
  if(ans==N) return (void) puts("-1"); printf("%d\n",ans);
}
} signed main() {Luitaryi::main(); return 0;}

但是上面的方法比较慢,多一个\(log\)。
于是还是类比点分治板子的思想,对于一颗子树,与之前的子树做贡献。我们可以开一个类似桶的数组记录长度为\(len\)的路径有没有出现过,同时对应记录长度为\(len\)的路径对应的最小边数。每求出来一颗子树的信息就扫一遍。

// luogu-judger-enable-o2
#include<bits/stdc++.h>
#define R register int
using namespace std;
namespace Luitaryi {
template<class I> inline I g(I& x) { x=0; register I f=1;
  register char ch; while(!isdigit(ch=getchar())) f=ch=='-'?-1:f;
  do x=x*10+(ch^48); while(isdigit(ch=getchar())); return x*=f;
} const int N=2e5+10,M=1e6+10,Inf=0x3f3f3f3f;
int n,K,cnt,sum,rt,tot,ans=N,SZ; bool vis[N],mem[M];
int vr[N<<1],nxt[N<<1],fir[N],w[N<<1],sz[N],d[N],f[M],s[N],mx[N],buf[N],l[N],dis[N];
inline void add(int u,int v,int ww) {
  vr[++cnt]=v,nxt[cnt]=fir[u],w[cnt]=ww,fir[u]=cnt;
  vr[++cnt]=u,nxt[cnt]=fir[v],w[cnt]=ww,fir[v]=cnt;
}
inline void getsz(int u,int fa) { sz[u]=1,mx[u]=0;
  for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
    if(v==fa||vis[v]) continue;
    getsz(v,u),sz[u]+=sz[v];
    mx[u]=max(mx[u],sz[v]);
  } mx[u]=max(mx[u],sum-sz[u]);
  if(mx[u]<mx[rt]) rt=u;
}
inline void getdis(int u,int fa) {
  dis[++tot]=d[u],l[tot]=s[u];
  for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
    if(v==fa||vis[v]) continue;
    d[v]=d[u]+w[i],s[v]=s[u]+1;
    if(d[v]<=K) getdis(v,u);
  }
}
//inline bool cmp(const int& a,const int& b) {
//  return d[a]<d[b]||(d[a]==d[b]&&f[a]>f[b]);
//}
//inline void solve(int u,int fa) {
//  tot=0,vis[u]=true; mem[++tot]=u,d[u]=f[u]=0,b[u]=u;
//  for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
//    if(v==fa||vis[v]) continue;
//    d[v]=w[i],f[v]=1,b[v]=v; getdis(v,u);
//  } sort(mem+1,mem+tot+1,cmp);
////  for(R i=1;i<=tot;++i) cout<<mem[i]<<" ";  cout<<endl;
//  R l=1,r=tot; while(l<r) {
//    if(d[mem[l]]+d[mem[r]]>K) --r;
//    else if(d[mem[l]]+d[mem[r]]<K) ++l;
//    else if(b[mem[l]]==b[mem[r]]) {
////      if(d[mem[r]]==d[mem[r-1]]) --r;
////      else ++l;
//      if(d[mem[l]]==d[mem[l+1]]) ++l;
//      else --r;
//    } else {
//      ans=min(ans,f[mem[l]]+f[mem[r]]);
////      if(d[mem[r]]==d[mem[r-1]]) --r;
////      else ++l;
//      if(d[mem[l]]==d[mem[l+1]]) ++l;
//      else --r;
//    }
//  } for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
//    if(v==fa||vis[v]) continue;
//    sum=sz[v],rt=0,mx[rt]=Inf;
//    getsz(v,u); getsz(rt,-1); solve(rt,u);
//  }
//}
inline void solve(int u,int fa) { tot=0; vis[u]=true;
  buf[++SZ]=0,mem[0]=true,d[u]=s[u]=0,f[0]=0;
  for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
    if(vis[v]||v==fa) continue;
    d[v]=w[i],s[v]=1; getdis(v,u);
    for(R i=1;i<=tot;++i) if(K>=dis[i]&&mem[K-dis[i]])
      ans=min(ans,f[K-dis[i]]+l[i]);
    for(R i=1;i<=tot;++i) {
      if(!mem[dis[i]]) buf[++SZ]=dis[i],mem[dis[i]]=true;
      f[dis[i]]=min(f[dis[i]],l[i]);
    } tot=0;
  } while(SZ) mem[buf[SZ]]=false,f[buf[SZ]]=Inf,--SZ;
  for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
    if(v==fa||vis[v]) continue;
    sum=sz[v],rt=0,mx[rt]=Inf;
    getsz(v,u),getsz(rt,-1); solve(rt,u);
  }
}
inline void main() {
  memset(f,0x3f,sizeof(f)); g(n),g(K);
  for(R i=1,u,v,w;i<n;++i) g(u),g(v),g(w),++u,++v,add(u,v,w);
  sum=n,mx[0]=Inf; getsz(1,-1),getsz(rt,-1); solve(rt,-1);
  if(ans==N) return (void) puts("-1"); printf("%d\n",ans);
}
} signed main() {Luitaryi::main(); return 0;} 

还有最近老犯一个错误,数组名老弄重导致算错。。。必须给予重视。。



2019.08.31
69

原文地址:https://www.cnblogs.com/Jackpei/p/11437895.html

时间: 2024-11-05 16:06:58

P4149 [IOI2011]Race 点分治的相关文章

BZOJ 2599: [IOI2011]Race( 点分治 )

数据范围是N:20w, K100w. 点分治, 我们只需考虑经过当前树根的方案. K最大只有100w, 直接开个数组CNT[x]表示与当前树根距离为x的最少边数, 然后就可以对根的子树依次dfs并更新CNT数组和答案. ------------------------------------------------------------------------------------------ #include<bits/stdc++.h> using namespace std; typ

IOI2011 Race [点分治]

题意 给一棵树,每条边有权.求一条简单路径,权值和等于 $K$,且边的数量最小. 点分治,求距离时带上经过边的数量即可.用的第一种写法(下面). 食用淀粉质注意事项 1. 统计子树内答案的两种写法: 跟树形dp一样将某子树与前面的子树合并   或者是   考虑所有子树的答案再容斥,减去不合法的一棵子树内答案. 2.好好写求重心,千万不要写假!!!假淀粉害死人 注意每次遍历先初始化$f[x]=0$,要有子树大小$S$. 1 #include <cstdio> 2 #include <cst

P4149 [IOI2011]Race

传送门 十分显然的点分治 枚举所有点作为两点的LCA 开一个桶$pd$判断之前子树内是否出现过此路程 对于每一个子树都把子树到根的所有路程dis都考虑匹配 如果 $pd[K-dis]=1$ 那么就说明存在匹配 然鹅题目还要求在合法匹配中选最少经过边数的匹配 那么再开一个数组 $dd$ ,$dd[i]$ 存当路程为 i 时经过的最少边数 dfs一个子树时开一个栈,存每个路程($st$)和经过的最少边数($d$) 那么dfs完一颗子树后就枚举栈中的所有元素,如果$pd[K-st[i]]=1$并且$d

【BZOJ2599】[IOI2011]Race 树的点分治

[BZOJ2599][IOI2011]Race Description 给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000 Input 第一行 两个整数 n, k第二..n行 每行三个整数 表示一条无向边的两端和权值 (注意点的编号从0开始) Output 一个整数 表示最小边数量 如果不存在这样的路径 输出-1 Sample Input 4 3 0 1 1 1 2 2 1 3 4 Sample Output 2 题解:本题大

【BZOJ-2599】Race 点分治

2599: [IOI2011]Race Time Limit: 70 Sec  Memory Limit: 128 MBSubmit: 2590  Solved: 769[Submit][Status][Discuss] Description 给一棵树,每条边有权.求一条简单路径,权值和等于K,且边的数量最小.N <= 200000, K <= 1000000 Input 第一行 两个整数 n, k第二..n行 每行三个整数 表示一条无向边的两端和权值 (注意点的编号从0开始) Output

bzoj2599: [IOI2011]Race(点分治)

写了四五道点分治的题目了,算是比较理解点分治是什么东西了吧= = 点分治主要用来解决点对之间的问题的,比如距离为不大于K的点有多少对. 这道题要求距离等于K的点对中连接两点的最小边数. 那么其实道理是一样的.先找重心,然后先从重心开始求距离dis和边数num,更新ans,再从重心的儿子开始求得dis和num,减去这部分答案 因为这部分的答案中,从重心开始的两条链有重叠部分,所以要剪掉 基本算是模板题,但是减去儿子的答案的那部分还有双指针那里调了好久,所以还不算特别熟练.. PS跑了27秒慢到飞起

bzoj 2599 [IOI2011]Race (点分治)

[题意] 问树中长为k的路径中包含边数最少的路径所包含的边数. [思路] 统计经过根的路径.假设当前枚举到根的第S个子树,若x属于S子树,则有: ans<-dep[x]+min{ dep[y] },y属于前S-1个子树,dis[x]<=K 所以只需要用一个数组t[len]记录前S-1棵子树中长度为len的最少边数即可.t只用开到K的最大值. 然后分治处理子树. [代码] 1 #include<set> 2 #include<cmath> 3 #include<qu

【BZOJ2599】[IOI2011]Race【点分治】

[题目链接] 点分治. 考虑经过点x的路径,对于x,用类似TreeDP的方法,记录no[d],表示路径长度为d时经过边最少的点的编号. 对于已经走过的子树,更新no.对于当前子树,遍历到一个点v,用depth[no[k - dis[v]]] + depth[v]更新答案. 注意给no清零时,用dfs姿势清零,这样做是O(n)的.如果直接用for或者memset,这样做是O(k)的,会TLE. /* Telekinetic Forest Guard */ #include <cstdio> #i

【点分治】【哈希表】bzoj2599 [IOI2011]Race

给nlog2n随便过的跪了,不得已弄了个哈希表伪装成nlogn(当然随便卡,好孩子不要学)…… 不过为啥哈希表的大小开小点就RE啊……?必须得超过数据范围一大截才行……谜 #include<cstdio> #include<algorithm> #include<cstring> using namespace std; int f,c; inline void R(int &x){ c=0;f=1; for(;c<'0'||c>'9';c=getc