HAOI2018 简要题解

奇怪的背包
发现就是求一个 n + 1 n+1 n+1元方程组 a 1 x 1 + a 2 x 2 + ⋅ ˙ ⋅ ⋅ + a n x n + a n + 1 m o d = w a_1x_1+a_2x_2+\dot\cdot\cdot\cdot+a_nx_n+a_{n+1}mod=w a1x1+a2x2+˙+anxn+an+1mod=w的解数,两个解被认为是不同的当且 a i a_i ai非零的位置 i i i组成的集合不同。
于是可以按照 e x g c d exgcd exgcd的方法递推出这个方程有解的充要条件: g c d ( a 1 , a 2 , ⋅ ˙ ⋅ ⋅ a n + 1 ) ∣ w gcd(a_1,a_2,\dot\cdot\cdot\cdot a_{n+1})|w gcd(a1,a2,˙an+1)w,当然也可以用裴蜀定理秒证。
于是可以设计状态 f i , j f_{i,j} fi,j表示前 i i i个数的 g c d gcd gcd j j j的方案数 d p dp dp一波。
注意一个优化:由于最后要跟 m o d mod mod g c d gcd gcd,因此输入 a i a_i ai时可以直接跟 m o d mod mod取一个 g c d gcd gcd,这样 i i i的上限约为 m o d 1 3 mod^{\frac13} mod31
代码:

#include
#define ri register int
using namespace std;
inline int read(){
	int ans=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
	return ans;
}
const int N=1505,K=1000007,mod=1e9+7;
typedef long long ll;
int n,m=0,q,P,f[N][N],g[K],a[K],b[K],divv[K],tot=0,lim=0,pow2[K];
inline int gcd(int a,int b){while(b){int t=a;a=b,b=t-t/a*a;}return a;}
inline int add(const int&a,const int&b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(const int&a,const int&b){return a>=b?a-b:a-b+mod;}
inline int mul(const int&a,const int&b){return (ll)a*b%mod;}
namespace Hash{
	int id[K],sta[K],val[K],Mod=1000007,cnt=0;
	inline void init(){memset(id,-1,sizeof(id));}
	inline void insert(const int&x,const int&v){
		int pos=x%Mod;
		if(!pos)++pos;
		while(~id[pos])pos=pos==Mod-1?1:pos+1;
		sta[id[pos]=++cnt]=x,val[cnt]=v;
	}
	inline int query(const int&x){
		int pos=x%Mod;
		if(!pos)++pos;
		while(~id[pos]&&sta[id[pos]]!=x)pos=pos==Mod-1?1:pos+1;
		return val[id[pos]];
	}
}
int main(){
	n=read(),q=read(),P=read();
	for(ri i=1;i<=n;++i)a[i]=gcd(read(),P);
	sort(a+1,a+n+1);
	for(ri i=1;i<=n;++i)a[i]^a[i-1]?a[++tot]=a[i],b[tot]=1:++b[tot];
	n=tot;
	for(ri i=1;i<=n;++i)lim=max(lim,b[i]);
	pow2[0]=1;
	for(ri i=1;i<=lim;++i)pow2[i]=mul(pow2[i-1],2);
	for(ri i=1;i<=P/i;++i){
		if(P!=P/i*i)continue;
		divv[++m]=i;
		if(P!=i*i)divv[++m]=P/i;
	}
	sort(divv+1,divv+m+1);
	Hash::init();
	for(ri i=1;i<=m;++i)Hash::insert(divv[i],i);
	f[0][m]=1;
	for(ri i=1,gc;i<=n;++i){
		for(ri j=1;j<=m;++j){
			gc=Hash::query(gcd(a[i],divv[j]));
			f[i][j]=add(f[i][j],f[i-1][j]);
			f[i][gc]=add(f[i][gc],mul(f[i-1][j],dec(pow2[b[i]],1)));
		}
	}
	for(ri i=1;i<=m;++i)for(ri j=1;j<=i;++j)if(divv[i]==divv[i]/divv[j]*divv[j])g[i]=add(g[i],f[n][j]);
	while(q--)cout<<g[Hash::query(gcd(read(),P))]<<'\n';
	return 0;
}

反色游戏
神题啊蒟蒻只能膜大佬的题解。
首先有一个是个 O I e r OIer OIer都知道的 60 60 60分高斯消元解异或方程组。
以及一个很显然的结论:当且仅当每个连通块的黑点个数为偶数的时候才有解。
原因是对于任意两个黑点间的任意一条路径,如果我们把这条路径上面的所有边都选择的话这两个点状态将取反而其它点状态不变,因此对于一棵树它的答案是唯一的。
现在考虑一堆返祖边,显然每条都会对答案产生贡献。
于是对于一张图它的方案数就是 2 ∣ V ∣ − ∣ E ∣ + 连 通 块 数 2^{|V|-|E|+连通块数} 2VE+
这样我们可以处理出不删点的答案,然后如果要删点我们统计一下每个点对于答案的影响即可。
显然只用分三种情况:

  1. 该点是割点
  2. 该点是独立连通块
  3. 该点是一个没有特殊性质的点

细节见代码。
代码:

#include
#define ri register int
using namespace std;
inline int read(){
	int ans=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
	return ans;
}
const int N=2e5+5,mod=1e9+7;
typedef long long ll;
inline int add(const int&a,const int&b){return a+b>=mod?a+b-mod:a+b;}
inline int mul(const int&a,const int&b){return (ll)a*b%mod;}
int n,m,pow2[N],dfn[N],low[N],ban[N],odd[N],stk[N],det[N],top,tot,loopcnt=0,sig=0;
vector<int>e[N];
bool cut[N],col[N],ori[N],black,allcol[N],vis[N];
char s[N];
void tarjan(int p,int fa){
	int ch=0;
	stk[++top]=p,dfn[p]=low[p]=++tot;
	for(ri i=0,v;i<e[p].size();++i){
		if((v=e[p][i])==fa)continue;
		if(dfn[v]){low[p]=min(low[p],low[v]);continue;}
		tarjan(v,p),low[p]=min(low[p],low[v]);
		if(low[v]>=dfn[p]){
			++ch;
			if(fa)cut[p]=1;
			int x;
			bool fl=0;
			do x=stk[top--],fl^=col[x];while(x^v);
			col[p]^=fl,++det[p],odd[p]+=(int)fl;
		}
	}
	if(!fa&&ch>1)cut[p]=1;
	if(cut[p]){
		if(fa)++det[p];
		odd[p]+=col[p]^black;
		if(loopcnt-(int)black+odd[p])ban[p]=1;
	}
	else if(loopcnt-(int)black+(int)(ori[p]^black))ban[p]=1;
}
void dfs(int p){black^=col[p],vis[p]=1;for(ri i=0;i<e[p].size();++i)if(!vis[e[p][i]])dfs(e[p][i]);}
int main(){
	pow2[0]=1;
	for(ri i=1;i<=200000;++i)pow2[i]=mul(pow2[i-1],2);
	for(ri tt=read();tt;--tt){
		n=read(),m=read(),top=tot=loopcnt=sig=0;
		for(ri i=1;i<=n;++i)e[i].clear(),cut[i]=low[i]=dfn[i]=vis[i]=det[i]=odd[i]=ban[i]=0;
		for(ri i=1,u,v;i<=m;++i)u=read(),v=read(),e[u].push_back(v),e[v].push_back(u);
		scanf("%s",s+1);
		for(ri i=1;i<=n;++i)ori[i]=col[i]=s[i]^48;
		for(ri i=1;i<=n;++i)if(!vis[i])black=0,++sig,dfs(i),allcol[i]=black,loopcnt+=(int)black;
		if(loopcnt>1){for(ri i=1;i<=n+1;++i)putchar('0'),putchar(i>n?'\n':' ');continue;}
		for(ri i=1;i<=n;++i)if(!dfn[i])top=tot=0,black=allcol[i],tarjan(i,0),cut[i]|=!e[i].size();
		cout<<(loopcnt?0:pow2[m-n+sig])<<' ';
		for(ri i=1;i<=n;++i){
			if(ban[i])putchar('0');
			else cout<<pow2[(m-e[i].size())-(n-1)+sig+(cut[i]?det[i]-1:0)];
			putchar(i^n?' ':'\n');
		}
	}
	return 0;
}

字串覆盖
一道有点毒瘤的题。
有个显然的贪心:对于当前所有可以覆盖的串,我们选左端点最靠前的覆盖掉。
我只会 n 2 2000 \frac{n^2}{2000} 2000n2的点和 n n n很小的测试点。
这种情况显然直接用一个 s a m sam sam+倍增+线段树合并一直暴力匹配就行了。
然后考虑 r − l ≤ 50 r-l\le50 rl50的怎么做。
我们可以用一个倍增来氵过去。
考虑维护一个 n x t L , i , j nxt_{L,i,j} nxtL,i,j表示对于以 i i i为左端点的长度为 L L L的字符串,下 2 j 2^j 2j个跟它相同的字符串的左端点是啥,并再记一个 s u m L , i , j sum_{L,i,j} sumL,i,j维护这些不相交的相同串对于答案的贡献。
然后每次我们倍增求出答案即可。
注意可以用 h a s h hash hash来快速判断两个串是否相同。
然后还有就是直接这样建倍增数组会 M L E MLE MLE,我们需要将询问离线然后按照 L L L排序,这样可以省掉倍增数组的第一维。
幸好各大OJ没有卡我每次memset倍增数组的常不然自闭了
代码:

#include
#define ri register int
using namespace std;
inline int read(){
	int ans=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
	return ans;
}
typedef long long ll;
const int N=1e5+5,M=4e6+5;
int rt[M],n,q;
ll ans[N],K;
char ss[N],tt[N];
struct Node{int s,t,l,r,id,len;friend inline bool operator<(const Node&a,const Node&b){return a.len<b.len;}}qry[N];
namespace SGT{
	#define lc (son[p][0])
	#define rc (son[p][1])
	#define mid (l+r>>1)
	int son[M][2],tot=0;
	bool exi[M];
	inline void build(int&p,int l,int r,int k){
		if(!p)p=++tot;
		exi[p]=1;
		if(l==r)return;
		k<=mid?build(lc,l,mid,k):build(rc,mid+1,r,k);
	}
	inline int merge(int x,int y,int l,int r){
		if(!x||!y)return x+y;
		int p=++tot;
		exi[p]=exi[x]|exi[y];
		if(l==r)return p;
		lc=merge(son[x][0],son[y][0],l,mid);
		rc=merge(son[x][1],son[y][1],mid+1,r);
		return p;
	}
	inline int query(int p,int l,int r,int ql,int qr){
		if(!exi[p])return -1;
		if(l==r)return l;
		if(qr<=mid)return query(lc,l,mid,ql,qr);
		if(ql>mid)return query(rc,mid+1,r,ql,qr);
		int ret=query(lc,l,mid,ql,qr);
		return ~ret?ret:query(rc,mid+1,r,ql,qr);
	}
	#undef lc
	#undef rc
	#undef mid
}
namespace HASH{
	const int mod=1e9+7,Base=31;
	int mi[N],Hash[N],inv[N],Log[N];
	inline int mul(const int&a,const int&b){return (ll)a*b%mod;}
	inline int add(const int&a,const int&b){return a+b>=mod?a+b-mod:a+b;}
	inline int dec(const int&a,const int&b){return a>=b?a-b:a-b+mod;}
	inline int ksm(int a,int p){int ret=1;for(;p;p>>=1,a=mul(a,a))if(p&1)ret=mul(ret,a);return ret;}
	inline int get(int l,int r){return mul(dec(Hash[r],Hash[l-1]),inv[l-1]);}
	vector<vector<int> >Pos;
	map<int,int>id;
	int cnt,nxt[N][18];
	ll sum[N][18];
	inline void init(){
		for(ri i=2;i<=n;++i)Log[i]=Log[i>>1]+1;
		mi[0]=inv[0]=1;
		for(ri Inv=ksm(Base,mod-2),i=1;i<=n;++i){
			mi[i]=mul(mi[i-1],Base),inv[i]=mul(inv[i-1],Inv);
			Hash[i]=add(Hash[i-1],mul(tt[i]-'a'+1,mi[i]));
		}	
	}
	inline void build(int L){
		id.clear(),Pos.clear(),cnt=0,memset(nxt,0,sizeof(nxt)),memset(sum,0,sizeof(sum));
		for(ri i=L,tmp=0;i<=n;++i,tmp=0){
			for(ri j=i-L+1;j<=i;++j)tmp=add(tmp,mul(ss[j]-'a'+1,mi[j-(i-L+1)+1]));
			if(!id.count(tmp))id[tmp]=cnt++,Pos.resize(cnt+1);
			Pos[id[tmp]].push_back(i);
		}
		for(ri i=0,p;i<Pos.size();++i){
			for(ri up=Pos[i].size(),j=up-1;~j;--j){
				p=Pos[i][j];
				for(ri k=j+1;k<up;++k)if(Pos[i][k]-p>=L){nxt[p][0]=Pos[i][k],sum[p][0]=K-(Pos[i][k]-L+1);break;}
				for(ri k=1;k<=Log[up];++k)nxt[p][k]=nxt[nxt[p][k-1]][k-1],sum[p][k]=sum[p][k-1]+sum[nxt[p][k-1]][k-1];
			}
		}		
	}
	inline ll solve(int L,int l,int r,int tmp){
		if(!id.count(tmp))return 0;
		int p=id[tmp],st=lower_bound(Pos[p].begin(),Pos[p].end(),l+L-1)-Pos[p].begin();
		if(st==Pos[p].size())return 0;
		st=Pos[p][st];
		if(st>r)return 0;
		ll ret=K-(st-L+1);
		for(ri i=17;~i;--i)if(nxt[st][i]<=r&&nxt[st][i])ret+=sum[st][i],st=nxt[st][i];
		return ret;
	}
}
namespace sam{
	int son[N<<1][26],tot=1,last=1,len[N<<1],link[N<<1],st[N<<1][20],tid[N],tlen[N];
	inline void insert(int x,int id){
		int p=last,np=++tot;
		len[last=np]=len[p]+1,SGT::build(rt[np],1,n,id);
		while(p&&!son[p][x])son[p][x]=np,p=link[p];
		if(!p){link[np]=1;return;}
		int q=son[p][x],nq;
		if(len[q]==len[p]+1){link[np]=q;return;}
		len[nq=++tot]=len[p]+1,link[nq]=link[q],link[q]=link[np]=nq;
		memcpy(son[nq],son[q],sizeof(son[q]));
		while(p&&son[p][x]==q)son[p][x]=nq,p=link[p];
	}
	inline void init(){
		static int rk[N<<1],cnt[N<<1];
		for(ri i=1;i<=tot;++i)++cnt[len[i]];
		for(ri i=2;i<=tot;++i)cnt[i]+=cnt[i-1];
		for(ri i=tot;i;--i)rk[cnt[len[i]]--]=i;
		for(ri i=tot;i^1;--i)rt[link[rk[i]]]=SGT::merge(rt[link[rk[i]]],rt[rk[i]],1,n);
		for(ri i=1;i<=tot;++i)st[i][0]=link[i];
		for(ri j=1;j<20;++j)for(ri i=1;i<=tot;++i)st[i][j]=st[st[i][j-1]][j-1];
		for(ri i=1,p=1,nowlen=0;i<=n;++i){
			char x=tt[i]-'a';
			if(son[p][x])++nowlen,p=son[p][x];
			else{
				while(p&&!son[p][x])p=link[p];
				if(!p)nowlen=0,p=1;
				else nowlen=len[p]+1,p=son[p][x];
			}
			tid[i]=p,tlen[i]=nowlen;
		}
	}
    inline ll calc(int L,int p,int ql,int qr){
    	ll s1=0,s2=0;
    	while(ql<=qr){
    		int x=SGT::query(rt[p],1,n,ql,qr);
    		if(x==-1)return s1*(K+L-1)-s2;
    		++s1,s2+=x,ql=x+L;
    	}
    	return s1*(K+L-1)-s2;
    }
	inline ll solve(int L,int s,int t,int l,int r){
		if(tlen[r]<L)return 0;
		int p=tid[r];
		for(ri i=19;~i;--i)if(st[p][i]&&len[st[p][i]]>=L)p=st[p][i];
		return calc(L,p,s+L-1,t);
	}
}
inline void solve(){
	sam::init(),HASH::init();
	for(ri i=1,las=0;i<=q;++i){
		if(qry[i].len<=51){
			if(qry[i].len^las)HASH::build(qry[i].len);
			las=qry[i].len;
			ans[qry[i].id]=HASH::solve(qry[i].len,qry[i].s,qry[i].t,HASH::get(qry[i].l,qry[i].r));
		}
		else ans[qry[i].id]=sam::solve(qry[i].len,qry[i].s,qry[i].t,qry[i].l,qry[i].r);
	}
}
int main(){
	n=read(),K=read();
	scanf("%s%s",ss+1,tt+1),q=read();
	for(ri i=1;i<=n;++i)sam::insert(ss[i]-'a',i);
	for(ri i=1,s,t,l,r;i<=q;++i)s=read(),t=read(),l=read(),r=read(),qry[i]=(Node){s,t,l,r,i,r-l+1};
	sort(qry+1,qry+q+1);
	solve();
	for(ri i=1;i<=q;++i)cout<<ans[i]<<'\n';
	return 0;
}

苹果树
考虑每条边的贡献,对于第 i i i个插入点的父亲边,经过它的点对数为 s i z i ( n − s i z i ) siz_i(n-siz_i) sizi(nsizi)
这样可以设计状态 f i , j f_{i,j} fi,j表示对于第 i i i个点它的子树 s i z siz siz j j j的方案数。
这个直接可以上组合数学:
显然 j j j个后代必须在 i + 1 i+1 i+1~ n n n中选出,选法是 C n − i j − 1 C_{n-i}^{j-1} Cnij1,排列方案是 j ! j! j!,然后对于前 i i i个点排列方案为 i ! i! i!,剩下的 n − i − j + 1 n-i-j+1 nij+1个点插入进树的方案树则是 ( n − j − 1 ) ! ( i − 2 ) ! \frac{(n-j-1)!}{(i-2)!} (i2)!(nj1)!
于是
A n s = ∑ i = 2 n ∑ j = 1 n − i + 1 j ∗ ( n − j ) ∗ i ! ∗ C n − i j − 1 ∗ j ! ∗ ( n − j − 1 ) ! ( i − 2 ) ! Ans=\sum_{i=2}^n\sum_{j=1}^{n-i+1}j*(n-j)*i!*C_{n-i}^{j-1}*j!*\frac{(n-j-1)!}{(i-2)!} Ans=i=2nj=1ni+1j(nj)i!Cnij1j!(i2)!(nj1)!
         = ∑ i = 2 n ∑ j = 1 n − i + 1 j ∗ j ! ∗ i ∗ ( i − 1 ) ∗ C n − i j − 1 ∗ ( n − j ) ! \ \ \ \ \ \ \ \ =\sum_{i=2}^n\sum_{j=1}^{n-i+1}j*j!*i*(i-1)*C_{n-i}^{j-1}*(n-j)!         =i=2nj=1ni+1jj!i(i1)Cnij1(nj)!
代码:

#include
#define ri register int
using namespace std;
const int N=2005;
int n,mod,C[N][N],fac[N],ans;
int main(){
	scanf("%d%d",&n,&mod);
    for(ri i=0;i<=n;++i)C[i][0]=1;
    for(ri i=1;i<=n;++i){C[i][0]=1;for(ri j=1;j<=i;++j)C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;}
    fac[0]=1;
	for(ri i=1;i<=n;++i)fac[i]=(long long)fac[i-1]*i%mod;
    for(ri i=1;i<=n;++i)for(ri j=1;j<=n-i;++j)(ans+=(2ll*(n-j)*j*fac[i]%mod*C[n-i][j]%mod*fac[j]%mod*C[n-j-1][i-1]%mod*fac[n-j-i]%mod))%=mod;
    cout<<ans;
    return 0;
}

染色
容斥好题。
考虑容斥答案。
u p = m i n { ⌊ n s ⌋ , m } up=min\{\left\lfloor\frac ns\right\rfloor,m\} up=min{sn,m},于是考虑至少选出 i i i种恰好出现 s s s次的颜色的方案数 f i f_i fi
方案数为 C m i C_{m}^i Cmi,发现这样所有格子被分成了 i + 1 i+1 i+1份,其中第 i + 1 i+1 i+1份由剩余的 m − i m-i mi中颜色组成。
于是排列方案数为 n ! ( s ! ) i ∗ ( n − i s ) ! \frac{n!}{(s!)^i*(n-is)!} (s!)i(nis)!n!,并且剩下的 n − i s n-is nis个格子的总染色方案数为 ( m − i ) n − i s (m-i)^{n-is} (mi)nis
于是推出 f i = C m i ∗ n ! ( s ! ) i ∗ ( n − i s ) ! ∗ ( m − i ) n − i s f_i=C_{m}^i*\frac{n!}{(s!)^i*(n-is)!}*(m-i)^{n-is} fi=Cmi(s!)i(nis)!n!(mi)nis
现在考虑容斥出恰好选出选出 i i i种恰好出现 s s s次的颜色的方案数 a n s i ans_i ansi
a n s i = ∑ j = i u p ( − 1 ) j − i C j i f j ans_i=\sum_{j=i}^{up}(-1)^{j-i}C_j^if_j ansi=j=iup(1)jiCjifj
展开组合数得:
a n s i ∗ i ! = ∑ j = i u p ( − 1 ) j − i ( j − i ) ! j ! f k ans_i*i!=\sum_{j=i}^{up}\frac{(-1)^{j-i}}{(j-i)!}j!f_k ansii!=j=iup(ji)!(1)jij!fk
a i = ( − 1 ) i i ! , b i = i ! f i a_i=\frac{(-1)^i}{i!},b_i=i!f_i ai=i!(1)i,bi=i!fi
可以将 b b b翻转变成一个卷积的形式,然后上 n t t ntt ntt就完了。
代码:

#include
#define ri register int
using namespace std;
inline int read(){
	int ans=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar();
	return ans;
}
const int mod=1004535809,N=1e5+5,M=1e7+5;
typedef long long ll;
int lim,tim,n,m,s,w[N],fac[M],ifac[M],tmp[N];
inline int add(const int&a,const int&b){return a+b>=mod?a+b-mod:a+b;}
inline int dec(const int&a,const int&b){return a>=b?a-b:a-b+mod;}
inline int mul(const int&a,const int&b){return (ll)a*b%mod;}
inline int ksm(int a,int p){int ret=1;for(;p;p>>=1,a=mul(a,a))if(p&1)ret=mul(ret,a);return ret;}
inline int C(const int&n,const int&m){return mul(mul(fac[n],ifac[m]),ifac[n-m]);}
vector<int>A,B,pos;
inline void init(const int&up){
	lim=1,tim=0;
	while(lim<=up)lim<<=1,++tim;
	pos.resize(lim),A.resize(lim),B.resize(lim),pos[0]=0;
	for(ri i=0;i<lim;++i)pos[i]=(pos[i>>1]>>1)|((i&1)<<(tim-1));
}
inline void ntt(vector<int>&a,const int&type){
	for(ri i=0;i<lim;++i)if(i<pos[i])swap(a[i],a[pos[i]]);
	int typ=~type?3:(mod+1)/3,mult=mod-1>>1,wn,w,a0,a1;
	for(ri mid=1;mid<lim;mid<<=1,mult>>=1){
		wn=ksm(typ,mult);
		for(ri j=0,len=mid<<1;j<lim;j+=len){
			w=1;
			for(ri k=0;k<mid;++k,w=mul(w,wn)){
				a0=a[j+k],a1=mul(w,a[j+k+mid]);
				a[j+k]=add(a0,a1),a[j+k+mid]=dec(a0,a1);
			}
		}
	}
	if(type==-1)for(ri i=0,inv=ksm(lim,mod-2);i<lim;++i)a[i]=mul(a[i],inv);
}
struct poly{
	vector<int>a;
	poly(int k=0,int x=0){a.resize(k+1),a[k]=x;}
	inline int&operator[](const int&k){return a[k];}
	inline const int&operator[](const int&k)const{return a[k];}
	inline int deg()const{return a.size()-1;}
	inline poly extend(const int&k){poly ret=*this;return ret.a.resize(k+1),ret;}
	friend inline poly operator*(const poly&a,const poly&b){
		int n=a.deg(),m=b.deg();
		init(n+m);
		for(ri i=0;i<=n;++i)A[i]=a[i];
		for(ri i=n+1;i<lim;++i)A[i]=0;
		for(ri i=0;i<=m;++i)B[i]=b[i];
		for(ri i=m+1;i<lim;++i)B[i]=0;
		ntt(A,1),ntt(B,1);
		for(ri i=0;i<lim;++i)A[i]=mul(A[i],B[i]);
		poly ret;
		return ntt(A,-1),ret.a=A,ret;
	}
};
int main(){
	n=read(),m=read(),s=read();
	for(ri i=0;i<=m;++i)w[i]=read();
	int up=min(n/s,m),Up=max(s,max(n,m));
	fac[0]=ifac[0]=fac[1]=ifac[1]=1;
	for(ri i=2;i<=Up;++i)fac[i]=mul(fac[i-1],i),ifac[i]=mul(ifac[mod-mod/i*i],mod-mod/i);
	for(ri i=2;i<=Up;++i)ifac[i]=mul(ifac[i],ifac[i-1]);
	poly a(up),b(up);
	for(ri i=0;i<=up;++i)a[i]=i&1?mod-ifac[i]:ifac[i];
	for(ri i=0,inv=1,mult=ifac[s];i<=up;++i,inv=mul(inv,mult))tmp[i]=mul(mul(inv,fac[i]),mul(mul(ifac[n-i*s],fac[n]),mul(C(m,i),ksm(m-i,n-i*s))));
	reverse(tmp,tmp+up+1);
	for(ri i=0;i<=up;++i)b[i]=tmp[i];
	a=a*b;
	for(ri i=0;i<=up;++i)tmp[i]=a[i];
	reverse(tmp,tmp+up+1);
	int ans=0;
	for(ri i=0;i<=up;++i)ans=add(ans,mul(w[i],mul(ifac[i],tmp[i])));
	cout<<ans;
	return 0;
}

你可能感兴趣的:(#,题解)