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
废话
首先这题一眼能看出两个小贪心:
- 起点肯定选在某一堆物品上(这不是废话吗。)
- 搬东西时优先搬离自己近的
然后我就想到:枚举每一堆物品作为起点,然后二分向左右扩展的距离(即扩展到的物品都搬过来),然后更新答案。
怎么求一个范围内的所有物品到某个点的距离之和呢?
我们先看两个物品的距离计算方法(假设 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(x−1)!2f[x]=i=1∑x−2 j=1∑x−1−iCx−1iCx−1−ij×f[i]f[j][x−1−i−j∈A]i=1∑x−2 j=1∑x−1−ii!j!(x−1−i−j)!(x−1)!f[i]f[j][x−1−i−j∈A]i=1∑x−2 j=1∑x−1−i(x−1−i−j)!1i!f[i]j!f[j][x−1−i−j∈A]
乘 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=1k−1i!f[i](k−i)!f[k−i],代入上式得:
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] (x−1)!2f[x]=k=2∑x−1x−1−k[x−1−k∈A]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 r−l 的 f f f 来更新 m i d + 1 mid+1 mid+1 ~ r r r 的 g g g, 0 0 0 ~ r − l r-l r−l 中大于 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(a∈0 ~ l − 1 , b ∈ l l-1,b\in l l−1,b∈l ~ 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]);
}