状态分块优化转移

如题:

题目描述

白兔有一颗n个点以1为根的树。树上每个结点有一个权值val。 如果两个点a,b满足a是b的祖先且val[b]|val[a],则白兔可以直接从a跳到b。 现在白云想知道,对于每一个点k,白兔从1号点跳若干步到达k号点的方案数是多少? 两个方案不同为它们经过的点数不同或者某一步到达了不同的点。

n<=100000 , val <= 10^18


很容易想到n^2的做法,

但是数据范围为

没法做,这时看整除这个条件,一看就让人想到分解质因子。

发现所有的值都为一个10^18以内的数的约数,

这个数的约数为10^5级别,

那么我们可以边dfs边枚举当前这个数的倍数(先将倍数离散化,用vector存整除关系),而非枚举父亲,

尽管这样看复杂度还是O(n^2)

但是枚举倍数就给了我们一个机会将状态分块

我们将1号点的值val[1],令val[1]=a*b (gcd(a,b)=1)

那么每一个点i ,存在val[i] = c*d ( gcd(c,d)=1 ) 使得 c|a , d|b

那么我们就从枚举val[i]的倍数,

变成了同时枚举c和d的倍数。(状态由一维变为两维)

但是这样时间复杂度还是不变。

回想一下,枚举倍数相当于DP中的填表法。

那我们用一下刷表法试试,将一个数对其因数的贡献存在一个数组里,用时直接O(1)取出。

但是这样时间复杂度还是不变。

那么神奇的事情发生了,

如果我对于第一维用刷表法,第二维用填表法,那时间复杂度是多少呢?

你会惊奇的发现它变为了O(n * (a的因数个数)+n * (b的因数个数)) ( a * b = val[1] )

当a和b的因数个数相近时,为O(n * sqrt(n))

可以换种方式理解,

这像是一个二维区域和的问题

填表法相当于你每次询问时都遍历所有的点求和,询问O(n^2),修改O(1)

刷表法相当于你每次修改时都更改一下所有你存储的二维前缀和,询问O(1),修改O(n^2);

一起用相当于每次修改时只更改一维前缀和,每次询问时查n个一维前缀和的的,询问O(n),修改O(n)。

可能很多时候大家都习惯于O((log(n))^2 )的树状数组,才导致这种方法比较冷门。

但例题求的不是区域和而是特定的几行几列(整除),这种方法才有用武之地。

当动态一维修改询问只能O(n)时,不如尝试一下将状态拆为两维,优化至O(sqrt(n))

代码:

#include<cstdio>
#include<cstring>
#include<cctype>
#include<algorithm>
#include<vector>
#include<map>
#define mod 1000000007
#define maxn 200005
#define LL long long
using namespace std;

int n,l1,l2,Mid;
int info[maxn],Prev[maxn*2],to[maxn*2],cnt_e;
inline void Node(int u,int v){ Prev[++cnt_e]=info[u],info[u]=cnt_e,to[cnt_e]=v;}
vector<int>G[2][1005];

template <class T> inline void read(T &res)
{
    char ch;
    while(!isdigit(ch=getchar()));
    for(res=ch-'0';isdigit(ch=getchar());res=res*10+ch-'0');
}

LL val[maxn];
map<LL,int>Fac;

inline LL mul(LL a,LL b,LL c){ return (a*b-(LL)((long double)a/c*b+1e-10)*c)%c;}
inline LL gcd(LL a,LL b){ return !b ? a : gcd(b,a%b); }
inline LL power(LL base,LL k,LL P)
{
	LL ret=1;
	for(;k;k>>=1,base=mul(base,base,P)) if(k&1) ret=mul(ret,base,P);
	return ret;
}

bool Miller_Rabin(LL n)
{
	if(n==2) return 1;
	LL res=n-1,k=0;
	for(;!(res&1);res>>=1,k++);
	for(int i=1;i<=6;i++)
	{
		LL now=((rand()<<16)+rand())%(n-2)+2,pre=power(now,res,n);
		for(int j=1;j<=k;j++)
			if((now=mul(pre,pre,n))==1 && pre!=1 && pre!=n-1) return 0;
			else pre=now;
		if(now!=1) return 0;
	}
	return 1;
}

LL Rho(LL n,LL c)
{
	LL a=((rand()<<16)+rand())%(n-2)+2,b=a,d=1,k=1,i=1;
	for(;d==1;k++)
	{
		d=gcd(abs((a=(mul(a,a,n)+c)%n)-b),n);
		(k==i) && (b=a,i<<=1);
	}
	return (d==n) ? Rho(n,c+1) : d;
}

void Pollard(LL n)
{
	if(Miller_Rabin(n))
	{
		Fac[n]++;
		return;
	}
	LL d=Rho(n,3);
	Pollard(d);
	Pollard(n/d);
}

LL c[30],p[30],cnt_fac;
LL num[2][1005];

LL dp[maxn],had[1005][1005];
inline LL cut(LL a){ return a>=mod ? a-mod : a;}

void dfs(int now,int ff)
{
	LL u=1,v;
	for(int i=0;i<Mid;i++)
		while(val[now]%p[i]==0)
			u*=p[i],val[now]/=p[i];
	u=lower_bound(num[0],num[0]+l1,u)-num[0],v=lower_bound(num[1],num[1]+l2,val[now])-num[1];
	for(vector<int>::iterator it=G[1][v].begin();it!=G[1][v].end();it++)
		dp[now]=cut(dp[now]+had[u][*it]);
	for(vector<int>::iterator it=G[0][u].begin();it!=G[0][u].end();it++)
		had[*it][v]=cut(had[*it][v]+dp[now]);
	for(int i=info[now];i;i=Prev[i])
		if(to[i]!=ff)
			dfs(to[i],now);
	for(vector<int>::iterator it=G[0][u].begin();it!=G[0][u].end();it++)
		had[*it][v]=cut(had[*it][v]-dp[now]+mod);
}

int main()
{
	/*
	freopen("walk.in","r",stdin);
	freopen("walk.out","w",stdout);
	*/
	scanf("%d",&n);
	for(int u,v,i=1;i<n;i++)
	{
		read(u),read(v);
		Node(u,v),Node(v,u);
	}
	for(int i=1;i<=n;i++) read(val[i]);
	Pollard(val[1]);
	for(map<LL,int>::iterator it=Fac.begin();it!=Fac.end();it++)
		p[cnt_fac]=(*it).first,c[cnt_fac++]=(*it).second;
		
	int Min=1<<30,Loc;
	for(int sta=0;sta<1<<cnt_fac;sta++)
	{
		int siz[2]={1,1};
		for(int i=0;i<cnt_fac;i++)
			if(sta>>i & 1) siz[1]*=c[i]+1;
			else siz[0]*=c[i]+1;
		if(max(siz[0],siz[1])<Min) Min=max(siz[0],siz[1]),l1=siz[1],l2=siz[0],Loc=sta;
	}
	
	
	for(int i=0;i<cnt_fac;i++)
		if(Loc>>i & 1)
			c[i]*=-1,Mid++;
	
	for(int l=0,r=cnt_fac-1;l<r;l++)
		if(c[l]>0)
			swap(c[l],c[r]),swap(p[l--],p[r--]);
	
	for(int i=0;i<Mid;i++) c[i]*=-1;
	for(int i=0;i<l1;i++)
	{
		int tmp=i;num[0][i]=1;
		for(int j=0;j<Mid;tmp/=c[j++]+1)
			for(;tmp%(c[j]+1);) tmp--,num[0][i]*=p[j];
	}
	
	sort(num[0],num[0]+l1);
	for(int i=0;i<l1;i++)
		for(int j=0;j<l1;j++)
			if(num[0][i]%num[0][j]==0)
				G[0][i].push_back(j);
		
	for(int i=0;i<l2;i++)
	{
		int tmp=i;num[1][i]=1;
		for(int j=Mid;j<cnt_fac;tmp/=c[j++]+1)
			for(;tmp%(c[j]+1);) tmp--,num[1][i]*=p[j];
	}
	sort(num[1],num[1]+l2);
	for(int i=0;i<l2;i++)
		for(int j=0;j<l2;j++)
			if(num[1][j]%num[1][i]==0)
				G[1][i].push_back(j);
			
	dp[1]=1;
	dfs(1,0);
	for(int i=1;i<=n;i++)
		printf("%lld\n",dp[i]);
} 

但是这样时间复杂度还是不变。

猜你喜欢

转载自blog.csdn.net/qq_35950004/article/details/80792343