http://acm.hdu.edu.cn/showproblem.php?pid=4812
solv(rt)写成solv(v)了,一直超时,调了2个多小时,看到网上题解有写得跟我一毛一样的,二分抄代码找到了。。。
点分治超时肯定要么在dfs上,vis[u]=true没标之类的,要么在solv(rt)上,solv错了点导致不符合点分治了。
我之前写的点分治版本都是算出所有的然后再减去重复的组合
但是这题中由于要找字典序最小的解所以减去重复的组合会导致无法找出解
所以此时需要边找边更新数组,用桶记录当前时刻到rt乘积是x的最小编号端点num[x]
线性预处理逆元,不然多带一个log过不去,虽然直接用费马小定理暴力预处理1e6*log也能过,就慢100ms
#include<bits/stdc++.h>
using namespace std;
#pragma comment(linker,"/STACK:102400000,102400000")
const int maxl=1e5+10;
const int mod=1e6+3;
int n,k,cnt,len,ans,ansu,ansv,top;
int a[maxl],ehead[maxl],val[maxl],num[mod];
long long t[mod],inv[mod],s[maxl];
bool vis[maxl];
vector <int> tmp;
struct ed
{
int to,nxt;
}e[maxl<<1];
struct centertree
{
int ans,n,mini;
int son[maxl];
inline void dfs(int u,int fa)
{
son[u]=1;int v,ret=0;
for(int i=ehead[u];i;i=e[i].nxt)
{
v=e[i].to;
if(vis[v] || v==fa) continue;
dfs(v,u);
son[u]+=son[v];
ret=max(son[v],ret);
}
ret=max(ret,n-son[u]);
if(ret<mini)
{
ans=u;
mini=ret;
}
}
inline int getcenter(int u)
{
ans=0;mini=2e9;
dfs(u,0);
return ans;
}
}tree;
inline void add(int u,int v)
{
e[++cnt].to=v;e[cnt].nxt=ehead[u];ehead[u]=cnt;
}
inline void prework()
{
for(register int i=1;i<=n;++i)
ehead[i]=0,vis[i]=false;
for(register int i=1;i<=n;++i)
scanf("%d",&val[i]);
int u,v;cnt=0;
for(register int i=1;i<n;++i)
{
scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
}
inline void getval(int u,int fa)
{
a[++len]=u;
int v;
for(int i=ehead[u];i;i=e[i].nxt)
{
v=e[i].to;
if(vis[v] || v==fa) continue;
t[v]=1ll*t[u]*val[v]%mod;
getval(v,u);
}
}
inline void calc(int u,int fa)
{
t[u]=val[u];
len=0;
getval(u,0);
int x,y,d;
for(register int i=1;i<=len;++i)
{
d=1ll*k*inv[t[a[i]]]%mod;
d=1ll*d*inv[val[fa]]%mod;
if(num[d]>0)
{
ans=1;
x=min(num[d],a[i]);
y=max(num[d],a[i]);
if(x<ansu)
ansu=x,ansv=y;
else if(x==ansu && y<ansv)
ansu=x,ansv=y;
}
}
for(register int i=1;i<=len;++i)
if(num[t[a[i]]]>0)
num[t[a[i]]]=min(num[t[a[i]]],a[i]);
else
num[t[a[i]]]=a[i],s[++top]=t[a[i]];
}
inline void solv(int u)
{
vis[u]=true;
int v,rt;top=0;num[1]=u;
for(int i=ehead[u];i;i=e[i].nxt)
{
v=e[i].to;
if(vis[v]) continue;
calc(v,u);
}
for(int i=1;i<=top;++i)
num[s[i]]=0;
num[1]=0;
top=0;
for(int i=ehead[u];i;i=e[i].nxt)
{
v=e[i].to;
if(vis[v]) continue;
tree.n=tree.son[v];
rt=tree.getcenter(v);
solv(rt);
}
}
inline void mainwork()
{
ans=0;ansu=mod;ansv=mod;
tree.n=n;
int rt=tree.getcenter(1);
solv(rt);
}
inline void print()
{
if(!ans)
puts("No solution");
else
printf("%d %d\n",ansu,ansv);
}
int main()
{
inv[1]=1;
for(int i=2;i<mod;++i)
inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
while(~scanf("%d%d",&n,&k))
{
prework();
mainwork();
print();
}
return 0;
}
本文深入探讨了一道ACM竞赛题目,通过点分治算法结合线性逆元技巧,解决了一个关于寻找字典序最小解的问题。文章详细介绍了如何避免重复计算,以及如何使用桶记录和更新数组来找到最优解。此外,还提到了如何预处理逆元以减少计算复杂度。
522

被折叠的 条评论
为什么被折叠?



