版权属于Enzymii,想要引用此题(包括题面)的朋友请联系博主
分析:
一眼点分治,但是不是点分治
于是打了个暴力+第6个点点分治+状压=40
RTY(Enzymii)这套题的特点就是部分分的划分比较多,个人觉得比较符合省选的特点,所以重点分析一下各种部分分做法(也是对于以后解题的一种启发吧)
算法一
连个模数都不给,显然不能把边权都乘起来,于是就想到了质因数分解,对于路径上的每一个质因子 p p ,ta的次数必须是偶数
那么我们就有了简单粗暴的算法:从每个点开始dfs,维护每个质因数的出现次数
一开始预处理一下边权的质因数(出现次数为奇数),复杂度是
我简单算了一下,即使是最差情况,
100000000
100000000
以内的数也不可能存在15个以上的不同质因子(
2∗3∗5∗7∗11∗13∗17∗19∗21=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 质因数分解,每个出现过的质数离散化一下,可以发现至多出现个不同的质数
那么使用bitset维护每个质数的出现次数,再dfs就可以做到 O(n232) O ( n 2 32 ) 了,但是这样并不能跑出 n=3000 n = 3000 事实上我们直接对离散化之后的质数开桶统计就能做到 O(n2logn) O ( n 2 l o g n ) 了,期望得分50分
算法三
对于
w≤100
w
≤
100
的数据,由于100以内的质数不超过
25
25
个,于是我们就可以把边权表示为一个二进制(状压,如果一个质因子出现了奇数次,那么该位上式1)
因此,一条路径的权值就是每条边权值的异或
从根开始dfs求出根到每个结点的权值,那么
u→v
u
→
v
是可行的当且仅当根到
u
u
的权值等于根到的权值,把根到所有结点的权值排序统计一下即可
时间复杂度 O(nlogn) O ( n l o g n )
一开始我还zz的写了一个点分,实际上根本不用
因为任何一条路径都可表示为
root−>u+root−>w−2∗(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
)
算法四
考虑推广算法三,我们考虑把
1∼108
1
∼
10
8
的每个质数hash成一个long long范围内的数, 然后像算法三一样从根一路异或即可.
质因数分解的话我们预处理出
104
10
4
以内的质数,暴力分解即可
举个例子
1 2 3 4 5 6
素数表: 2 3 5 11 13 17
当前的数字分解出来是
2∗3∗3∗11∗13
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;
}