Enzymii的hu测 T1.eromanga(hash)

版权属于Enzymii,想要引用此题(包括题面)的朋友请联系博主

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

分析:
一眼点分治,但是不是点分治

于是打了个暴力+第6个点点分治+状压=40


题目来源
官方正解

RTY(Enzymii)这套题的特点就是部分分的划分比较多,个人觉得比较符合省选的特点,所以重点分析一下各种部分分做法(也是对于以后解题的一种启发吧)

算法一

连个模数都不给,显然不能把边权都乘起来,于是就想到了质因数分解,对于路径上的每一个质因子 p p ,ta的次数必须是偶数

那么我们就有了简单粗暴的算法:从每个点开始dfs,维护每个质因数的出现次数
一开始预处理一下边权的质因数(出现次数为奇数),复杂度是O(n2logn)
我简单算了一下,即使是最差情况, 100000000 100000000 以内的数也不可能存在15个以上的不同质因子( 23571113171921=203693490 2 ∗ 3 ∗ 5 ∗ 7 ∗ 11 ∗ 13 ∗ 17 ∗ 19 ∗ 21 = 203693490
使用map离散化质因子并存次数,期望得分30分

void get(int x,int z) {
    int t=0;
    for (int i=2;i*i<=z;i++)
        if (z%i==0) {
            int tt=0;
            while (z%i==0) z/=i,tt++;
            if (tt%2) way[x].pri[++t]=i;    //出现奇数次 
        }
    if (z!=1) way[x].pri[++t]=z;
    way[x].pri[0]=t;
}
...
int pd() {
    for (int i=1;i<=maxnum;i++)
        if (cnt[i]) return 0;
    return 1;
}
void dfs(int now,int s) {
    if (now!=s) {
        if (pd()) ans=ans+1LL;
    }
    vis[now]=1;
    for (int i=st[now];i;i=way[i].nxt)
        if (!vis[way[i].y]) {
            for (int j=1;j<=way[i].pri[0];j++) {
                int num=way[i].pri[j],o;
                if (!mp[num]) mp[num]=++maxnum;
                o=mp[num];    //离散化 
                cnt[o]^=1;
            }
            dfs(way[i].y,s);
            for (int j=1;j<=way[i].pri[0];j++) {
                int num=way[i].pri[j],o;
                if (!mp[num]) mp[num]=++maxnum;
                o=mp[num];    //离散化 
                cnt[o]^=1;
            }
        }
}
算法二

我们把 w w 质因数分解,每个出现过的质数离散化一下,可以发现至多出现O(nlogn)个不同的质数

那么使用bitset维护每个质数的出现次数,再dfs就可以做到 O(n232) O ( n 2 32 ) 了,但是这样并不能跑出 n=3000 n = 3000 事实上我们直接对离散化之后的质数开桶统计就能做到 O(n2logn) O ( n 2 l o g n ) 了,期望得分50分

算法三

对于 w100 w ≤ 100 的数据,由于100以内的质数不超过 25 25 个,于是我们就可以把边权表示为一个二进制(状压,如果一个质因子出现了奇数次,那么该位上式1)
因此,一条路径的权值就是每条边权值的异或
从根开始dfs求出根到每个结点的权值,那么 uv u → v 是可行的当且仅当根到 u u 的权值等于根到v的权值,把根到所有结点的权值排序统计一下即可

时间复杂度 O(nlogn) O ( n l o g n )

一开始我还zz的写了一个点分,实际上根本不用
因为任何一条路径都可表示为 root>u+root>w2(root>lca) r o o t − > u + r o o t − > w − 2 ∗ ( r o o t − > l c a )
显然因为 root>lca r o o t − > l c a 的路径我们计算了两次就异或掉了,因此这条路径的权值就是 (root>u) Xor (root>w) ( r o o t − > u )   X o r   ( r o o t − > w )

算法四

考虑推广算法三,我们考虑把 1108 1 ∼ 10 8 的每个质数hash成一个long long范围内的数, 然后像算法三一样从根一路异或即可.
质因数分解的话我们预处理出 104 10 4 以内的质数,暴力分解即可

举个例子

         1   2   3   4   5   6
素数表:   2   3   5   11  13  17 

当前的数字分解出来是 2331113 2 ∗ 3 ∗ 3 ∗ 11 ∗ 13
hash=2x+11x4+13x5 h a s h = 2 x + 11 x 4 + 13 x 5

可以看到我们再hash的时候自动忽略了出现次数为偶数的质数,所以我们需要一个数组记录每一个素数出现的次数
为了保险,可以选择双hash

时间复杂度大概是 O(w+nπ(w)+nlogn) O ( w + n π ( w ) + n l o g n ) ,期望得分100分

官方题解是用随机化进行hash:

#include <map>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N=131075;
map<int,LL> hs;
inline int gn(int a=0,char c=0){
    for(;c<'0'||c>'9';c=getchar());
    for(;c>47&&c<58;c=getchar()) a=a*10+c-48;
return a;}
inline LL nxt_rd(){
    LL res=1000000000LL*rand()+rand();
    return res;
}
int pr[1333],ptot; bool np[10101];
void shai(){
    for(int i=2;i<=10000;++i){
        if(!np[i]) pr[++ptot]=i;
        for(int j=1;j<=ptot&&i*pr[j]<=10000;++j){
            np[i*pr[j]]=1; if(i%pr[j]==0) break;
        }
    }
}
struct edge{int to,next,data;}e[N<<1];
int v[N],tot,n; bool vis[N]; LL d[N];
inline void buildedge(int x,int y,int z){
    e[++tot].to=x; e[tot].next=v[y]; e[tot].data=z; v[y]=tot;
    e[++tot].to=y; e[tot].next=v[x]; e[tot].data=z; v[x]=tot;
}
void dfs(int x){ vis[x]=1;
    for(int i=v[x];i;i=e[i].next){
        int y=e[i].to,z=e[i].data;
        if(!vis[y]){
            d[y]=d[x];
            for(int j=1;j<=ptot;++j)
                while(pr[j]*pr[j]<=z&&z%pr[j]==0){
                    if(hs.find(pr[j])==hs.end())
                        hs[pr[j]]=nxt_rd();
                    d[y]^=hs[pr[j]]; z/=pr[j];
                }
            if(z!=1){
                if(hs.find(z)==hs.end())
                    hs[z]=nxt_rd();
                d[y]^=hs[z];
            }
            dfs(y);
        }
    }
}
int main(){ 
    freopen("eromanga.in","r",stdin);
    freopen("eromanga.out","w",stdout);
    srand(19260817);
    n=gn(); int x,y,z; shai(); LL ans=0;
    for(int i=1;i<n;++i)
        x=gn(),y=gn(),z=gn(),buildedge(x,y,z);
    dfs(1); sort(d+1,d+n+1); d[n+1]=-1;
    for(int i=1,l=1;i<=n+1;++i)
        if(d[i]!=d[i-1])
            ans+=1LL*(i-l)*(i-l-1),l=i;
    printf("%lld",ans);
}

我写了一版用字符串哈希的方法进行hash
一开始WA了:根结点的hash值也要记录

#include<cstdio>
#include<cstring>
#include<iostream>
#include<map>
#include<algorithm>
#define ll long long

using namespace std;

const ll p1=998244353;
const ll p2=1004535809;
const ll x1=131;
const ll x2=17;
const int N=1500010;
int n,st[N],tot=0,sshu[10005],cnt=0,vis[15000];
struct node{
    int y,nxt;
    ll v;
}way[N<<1];
struct point{
    ll d1,d2;
    bool operator <(const point &A) const {
        return d1<A.d1||(d1==A.d1&&d2<A.d2);
    }
    bool operator ==(const point &A) const {
        return d1==A.d1&&d2==A.d2;
    }
}d[N];
bool no[10005];
map<int,ll> mp1;
map<int,ll> mp2;
map<int,int> num;

void add(int u,int w,ll z) {
    tot++;way[tot].y=w;way[tot].v=z;way[tot].nxt=st[u];st[u]=tot;
    tot++;way[tot].y=u;way[tot].v=z;way[tot].nxt=st[w];st[w]=tot;
}

ll KSM(ll a,ll b,ll p) {
    ll t=1;
    while (b) {
        if (b&1) t=(t*a)%p;
        b>>=1;
        a=(a*a)%p;
    }
    return t%p;
}

void dfs(int now,int fa) {
    for (int i=st[now];i;i=way[i].nxt)
        if (way[i].y!=fa) {
            int y=way[i].y;
            d[y].d1=d[now].d1;
            d[y].d2=d[now].d2;

            ll v=way[i].v;

            for (int j=2;j*j<=v;j++)
                if (v%j==0) {
                    int opt=0;
                    while (v%j==0) opt^=1,v/=j;
                    if (opt) {
                        if (vis[num[j]]) d[y].d1-=(1LL*mp1[j])%p1,d[y].d2-=(1LL*mp2[j])%p2;   //出现偶数次 
                        else d[y].d1+=(1LL*mp1[j])%p1,d[y].d2+=(1LL*mp2[j])%p2;
                    }
                    vis[num[j]]^=opt;                         //这个素数有没有出现过 
                }
            if (v!=1) {
                if (mp1[v]==0&&mp2[v]==0) {     //新的素数 
                    sshu[0]++;
                    mp1[v]=(KSM(x1,sshu[0],p1)*v)%p1;
                    mp2[v]=(KSM(x2,sshu[0],p2)*v)%p2;
                    num[v]=sshu[0];
                }

                if (vis[num[v]]) d[y].d1-=(1LL*mp1[v])%p1,d[y].d2-=(1LL*mp2[v])%p2;   //出现偶数次 
                else d[y].d1+=(1LL*mp1[v])%p1,d[y].d2+=(1LL*mp2[v])%p2;
                vis[num[v]]^=1;
            }

            d[y].d1=(d[y].d1%p1+p1)%p1;
            d[y].d2=(d[y].d2%p2+p2)%p2;
            dfs(y,now);

            v=way[i].v;
            for (int j=2;j*j<=v;j++)
                if (v%j==0) {
                    int opt=0;
                    while (v%j==0) opt^=1,v/=j;
                    vis[num[j]]^=opt;                         //这个素数有没有出现过 
                }
            if (v!=1) vis[num[v]]^=1;
        }
}

void prepare() {
    for (int i=2;i<=10000;i++) {
        if (!no[i]) sshu[++sshu[0]]=i;
        for (int j=1;j<=sshu[0]&&sshu[j]*i<=10000;j++) {
            no[sshu[j]*i]=1;
            if (i%sshu[j]==0) break;
        }
    }

    ll num1=x1,num2=x2;
    for (int i=1;i<=sshu[0];i++) {
        mp1[sshu[i]]=(num1*(ll)sshu[i])%p1;
        mp2[sshu[i]]=(num2*(ll)sshu[i])%p2;
        num[sshu[i]]=i;
        num1=(num1*x1)%p1;
        num2=(num2*x2)%p2;
    }
}

int main()
{
    prepare();
    scanf("%d",&n);
    for (int i=1;i<n;i++) {
        int u,w; ll z;
        scanf("%d%d%lld",&u,&w,&z);
        add(u,w,z);
    }
    dfs(1,0);

    sort(d+1,d+1+n);
    ll ans=0,l,r;
    l=r=1;
    for (int i=1;i<=n;i++) {
        if (d[i]==d[r]) r=i;
        else {
            ans=ans+(r-l+1)*(r-l);
            l=r=i;
        }
    }
    ans=ans+(r-l+1)*(r-l);
    printf("%lld\n",ans);

    return 0;
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值