s_computer @ 2023-10-28 03:42:52
求助求助,7,9被卡,但时间复杂度应该没问题。 如果有能帮忙看看的朋友,提前感谢!
#include<bits/stdc++.h>
using namespace std;
#define int long long
int k;
int h[40010],e[40010],ne[40010],w[40010],idx;
int use[20010];
int d[20010];
int f[20010],siz[20010];
int cnt,Siz,rt;
void add(const int &a,const int &b,const int &c)
{
w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
w[idx]=c,e[idx]=a,ne[idx]=h[b],h[b]=idx++;
}
//求重心
void getroot(const int &x,const int &fa)//x为当前点,fa为父亲节点
{
f[x]=0,siz[x]=1;//f表示这个点最大子树的大小,siz是这个点子树大小的和
for(int i=h[x];i!=-1;i=ne[i])//枚举儿子
{
int y=e[i];
if(use[y]||y==fa) continue;//use表示之前遍历过了,这里没啥用
getroot(y,x);//往下遍历
f[x]=max(f[x],siz[y]);//更新f
siz[x]+=siz[y];
}
f[x]=max(f[x],Siz-siz[x]);//Siz表示在现在这棵子树中点的总数,开始时Siz=n,除了枚举的儿子所在的子树外,还有一棵子树是上面的那一堆,容斥原理
if(f[x]<f[rt]) rt=x;//更新root
}
//求到重心的距离
int findunder(int l,const int &x)//找下界,r左移
{
int ans=0,r=cnt;
while(l<=r)
{
int mid(l+r>>1);
if(d[mid]<x) l=mid+1;
else ans=mid,r=mid-1;
}
return ans;
}
int findup(int l,const int &x)//找上界
{
int ans=0,r=cnt;
while(l<=r)
{
int mid(l+r>>1);
if(d[mid]<=x) ans=mid,l=mid+1;
else r=mid-1;
}
return ans;
}
void getd(const int &x,const int &fa,const int &dis)
{
for(int i=h[x];i!=-1;i=ne[i])
{
int j=e[i];
if(use[j]||j==fa) continue;
d[++cnt]=dis+w[i];
getd(j,x,d[cnt]);
}
}
//求答案
int getans(const int &x,const int &dis,const int&k)
{
d[cnt=1]=dis;
getd(x,0,dis);
sort(d+1,d+1+cnt);
int l=1,ans=0;
while(l<cnt&&d[l]+d[cnt]<k) l++;
while(l<cnt&&d[l]<=k-d[l])//保证要找的在后面
{
int d1=findunder(l,k-d[l]),d2=findup(l,k-d[l]);//也可以用lower_bound()和upper_bound()实现
if(d2>=d1)ans += d2-d1+1;//ans表示符合要求的
l++;
}
return ans;
}
int ans=0;//以x点为重心的树产生的符合k的答案
//分治划分
void fz(const int &x,int k)//x就是树根
{
//cout<<"x:"<<x<<endl;
use[x]=1;
ans += getans(x,0,k);//
//printf("x=%d,ans1=%d\n",x,ans);
for(int i=h[x];i!=-1;i=ne[i])
{
int j=e[i];
if(use[j]) continue;
int dis=w[i];
ans -= getans(j,dis,k);
// printf("x=%d,ans2=%d\n",x,ans);
Siz=siz[j],rt=0;
getroot(j,x);//找子树中的重心
// printf("%d kid-ans %d",x,rt);
// rts[j]=rt;
fz(rt,k);
}
}
signed main()
{
f[0]=1e9+7;
memset(h,-1,sizeof h);
int n,m;
scanf("%lld%lld",&n,&m);
for(int i=0;i<n-1;i++)
{
int a,b,c;
scanf("%lld%lld%lld",&a,&b,&c);
add(a,b,c);
}
/*for(int i=1;i<=n;i++)
{
printf("%d:",i);
for(int j=h[i];j!=-1;j=ne[j]) printf("%d ",e[j]);
printf("\n");
}*/
Siz=n;
getroot(1,1);
//for(int i=1;i<=n;i++) cout<<i<<" f:"<<f[i]<<" siz:"<<siz[i]<<endl;
//cout<<"rt:"<<rt<<endl;
int root=rt;
int q[100];
for(int i=0;i<m;i++)
{
scanf("%lld",&q[i]);
//memset(use,0,sizeof use);
}
for(int i=0;i<m;i++)
{
ans=0;
for(int i=0;i<=n;i++)use[i]=0;
fz(root,q[i]);
if(ans) printf("AYE\n");
else printf("NAY\n");
}
}
by s_computer @ 2023-10-28 14:24:20
这道题确实可以优化,上述代码中相当于对于每一个询问都建一遍树,这样增加了时间复杂度。我们可以之间一遍树,将询问和每个询问的结果都存起来,在getans时统一处理,这样就实现了优化,可以AC。代码如下:
#include<bits/stdc++.h>
using namespace std;
const int N=10005;
const int INF=10000005;
int k;
int h[40010],e[40010],ne[40010],w[40010],idx;
int use[20010];
int d[20010],dis[N];
int f[20010],siz[20010];
int dep[10010];
int cnt,Siz,rt=1;
int ans[N];
int mx=1e9+7;
int n,m;
int ask[N];
void add(const int &a,const int &b,const int &c)
{
w[idx]=c,e[idx]=b,ne[idx]=h[a],h[a]=idx++;
w[idx]=c,e[idx]=a,ne[idx]=h[b],h[b]=idx++;
}
//求重心
void getroot(int u,int fa)//x为当前点,fa为父亲节点
{
f[u]=0,siz[u]=1;//f表示这个点最大子树的大小,siz是这个点子树大小的和
for(int i=h[u];i!=-1;i=ne[i])//枚举儿子
{
int y=e[i];
if(use[y]||y==fa) continue;//use表示之前遍历过了,这里没啥用
getroot(y,u);//往下遍历
siz[u]+=siz[y];
f[u]=max(f[u],siz[y]);//更新f
}
f[u]=max(f[u],Siz-siz[u]);//Siz表示在现在这棵子树中点的总数,开始时Siz=n,除了枚举的儿子所在的子树外,还有一棵子树是上面的那一堆,容斥原理
if(f[u]<mx)
{
mx=f[u];
rt=u;//更新root
}
}
//求到重心的距离
void getd(int x,int fa)
{
dis[++cnt]=d[x];
// printf("d[x]=%d,cnt=%d,dis[cnt]=%d\n",d[x],cnt,dis[cnt]);
//printf("dis[1]=%d dis[2]=%d\n",dis[1],dis[2]);
for(int i=h[x];i!=-1;i=ne[i])
{
int j=e[i];
if(j==fa||use[j]) continue;
d[j]=d[x]+w[i];
getd(j,x);
}
}
//求答案
void getans(const int &x,int w,const int sign)
{
cnt=0;
d[x]=w;
getd(x,0);
sort(dis+1,dis+1+cnt);
for(int i=1;i<=m;i++)
{
int l=1,r=cnt;
while(l<r)
{
if(dis[l]+dis[r]<=ask[i])
{
//printf("dis[1]=%d dis[2]=%d l=%d r=%d\n",dis[1],dis[2],l,r);
//printf("dis[l]=%d dis[r]=%d l+r%=d",dis[l],dis[r],dis[l]+dis[r]);
if(dis[l]+dis[r]==ask[i]) ans[i] += sign;
++l;
}
else --r;
}
}
}
//分治划分
void fz(const int &x)//x就是树根
{
//cout<<"x:"<<x<<endl;
use[x]=1;
getans(x,0,1);//
//printf("x=%d,ans1=%d\n",x,ans);
for(int i=h[x];i!=-1;i=ne[i])
{
int j=e[i];
if(use[j]) continue;
getans(j,w[i],-1);
// printf("x=%d,ans2=%d\n",x,ans);
Siz=siz[j],mx=1e9+7;
getroot(j,x);//找子树中的重心
// printf("%d kid-ans %d",x,rt);
// rts[j]=rt;
fz(rt);
}
}
signed main()
{
f[0]=1e9+7;
memset(h,-1,sizeof h);
scanf("%d%d",&n,&m);
for(int i=0;i<n-1;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
/*for(int i=1;i<=n;i++)
{
printf("%d:",i);
for(int j=h[i];j!=-1;j=ne[j]) printf("%d ",e[j]);
printf("\n");
}*/
Siz=n;
getroot(1,1);
//for(int i=1;i<=n;i++) cout<<i<<" f:"<<f[i]<<" siz:"<<siz[i]<<endl;
//cout<<"rt:"<<rt<<endl;
int root=rt;
for(int i=0;i<=n;i++)use[i]=0;
for(int i=1;i<=m;i++)
{
scanf("%d",&ask[i]);
//memset(use,0,sizeof use);
}
fz(root);
for(int i=1;i<=m;i++)
{
if(ans[i]>0) printf("AYE\n");
else printf("NAY\n");
}
}