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

这是为了真正去学一下点分治。。然后看了迪克李的ppt

又是一道写(改)了很久的题。。终于ac了

1354799 orzliyicheng 2599 Accepted 31936 kb 23584 ms C++/Edit 2218 B 2016-03-27 15:55:17

不算快呢。。具体实现是看的hzwer的blog,然而迪克李的ppt已经将想法讲得很清楚了

uoj文件里有,就懒得贴题解了

刚刚写完的时候,一个极限数据要跑60sec,我也是醉了。。主要原因有:

1.清空数组的时候竟然跑了n遍for循环,这不是显然O(n^2),我是不是傻。。(改完以后跑了30sec

2.getdeep,亦或是Add,都必须是对一整颗子树,我好像直接当前节点就开始了啊。。(这样下来时间就差不多了

然而还是有bug

都是一些没清零之类的问题。。晕@[email protected]

然后就很开心地贴了,成功wa     要来数据发现第十个点就错了

改了有毛两个小时。。那是一条链的情况。。还以为有哪条路径没考虑到呢。。

原来只是边权为零的问题。。。。。。。。。

QAQ于是一个下午就这么过去了。。下次再写点分治应该效率会变高了吧

听说黄学长的getroot有一些问题会很慢。。?然而我并不觉得呀,还是留到以后再看吧。。(懒)

/**************************************************************
    Problem: 2599
    User: orzliyicheng
    Language: C++
    Result: Accepted
    Time:23584 ms
    Memory:31936 kb
****************************************************************/

#include<cstdio>
#include<algorithm>
#define N 400100
using namespace std;
int edgenum,k,n,root,m,w,ans,sum,u,v,rn;
int son[N],flag[N],vet[N],s[N],next[N],ff[N],f[1000100],head[N],pri[N],dis[1000100],fa[N];
void getroot(int u,int fa)
{
  son[u]=1;ff[u]=0;int e=head[u];
  while(e>0)
  {
    int v=vet[e];
    if(flag[v]==0&&v!=fa)
    {
      getroot(v,u);son[u]+=son[v];
      ff[u]=max(ff[u],son[v]);
    }
    e=next[e];
  }
  ff[u]=max(ff[u],sum-son[u]);
  if(ff[u]<ff[root])root=u;
}
void Add(int u,int fa,int biao)
{
  if(dis[u]<=k)
  {
    if(biao>0)f[dis[u]]=min(f[dis[u]],s[u]);else f[dis[u]]=n*2;
  }
  int e=head[u];
  while(e>0)
  {
    int v=vet[e];
    if(flag[v]==0)if(v!=fa)
    {
      Add(v,u,biao);
    }
    e=next[e];
  }
}
void getdeep(int u,int fa)
{
  if(k>=dis[u])if(f[k-dis[u]]+s[u]<ans)ans=f[k-dis[u]]+s[u];
  int e=head[u];
  while(e>0)
  {
    int v=vet[e];
    if(flag[v]==0&&v!=fa)
    {
      s[v]=s[u]+1;dis[v]=dis[u]+pri[e];
      getdeep(v,u);
    }
    e=next[e];
  }
}
void work(int u)
{
  dis[u]=0;s[u]=0;flag[u]=1;
  int e=head[u];rn=u;
  while(e>0)
  {
    int v=vet[e];
    if(flag[v]==0)
    {
      dis[v]=dis[u]+pri[e];s[v]=s[u]+1;
      getdeep(v,0),Add(v,0,1);
    }
    e=next[e];
  }
  e=head[u];
  while(e>0)
  {
    int v=vet[e];
    if(flag[v]==0)Add(v,0,0);
    e=next[e];
  }f[0]=0;
  e=head[u];
  while(e>0)
  {
    int v=vet[e];
    if(flag[v]==0)
    {
      root=0;sum=son[v];
      getroot(v,0);work(root);
    }
    e=next[e];
  }
}
void add(int u,int v,int w)
{
  edgenum++;vet[edgenum]=v;next[edgenum]=head[u];head[u]=edgenum;
  pri[edgenum]=w;
}
int main()
{

  scanf("%d%d",&n,&k);
  for(int i=1;i<=n-1;i++)
  {
    scanf("%d%d%d",&u,&v,&w);u++;v++;
    add(u,v,w);add(v,u,w);
  }
  ff[0]=sum=n;ans=n*2;
  for(int i=1;i<=k;i++)f[i]=n*2;
  f[0]=0;
  root=0;getroot(1,0);work(root);
  if(ans>=n)ans=-1;
  printf("%d",ans);
  //fclose(stdin);fclose(stdout);
}

算是吧省选一试以后的一个遗留问题给解决了。。

大家去安徽集训了,也不知道可以系统地做什么。。那就先颓一会儿文化课吧,先把月考给考了。嗯

时间: 2024-08-28 17:37:14

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

【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 题解:本题大

bzoj2599: [IOI2011]Race(点分治)

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

【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] -- 点分治

令ans[i]表示权值和等于k的路径条数,然后点分治就可以了. 具体看代码. 代码: 1 #include<iostream> 2 #include<cstdio> 3 #include<cstring> 4 #include<algorithm> 5 #include<vector> 6 using namespace std; 7 #define N 200010 8 #define INF 2147483647 9 inline int M

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

2599. [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 开一个100W的数组t,t[i]表示到当前处理的树的根距离

[BZOJ2599]Race

假设当前分治到$x$,记$t_i$为到$x$距离为$i$的点中,到$x$最少的边数,那么我们采用分治2:每次统计一个儿子与集合中点产生的贡献,并把儿子信息合并到集合中,假设当前统计到$u$,$u$到$x$的边数为$d$,那么用$d+t_{k-dis_{x,u}}$更新答案即可,注意最后重置$t_i$时要遍历子树而不是memset,不然无法保证复杂度 #include<stdio.h> const int inf=100000000; int min(int a,int b){return a&

大神刷题表

9月27日 后缀数组:[wikioi3160]最长公共子串 dp:NOIP2001统计单词个数 后缀自动机:[spoj1812]Longest Common Substring II [wikioi3160]最长公共子串 [spoj7258]Lexicographical Substring Search 扫描线+set:[poj2932]Coneology 扫描线+set+树上删边游戏:[FJOI2013]圆形游戏 结论:[bzoj3706][FJ2014集训]反色刷 最小环:[poj1734

【算法总结】树相关

[点分治] [模板代码] 1 void getroot(int x,int fa) 2 { 3 sz[x]=1;mx[x]=0; 4 for(int i=first[x];i;i=e[i].next) 5 { 6 int to=e[i].to; 7 if(to==fa||vis[to])continue; 8 getroot(to,x); 9 sz[x]+=sz[to]; 10 mx[x]=max(mx[x],sz[to]); 11 } 12 mx[x]=max(mx[x],sum-sz[x])