2022-7-10 #10 CF1644F & CF643F & CF1588F
第十篇了呢!
感冒了,今天就做一些简单的题目吧。😷🤒
028 CF1644F Basis
之前 edu 没做出来的题。
观察可知,我们选出来的基一定是去掉最后一段之后所有段的 \(\gcd=1\) 的串,我们直接对其计数:
枚举最大的 \(\gcd\) 进行容斥,容易发现容斥系数为 \(\mu(k)\):(注意为避免对空集求 \(\gcd\),我们需要忽略只有一段的情况)
而求第二类斯特林数前缀和单项可以线性,具体地,我们列出卷积的式子:
枚举第一维对第二维做前缀和即可,注意 \(i^n\) 需要线性筛。
复杂度 \(O(n\log n)\)。
#include<stdio.h>
const int maxn=200005,mod=998244353;
int n,m,ans,ps;
int c[maxn],p[maxn],miu[maxn],mul[maxn],rec[maxn],A[maxn],B[maxn],nfac[maxn],inv[maxn];
void sieve(int n){
c[1]=miu[1]=inv[1]=nfac[0]=nfac[1]=1;
for(int i=2;i<=n;i++){
if(c[i]==0)
p[++ps]=i,miu[i]=-1;
rec[i]=ps,inv[i]=mod-1ll*(mod/i)*inv[mod%i]%mod,nfac[i]=1ll*nfac[i-1]*inv[i]%mod;
for(int j=1;j<=ps&&i*p[j]<=n;j++){
c[i*p[j]]=1;
if(i%p[j]==0)
break;
miu[i*p[j]]=-miu[i];
}
}
}
int ksm(int a,int b){
int res=1;
while(b){
if(b&1)
res=1ll*res*a%mod;
a=1ll*a*a%mod,b>>=1;
}
return res;
}
int calc(int n,int k){
if(k>n)
k=n;
mul[1]=1;
for(int i=2;i<=k;i++){
if(c[i]==0)
mul[i]=ksm(i,n);
for(int j=1;j<=rec[i]&&i*p[j]<=n;j++){
mul[i*p[j]]=1ll*mul[i]*mul[p[j]]%mod;
if(i%p[j]==0)
break;
}
}
int res=0;
for(int i=1;i<=k;i++)
B[i]=(B[i-1]+1ll*mul[i]*nfac[i]%mod)%mod;
for(int i=0;i<=k;i++)
res=(res+1ll*A[i]*B[k-i])%mod;
return res;
}
int main(){
scanf("%d%d",&n,&m);
if(n==1||m==1){
puts("1");
return 0;
}
sieve(n);
for(int i=0;i<=n;i++)
A[i]=1ll*((i&1)? mod-1:1)*nfac[i]%mod;
for(int i=1;i<=n;i++)
if(miu[i])
ans=(ans+1ll*(miu[i]+mod)*(calc((n+i-1)/i,m)-1+mod))%mod;
printf("%d\n",ans);
return 0;
}
029 CF643F Bears and Juice
比较神奇的题。
我们考虑熊能表示出的状态数是多少:
表示枚举有几只熊睡觉,如果睡了是在哪一天睡的。
可以构造策略使得其恰好能辨认出第 \(1,2,\cdots,\sum_{i=0}^{\min(p,n-1)}{n\choose i}t^i\) 这些酒桶。
假设是第 \(k\) 个桶是酒,我们只需让哪些醉了的熊恰好在它们对应的天喝这桶,其他的不喝即可。
于是我们要对于 \(t\in[1,q]\) 快速计算上式,我们可以 \(O(pq)\) 枚举,对于所有 \(i\leqslant\min(p,n-1)\) 预处理 \(n\choose i\) 即可 \(O(1)\) 计算。
复杂度 \(O(pq)\)。
#include<stdio.h>
const int maxn=205;
int n,p,q;
int v[maxn];
unsigned int ans;
unsigned int C[maxn];
int gcd(int a,int b){
return b==0? a:gcd(b,a%b);
}
int main(){
scanf("%d%d%d",&n,&p,&q);
for(int i=0;i<=p&&i<=n-1;i++){
for(int j=1;j<=i;j++)
v[j]=n-j+1;
for(int j=1;j<=i;j++){
int w=j;
for(int k=1;k<=i;k++){
int g=gcd(w,v[k]);
w/=g,v[k]/=g;
}
}
C[i]=1;
for(int j=1;j<=i;j++)
C[i]*=v[j];
}
for(int i=1;i<=q;i++){
unsigned int res=0,mul=1;
for(int j=0;j<=p&&j<=n-1;j++,mul*=i)
res+=C[j]*mul;
ans^=i*res;
}
printf("%u\n",ans);
return 0;
}
030 CF1588F Jumping Through the Array
2e5 8s,考虑操作分块。
一开始以为是基环树。。。
先考虑没有 3 操作的情况,询问的时候枚举这个块内的修改,可以 \(O(1)\) 计算这个环内的区间内结点数量,走完一个操作块就重新计算一下权值。
有 3 操作时,我们不方便直接维护每个环,也不方便重新计算权值,我们考虑能不能暴力处理这件事。
实际上是可以的,我们每次提取出操作影响到的点作为关键点,把没有修改过的点挂在关键点上面,我们维护这样一个 \(O(\sqrt n)\) 的图暴力修改查询就好了。注意我们要对于每个环维护一个二维前缀和状物,发现两维都可以离散化到 \(O(\sqrt n)\),暴力处理即可。
复杂度 \(O((n+q)\sqrt q)\)。
头痛,懒得写严格根号了。。。
#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn=200005;
int n,q,ps;
int p[maxn],qo[maxn],qx[maxn],qy[maxn],bel[maxn],pos[maxn];
long long a[maxn],sum[maxn],lazy[maxn];
vector<int>v[maxn];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)
scanf("%d",&p[i]),bel[i]=-1;
scanf("%d",&q);
for(int L=1,R;L<=q;L=R+1){
R=min(R+500,q),ps=0;
for(int i=L;i<=R;i++){
scanf("%d%d%d",&qo[i],&qx[i],&qy[i]);
if(qo[i]==2)
bel[qx[i]]=qx[i];
if(qo[i]==3)
bel[qx[i]]=qx[i],bel[qy[i]]=qy[i];
}
for(int i=1;i<=n;i++)
if(bel[i]==-1){
int j,k,f=1;
for(j=p[i];j!=i&&bel[j]==-1;j=p[j]);
for(k=i;(k!=i&&bel[k]==-1)||f;k=p[k])
bel[k]=j==i? 0:bel[j],f=0;
}
for(int i=1;i<=n;i++){
sum[i]=sum[i-1]+a[i];
if(bel[i]==i)
pos[++ps]=i;
if(bel[i]>0)
v[bel[i]].push_back(i);
}
for(int i=L;i<=R;i++){
int o=qo[i],x=qx[i],y=qy[i];
if(o==1){
long long res=sum[y]-sum[x-1];
for(int j=1;j<=ps;j++)
res+=1ll*(upper_bound(v[pos[j]].begin(),v[pos[j]].end(),y)-lower_bound(v[pos[j]].begin(),v[pos[j]].end(),x))*lazy[pos[j]];
printf("%lld\n",res);
}
if(o==2){
int flg=1;
for(int i=x;i!=x||flg;i=bel[p[i]])
lazy[i]+=y,flg=0;
}
if(o==3)
swap(p[x],p[y]);
}
for(int i=1;i<=n;i++)
if(bel[i]>0)
a[i]+=lazy[bel[i]];
for(int i=1;i<=n;i++)
bel[i]=-1,lazy[i]=0,v[i].clear();
}
return 0;
}