【拉格朗日插值】HDU6453 Counting Sheep in Ami Dongsuo

版权声明:这是蒟蒻的BLOG,神犇转载也要吱一声哦~ https://blog.csdn.net/Dream_Lolita/article/details/89171675

【题目】
HDU
一幅 n n 个点 m m 条边的有向无环图,每个点上有一个不超过 w w 的正整数。三个人同时从某个点出发,每个人选择任意一条路径(可相同)走到某个点,三个人走到点点权和即旅行的特征值。求特征值为 0 3 w 0\sim 3w 的旅行个数分别有多少个,答案对 1 0 9 + 7 10^9+7 取模。
n 1 0 4 , m 3 × 1 0 4 , w 400 n\leq 10^4,m\leq 3\times 10^4,w\leq 400

【解题思路】
一种很暴力的想法是求出从每个点出发走出每一种权值分别有多少种方式,然后求三次幂就可以了。
这样是 O ( n w log w ) O(nw\log w) 的,还有一个 3 3 的常数以及要写毛爷爷 FFT \text{FFT} 的大常数,并不能过。

考虑拉格朗日插值,将每个点的点权看作 x w x^w 的指数,求三次幂就真的只是三次幂了,相当于指数相加,就是点乘。
将每个点的系数(方案)加起来,用 x = 0 3 w x=0\sim 3w 代入,再插值回去就可以了。

复杂度 O ( n w + w 3 ) O(nw+w^3)

这个拉格朗日插值求系数还得推一推,不过也就是套公式而已。

这题卡常,但是我知道了常数优化的一个点:加一个数的函数,如果可能出现负数,在可以判断正负号的情况下,分开写常数可以少一半。

【参考代码】

#include<bits/stdc++.h>
#define pb push_back
using namespace std;

const int N=1e4+10,M=1234,mod=1e9+7,inv6=(mod+1)/6;

namespace IO
{
	inline int read()
	{
		int ret=0;char c=getchar();
		while(!isdigit(c)) c=getchar();
		while(isdigit(c)) ret=ret*10+(c^48),c=getchar();
		return ret;
	}
	void write(int x){if(x>9)write(x/10);putchar(x%10^48);}
	inline void writeln(int x){write(x);putchar('\n');}
	inline void writesp(int x){write(x);putchar(' ');}
}
using namespace IO;

namespace Math
{
	int inv[M],p0[M],fac[M],ifac[M],pw[M][M];
	inline int Add(int x){return x>=mod?x-mod:x;}
	inline void add(int &x,int y){x=Add(x+y);}
	inline int Sub(int x){return x<0?x+mod:x;}
	inline void sub(int &x,int y){x=Sub(x-y);}
	inline int mul(int x,int y){return 1ll*x*y%mod;}
	inline int qpow(int x,int y){int res=1;for(;y;y>>=1,x=mul(x,x))if(y&1)res=mul(res,x);return res;}
	void initmath()
	{
		inv[0]=inv[1]=1;for(int i=2;i<M;++i) inv[i]=mul(mod-mod/i,inv[mod%i]);
		fac[0]=1;for(int i=1;i<M;++i) fac[i]=mul(fac[i-1],i);
		ifac[M-1]=qpow(fac[M-1],mod-2);for(int i=M-2;~i;--i)ifac[i]=mul(ifac[i+1],i+1);
		p0[0]=1;p0[1]=qpow(mod-1,mod-2);for(int i=2;i<M;++i) p0[i]=mul(p0[i-1],p0[1]);
		for(int i=0;i<M;++i)
		{
			pw[i][0]=1;
			for(int j=1;j<M;++j) pw[i][j]=mul(pw[i][j-1],i);
		}
	}
}
using namespace Math;

namespace DreamLolita
{
	int n,m,w;
	int du[N],p[N],a[N];
	int A[M],sum[M],now[M],ans[M],f[N][M][4];
	vector<int>mp[N];
	queue<int>q;
	void lagrange(int *a,int n,int *res)
	{
		sum[0]=1;
		for(int i=0;i<=n;++i) 
		{
			for(int j=i+1;j;--j) sum[j]=mul(sum[j],mod-i),add(sum[j],sum[j-1]);
			sum[0]=mul(sum[0],mod-i);
		}
		for(int i=0;i<=n;++i)
		{
			int tot=a[i];
			tot=mul(tot,ifac[i]);tot=mul(tot,mul(ifac[n-i],p0[n-i]));
			int coe=mul(p0[1],inv[i]);now[0]=mul(sum[0],coe);
			for(int j=1;j<=n;++j) now[j]=mul(Sub(sum[j]-now[j-1]),coe);
			for(int j=0;j<=n;++j) add(res[j],mul(tot,now[j]));
		}
	}
	void clear()
	{
		for(int i=1;i<=n;++i) mp[i].clear(),du[i]=0;
		memset(A,0,sizeof(A));memset(sum,0,sizeof(sum));memset(ans,0,sizeof(ans));
	}
	void init()
	{
		n=read();m=read();w=read()*3;
		for(int i=1;i<=n;++i) a[i]=read();
		while(m--)
		{
			int x=read(),y=read();
			mp[x].pb(y);++du[y];
		}
	}
	void solve()
	{
		int cnt=0;
		for(int i=1;i<=n;++i) if(!du[i]) q.push(i);
		while(!q.empty())
		{
			int x=q.front();q.pop();p[++cnt]=x;
			for(int j=0;j<(int)mp[x].size();++j)
			{
				int v=mp[x][j];
				if(!--du[v]) q.push(v);
			} 
		}
		for(int i=n;i;--i) 
		{
			int x=p[i];
			for(int j=0;j<=w;++j) for(int k=1;k<=3;++k) f[x][j][k]=pw[j][a[x]*k];
			for(int j=0;j<(int)mp[x].size();++j)
			{
				int v=mp[x][j];
				for(int k=0;k<=w;++k) for(int l=1;l<=3;++l) add(f[x][k][l],f[v][k][l]);
			}
			for(int j=0;j<=w;++j)
			{
				int coe=qpow(f[x][j][1],3);
				sub(coe,mul(3,mul(f[x][j][1],f[x][j][2])));
				add(coe,mul(2,f[x][j][3]));
				add(A[j],coe);
			}	
		}	
		lagrange(A,w,ans);
		for(int i=1;i<w;++i) writesp(mul(inv6,ans[i])); 
		writeln(mul(inv6,ans[w]));
	}
	void solution(){init();solve();clear();}
}

int main()
{
#ifdef Durant_Lee
	freopen("HDU6453.in","r",stdin);
	freopen("HDU6453.out","w",stdout);
#endif
	initmath();int T=read();
	for(int i=1;i<=T;++i) printf("Case #%d: ",i),DreamLolita::solution();
	return 0;
}

猜你喜欢

转载自blog.csdn.net/Dream_Lolita/article/details/89171675