2017.3.16 聪聪可可 思考记录(非常不容易)

    听说是树分治的题,就进来了,
   然而一看数据,最大才2000   
      n^2暴力应该能过啊;但是T了,因为——此题时限0.3秒、、
    听说树形dp和点分治都可以, 那先想想吧

    结果,竟然搞了一天、、、

   如果学完了树分治, 其实思路是好想的:
  3的倍数和具体的数字没有关系,所以只需统计dis%3的值,
 因为:所有%3=1的数+%3=2的数再%3一定=0,对答案有贡献(魔法分配律)

     需要递归加减的量就是 余数为2的个数*(余数为1的个数-1)+∑(i=1;i<(余数为0的个数-1))(i);    

直接树分治就可以搞了。
码:

#include<iostream>
#include<cstdio>
using namespace std;
#include<cstring>
#define N 20001
#define M 40002
int tot,xia[N],d[N],zhe,hou[M],zhong[M],zhi[M],x,y,n,z,size[N],root,max1=1e9,yu[3],ans;
bool vis[N];
void add(int x,int y,int z)
{
  hou[++tot]=xia[x],xia[x]=tot,zhong[tot]=y,zhi[tot]=z; 
}
void dfs(int now,int fa)
{
    size[now]=1;
    int maxx=0;
    for(int i=xia[now];i!=-1;i=hou[i])
    {
        int nd=zhong[i];
        if(nd==fa||vis[nd])continue;
        dfs(nd,now);
        size[now]+=size[nd];
        maxx=max(maxx,size[nd]);    
    }
    maxx=max(maxx,tot-size[now]);
    if(maxx<max1)max1=maxx,root=now;
}
void dfs2(int now,int fa)
{


        yu[d[now]%3]++;     


    for(int i=xia[now];i!=-1;i=hou[i])
    {
    int nd=zhong[i];
    if(vis[nd]||nd==fa)continue;
    d[nd]=d[now]+zhi[i];
    dfs2(nd,now);
    }   
}
int work(int now,int hehe)
{
    yu[0]=0;
    yu[1]=0;
    yu[2]=0;
    d[now]=hehe;
//  cout<<now<<" "<<d[now]<<endl;
//  zhe=now;
    dfs2(now,0);    
    return yu[1]*yu[2]+(yu[0])*(yu[0]-1)/2;
}
//int work2(int now)
//{
//  yu[0]=0;
//      yu[1]=0;
//      yu[2]=0;
//  zhe=now;
//      dfs2(now,0);
//yu[d[now]%3]++;
//   // cout<<now<<" "<<d[now]<<endl;
//
//      return yu[1]*yu[2]+(yu[0]+1)*yu[0]/2;
//  
//}
int solve(int now)
{   //cout<<now<<" ";
    ans+=work(now,0);
    //cout<<now<<" "<<ans<<" ";
    vis[now]=1;
    for(int i=xia[now];i!=-1;i=hou[i])
    {
        int nd=zhong[i];
        if(vis[nd])continue;
        ans-=work(nd,zhi[i]);
    //  cout<<nd<<" "<<ans<<endl;
        max1=1e9;tot=size[nd];
        dfs(nd,now);
        solve(root);        
    }   
}
int gcd(int a,int b)
{
    if(!b)return a;
    return gcd(b,a%b);
}
int main()
{
    memset(xia,-1,sizeof(xia));
     scanf("%d",&n);
     for(int i=1;i<n;i++)
     {
        scanf("%d%d%d",&x,&y,&z);
        add(x,y,z);
        add(y,x,z);     
     }   
//   for(int i=xia[4];i!=-1;i=hou[i]
//   {
//      cout<<zhong[i]<<" ";
//   }
//   cout<<endl;


    max1=1e9;tot=n;
    dfs(1,0);

    solve(root);
    int hehehe=gcd(ans*2+n,n*n);    
    printf("%d/%d",(ans*2+n)/hehehe,n*n/hehehe);


}

可以分析一下
这里写图片描述

注意到了根节点本身也被拉到%3=0的序列中了

注意到了经过上一个根的同一子树的情况都应该减掉,直接套用原来加的函数计算即可(子树的根节点在总的根节点的距离和其他节点是同级的,所以子树的根节点也要一块算进去,不用特殊处理!!!)

另一点要注意:减的操作要在找重心前!!!
这里写图片描述
这样来看是可以保证正确的,
但如果你想闹着玩,你也可以把计算方法改成:余数为2的个数*余数为1的个数+∑(i=1;i<余数为0的个数)(i)
这也是不影响的,因为如果你把单链也算进去,减的时候的同一子树的单链也会被减掉,所以是无意义的

这里写图片描述

虽然犀利糊涂的过了,但有一点比较重要,看到上面大段注释掉的代码应该知道:
加减的函数 我当成了不一样的!!
今天是我最ZZ的一天
其实分析都不用分析,因为既然是递归,要加要减肯定是一起的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值