UOJ Round #3 题解

A

不难发现, sgcd ( a , b ) = gcd ⁡ ( a , b ) / mindiv ( gcd ⁡ ( a , b ) ) \text{sgcd}(a,b)=\gcd(a,b)/\text{mindiv}(\gcd(a,b)) sgcd(a,b)=gcd(a,b)/mindiv(gcd(a,b)),其中 mindiv ( x ) \text{mindiv}(x) mindiv(x) 表示 x x x 最小的因子。

先将 a 1 a_1 a1 质因数分解,每次就可以 O ( a 1 的 质 因 子 个 数 ) O(a_1的质因子个数) O(a1) 找到这个 mindiv \text{mindiv} mindiv 了,时间复杂度大概是 O ( n log ⁡ 1 0 12 ) O(n\log 10^{12}) O(nlog1012)

代码如下:

#include <cstdio>
#include <algorithm>
using namespace std;
#define ll long long
#define maxn 100010

int n;
ll a[maxn],b[maxn],t=0;
ll gcd(ll x,ll y){
    
    return y==0?x:gcd(y,x%y);}
ll get(ll x)
{
    
    
	for(int i=1;i<=t;i++)if(x%b[i]==0)return b[i];
	return -1;
}

int main()
{
    
    
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
	ll p=a[1];
	for(int i=2;(ll)i*i<=p;i++)
	if(p%i==0){
    
    b[++t]=i;while(p%i==0)p/=i;}
	if(p!=1)b[++t]=p;
	for(int i=1;i<=n;i++)
	{
    
    
		p=get(a[i]);
		if(p!=-1)printf("%lld ",gcd(a[1],a[i])/p);
		else printf("-1 ");
	}
}

B

废话

首先这题一眼能看出两个小贪心:

  1. 起点肯定选在某一堆物品上(这不是废话吗。)
  2. 搬东西时优先搬离自己近的

然后我就想到:枚举每一堆物品作为起点,然后二分向左右扩展的距离(即扩展到的物品都搬过来),然后更新答案。

怎么求一个范围内的所有物品到某个点的距离之和呢?

我们先看两个物品的距离计算方法(假设 a < b a<b a<b): a a a 物品到 b b b 物品的距离为 x [ b ] − x [ a ] x[b]-x[a] x[b]x[a]

那么对于在这个点左边的物品,可以用前缀和统计出物品数量然后乘以 x [ b ] x[b] x[b],再减去这些物品的 a [ i ] ∗ b [ i ] a[i]*b[i] a[i]b[i]之和即可,这个也可以用前缀和维护出来。

这个做法包含了一个二分,二分里面要求一个区间到一个点的距离和,那么时间复杂度就是 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n),嗯,nice,然后我滑了一下鼠标滚轮,看到数据规模后,又抬起了放在 A l t Alt Alt T a b Tab Tab 键上的手指。

正解

这种二分不行,不妨考虑一下二分答案。我们每次二分出一个 m i d mid mid 后,从头开始,维护一个区间,这个区间内包含的物品数 ≥ m i d \geq mid mid,然后看看这些物品与我们枚举到的起点的距离之和是否小于等于 t 2 \dfrac t 2 2t,当我们的起点向右枚举过去的时候,区间也要跟着向右走。具体怎么走就看代码吧。

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define ll long long
#define maxn 500010

int n;ll t;
ll x[maxn],a[maxn],took[maxn];//took[i]记录第i堆物品被拿走了多少个
ll sum1[maxn],sum2[maxn];
ll getsum(int pos,int l,int r)
{
    
    
	ll tot(0);
	tot+=(ll)took[l]*(x[pos]-x[l])+(ll)took[r]*(x[r]-x[pos]);
	//左右端点可能没拿完,所以拿出来特殊考虑一下
	tot+=(sum1[pos]-sum1[l])*x[pos]-(sum2[pos]-sum2[l]);//起点左边的部分
	if(r>pos)tot+=(sum2[r-1]-sum2[pos])-(sum1[r-1]-sum1[pos])*x[pos];//起点右边的部分
	return tot<<1;
}
bool check(ll mid)
{
    
    
	memset(took,0,sizeof(took));
	int l=1,r=0;
	while(mid>0)//从点1开始,先维护出一开始的区间
	{
    
    
		r++;
		took[r]=min(a[r],mid);
		mid-=took[r];
	}
	if(getsum(1,l,r)<=t)return true;
	for(int i=2;i<=n;i++)
	{
    
    
		while(x[i]-x[l]>x[r]-x[i])
		//假如区间的左端点到起点的距离比右端点到起点的距离大
		//那么就将左端点右移,其中被吃掉的物品在在右端点右移时补回来
		{
    
    
			if(took[r]==a[r]){
    
    if(r<n){
    
    r++;continue;}break;}//假如拿完了,就要右移
			mid=min(a[r]-took[r],took[l]);//左端点右移,少的部分给右端点
			took[l]-=mid;took[r]+=mid;
			if(!took[l])l++;
		}
		if(getsum(i,l,r)<=t)return true;
	}
	return false;
}

int main()
{
    
    
	scanf("%d %lld",&n,&t);
	for(int i=1;i<=n;i++)scanf("%lld",&x[i]);
	for(int i=1;i<=n;i++)scanf("%lld",&a[i]),
	sum1[i]=sum1[i-1]+a[i],sum2[i]=sum2[i-1]+a[i]*x[i];
	ll l=0,r=sum1[n],mid,ans;
	while(l<=r)
	{
    
    
		mid=l+r>>1;
		if(check(mid))ans=mid,l=mid+1;
		else r=mid-1;
	}
	printf("%lld",ans);
}

C

f [ x ] f[x] f[x] 表示有 x x x 个点的树的形态数,因为链式反应后没有剩余的囧-2333原子,所以根节点肯定是白点,那么可以枚举根节点下面的两个白点的子树大小,得到一个 d p dp dp 方程:
f [ x ] = 1 2 ∑ i = 1 x − 2   ∑ j = 1 x − 1 − i C x − 1 i C x − 1 − i j × f [ i ] f [ j ] [ x − 1 − i − j ∈ A ] f [ x ] = 1 2 ∑ i = 1 x − 2   ∑ j = 1 x − 1 − i ( x − 1 ) ! i ! j ! ( x − 1 − i − j ) ! f [ i ] f [ j ] [ x − 1 − i − j ∈ A ] 2 f [ x ] ( x − 1 ) ! = ∑ i = 1 x − 2   ∑ j = 1 x − 1 − i 1 ( x − 1 − i − j ) ! f [ i ] i ! f [ j ] j ! [ x − 1 − i − j ∈ A ] \begin{aligned} f[x]=\frac 1 2&\sum_{i=1}^{x-2}~\sum_{j=1}^{x-1-i} C_{x-1}^i C_{x-1-i}^j \times f[i]f[j][x-1-i-j\in A]\\ f[x]=\frac 1 2&\sum_{i=1}^{x-2}~\sum_{j=1}^{x-1-i} \frac {(x-1)!} {i!j!(x-1-i-j)!} f[i]f[j][x-1-i-j\in A]\\ \frac {2f[x]} {(x-1)!}=&\sum_{i=1}^{x-2}~\sum_{j=1}^{x-1-i} \frac 1 {(x-1-i-j)!} \frac {f[i]} {i!}\frac {f[j]} {j!}[x-1-i-j\in A]\\ \end{aligned} f[x]=21f[x]=21(x1)!2f[x]=i=1x2 j=1x1iCx1iCx1ij×f[i]f[j][x1ijA]i=1x2 j=1x1ii!j!(x1ij)!(x1)!f[i]f[j][x1ijA]i=1x2 j=1x1i(x1ij)!1i!f[i]j!f[j][x1ijA]

1 2 \frac 1 2 21 是因为每对白点 ( i , j ) (i,j) (i,j) 都会以 ( j , i ) (j,i) (j,i) 的形式重复计算,所以要去掉。

g [ k ] = ∑ i = 1 k − 1 f [ i ] i ! f [ k − i ] ( k − i ) ! g[k]=\sum_{i=1}^{k-1} \frac {f[i]} {i!} \frac {f[k-i]} {(k-i)!} g[k]=i=1k1i!f[i](ki)!f[ki],代入上式得:
2 f [ x ] ( x − 1 ) ! = ∑ k = 2 x − 1 [ x − 1 − k ∈ A ] x − 1 − k g [ k ] \frac {2f[x]} {(x-1)!}=\sum_{k=2}^{x-1} \frac {[x-1-k\in A]} {x-1-k} g[k] (x1)!2f[x]=k=2x1x1k[x1kA]g[k]

然后就可以大力分治 F F T FFT FFT 了!

但是要注意,分治 F F T FFT FFT 时,我们用 l l l ~ m i d mid mid f f f 0 0 0 ~ r − l r-l rl f f f 来更新 m i d + 1 mid+1 mid+1 ~ r r r g g g 0 0 0 ~ r − l r-l rl 中大于 m i d mid mid 的部分我们还没有求出来,不能用来更新 g g g,所以不能加到做 F F T FFT FFT 的数组里。

以及,里面小于 l l l 的部分,与 l l l ~ m i d mid mid f f f F F T FFT FFT 时, a × b ( a ∈ 0 a\times b(a\in0 a×b(a0 ~ l − 1 , b ∈ l l-1,b\in l l1,bl ~ m i d ) mid) mid) 只会被计算一次,即 b × a b\times a b×a 没有计算到,所以里面小于 l l l 的部分要乘以 2 2 2 再放到做 F F T FFT FFT 的数组里。这个乘 2 2 2 其实就是补上面没算到的部分。

代码如下:

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define maxn 600010
#define mod 998244353
#define bin(x) (1<<(x))
#define MS(f,x) memset(f,0,4<<(x))

int n;
char s[maxn];int v[maxn];
int ksm(int x,int y){
    
    int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;}
int inv[maxn],w[maxn];void prep(int lg){
    
    int N=bin(lg);
	inv[1]=1;for(int i=2;i<=N;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for(int i=1,wn;i<N;i<<=1){
    
    
		w[i]=1;wn=ksm(3,(mod-1)/(i<<1));
		for(int j=1;j<i;j++)w[i+j]=1ll*w[i+j-1]*wn%mod;
	}
}
int limit,r[maxn];
void InitR(int lg){
    
    for(int i=1;i<bin(lg);i++)r[i]=(r[i>>1]>>1)|((i&1)<<(lg-1));}
int add(int x){
    
    return x>=mod?x-mod:x;}
int dec(int x){
    
    return x<0?x+mod:x;}
void ntt(int *f,int lg,int type=0){
    
    
	limit=bin(lg);if(type)reverse(f+1,f+limit);
	for(int i=1;i<limit;i++)if(i<r[i])swap(f[i],f[r[i]]);
	for(int mid=1,t;mid<limit;mid<<=1)for(int j=0;j<limit;j+=(mid<<1))for(int i=0;i<mid;i++)
	{
    
    t=1ll*f[j+i+mid]*w[mid+i]%mod;f[j+i+mid]=dec(f[j+i]-t);f[j+i]=add(f[j+i]+t);}
	if(type)for(int i=0;i<limit;i++)f[i]=1ll*f[i]*inv[limit]%mod;
}
void NTT(int *f,int *g,int ln){
    
    
	int lg=ceil(log2(ln));InitR(lg);ntt(f,lg);ntt(g,lg);
	for(int i=0;i<bin(lg);i++)f[i]=1ll*f[i]*g[i]%mod;ntt(f,lg,1);
}
int fac[maxn],inv_fac[maxn];
void FacInit(){
    
    
	fac[0]=inv_fac[0]=1;
	for(int i=1;i<=n;i++){
    
    
		fac[i]=1ll*fac[i-1]*i%mod;
		inv_fac[i]=1ll*inv_fac[i-1]*inv[i]%mod;
	}
}
int F[maxn],G[maxn],A[maxn],B[maxn];
void divide(int l,int r){
    
    
	if(l==r){
    
    
		if(l==1)F[l]=1;
		else F[l]=1ll*F[l]*inv[2]%mod*fac[l-1]%mod;
		return;
	}
	int mid=l+r>>1;divide(l,mid);
	
	int lg=ceil(log2(r-l+2));MS(A,lg);MS(B,lg);
	for(int i=l;i<=mid;i++)A[i-l]=G[i];
	for(int i=0;i<=r-l;i++)B[i]=v[i];
	NTT(A,B,r-l+2);for(int i=mid+1;i<=r;i++)F[i]=add(F[i]+A[i-l-1]);
	
	MS(A,lg);MS(B,lg);
	for(int i=l;i<=mid;i++)A[i-l]=1ll*F[i]*inv_fac[i]%mod;
	for(int i=0;i<=r-l;i++)B[i]=(i<l?2ll:i<=mid?1ll:0)*F[i]%mod*inv_fac[i]%mod;
	NTT(A,B,r-l+2);for(int i=mid+1;i<=r;i++)G[i]=add(G[i]+A[i-l]);
	
	divide(mid+1,r);
}

int main()
{
    
    
	scanf("%d %s",&n,s);
	prep(ceil(log2((n+1)<<1)));FacInit();
	for(int i=0;i<n;i++)v[i]=(s[i]-'0')*inv_fac[i];
	divide(1,n);
	for(int i=1;i<=n;i++)printf("%d\n",F[i]);
}

猜你喜欢

转载自blog.csdn.net/a_forever_dream/article/details/109591992
今日推荐