2022-6-22 #3 CF698F & CF1548E
连更两天了!
被🐕调教成面对模拟赛只会摆烂的菜逼了。🥰
005 CF698F Coprime Permutation
首先,对于两个质因子集合相同的数字,他们的位置显然可以任意交换。
除此之外,对于两个素数 \(p_1,p_2\),若他们的倍数个数相同,可以交换他们所有的倍数。(容易发现两个数一定大于根号,他们的乘积一定超过了 \(n\))
然后我们就对于每个存在的数字对应的最大质因子,查看它们是否在一个类型,对应的交换情况是否重复,如果没有重复,就更新一下这一类型还有多少个自由的素数就好了。
同样对于每个质因子集合维护一下还有多少个自由的数字,朴素实现是 \(O(n\omega(n))\) 的。
通过线性筛可以直接筛出每个数的质因子集合以及最大质因子对应的幂次,这样就是 \(O(n)\) 了。
#include<stdio.h>
char buf[1<<21],*p1=buf,*p2=buf,obuf[1<<21],*O=obuf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
const int maxn=1000005,mod=1000000007;
int n,ps,ans;
int c[maxn],p[maxn],tot1[maxn],tot2[maxn],fac[maxn],mx[maxn],mul[maxn],S[maxn],div[maxn],pto[maxn],vto[maxn];
inline int max(int a,int b){
return a>b? a:b;
}
void sieve(int n){
c[1]=fac[0]=fac[1]=mul[1]=mx[1]=S[1]=div[1]=tot1[1]=tot2[1]=1;
for(int i=2;i<=n;i++){
if(c[i]==0)
p[++ps]=i,mx[i]=mul[i]=S[i]=i;
for(int j=1;j<=ps&&i*p[j]<=n;j++){
int k=i*p[j];
c[k]=1,mx[k]=max(mx[i],p[j]),mul[k]=(mx[k]==mx[i]? mul[i]:1)*(mx[k]==p[j]? p[j]:1);
if(i%p[j]==0){
S[k]=S[i];
break;
}
S[k]=S[i]*p[j];
}
fac[i]=1ll*fac[i-1]*i%mod,tot1[S[i]]++;
}
}
void read(int &x){
x=0;
char ch=getchar();
while(ch<'0'||ch>'9')
ch=getchar();
while(ch>='0'&&ch<='9')
x=x*10+(ch^48),ch=getchar();
}
int main(){
read(n),sieve(n);
for(int i=1;i<=ps;i++)
div[p[i]]=n/p[i],tot2[div[p[i]]]++;
for(int i=1,x;i<=n;i++){
read(x);
if(x==0)
continue;
int a=mx[x],b=mx[i];
if(S[x/mul[x]]!=S[i/mul[i]]||div[a]!=div[b]||(vto[a]!=0&&vto[a]!=b)||(pto[b]!=0&&pto[b]!=a)){
puts("0");
return 0;
}
if(vto[a]==0&&pto[b]==0)
tot2[div[a]]--;
vto[a]=b,pto[b]=a,tot1[S[x]]--;
}
ans=1;
for(int i=1;i<=n;i++)
ans=1ll*ans*fac[tot1[i]]%mod*fac[tot2[i]]%mod;
printf("%d\n",ans);
return 0;
}
006 CF1548E Gregor and the Two Painters
数连通块问题肯定要变成数关键点,对于一个连通块,我们令其关键点为权值最小的格子,如果有多个,我们优先选择行号最小,其次选择列号最小的。
令 \(px_i,sx_i\) 表示 \(i\) 前面/后面第一个比 \(a_i\) 小(事实上,为了保证关键点行号最小,\(px_i\) 要表示小于等于)的数所在位置,\(py,sy\) 同理,于是我们知道代表元 \((x,y)\) 无法到达 \((px_i,y),(sx_i,y),(x,py_i),(x,sy_i)\)。
由于 \(x\) 是 \((px_i,sx_i)\) 区间中的区间最小值,所以到达 \((px_i,y)\) 的策略一定是从 \(x\) 向上/下走,然后横着走,然后向下/上走,于是可以直接列出限制:
- \(a_x+b_y\leqslant K\);
- \(a_x+\min\{\max_{py_y\leqslant i\leqslant y} b_i,\min_{y\leqslant i\leqslant sy_y} b_i\}>K\)
- \(\min\{\max_{px_x\leqslant i\leqslant x} a_i,\min_{x\leqslant i\leqslant sx_x} a_i\}+b_y>K\)
这明显是一个二维偏序,复杂度 \(O(n\log n)\)。
#include<stdio.h>
#include<algorithm>
#define lowbit(x) (x&-x)
using namespace std;
const int maxn=200005;
int n,m,k;
int a[maxn],b[maxn],aid[maxn],bid[maxn],t[maxn],va[maxn],vb[maxn],stk[maxn],rec[maxn];
long long ans;
inline int cmpa(int x,int y){
return a[x]<a[y];
}
inline int cmpb(int x,int y){
return vb[x]>vb[y];
}
void update(int x,int v){
for(int i=x;i<=200000;i+=lowbit(i))
t[i]+=v;
}
int query(int x){
int res=0;
for(int i=x;i;i-=lowbit(i))
res+=t[i];
return res;
}
void solve(int n,int *a,int *va){
for(int i=1;i<=n;i++)
va[i]=1e9;
int top=0;
for(int i=1;i<=n;i++){
int mx=a[i];
while(top&&a[stk[top]]>a[i])
mx=max(mx,rec[top]),top--;
if(top>0)
va[i]=min(va[i],mx);
stk[++top]=i,rec[top]=mx;
}
top=0;
for(int i=n;i>=1;i--){
int mx=a[i];
while(top&&a[stk[top]]>=a[i])
mx=max(mx,rec[top]),top--;
if(top>0)
va[i]=min(va[i],mx);
stk[++top]=i,rec[top]=mx;
}
}
int main(){
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),aid[i]=i;
for(int i=1;i<=m;i++)
scanf("%d",&b[i]),bid[i]=i;
solve(n,a,va),solve(m,b,vb),sort(aid+1,aid+1+n,cmpa),sort(bid+1,bid+1+m,cmpb);
for(int i=1,j=1;i<=n;i++){
int x=aid[i];
while(j<=m&&a[x]+vb[bid[j]]>k)
update(b[bid[j]],1),j++;
int L=max(1,k-va[x]+1),R=min(200000,k-a[x]);
if(L<=R)
ans+=query(R)-query(L-1);
}
printf("%lld\n",ans);
return 0;
}

浙公网安备 33010602011771号