T1 喜剧的迷人之处在于
将 \(a\) 分解质因数,容易找到满足 \(ab\) 为平方数的最小 \(b\),然后需要让 \(b\) 乘上一个平方数后在 \([L,R]\) 中,二分找即可。
T2 镜中的野兽
\[\begin{aligned} &f(x)=\sum\cdots\sum[\gcd=1,\text{lcm}=x]\\ &g(x)=\sum\cdots\sum[\gcd\mid x,\text{lcm}=x]\\ &h(x)=\sum\cdots\sum[\gcd\mid x,\text{lcm}\mid x] \end{aligned} \]外层枚举 \(gcd\) 后求 \(f(\frac{m}{gcd}-1)\) 即可,\(Q(x)\) 表示 \(x\) 的约数个数。
\[\begin{aligned} &h(x)=\operatorname{C}_{Q(x)}^{n}\\ &h(x)=\sum_{d\mid x}g(d)\\ &g(x)=\sum_{d\mid x}\mu(\frac{x}{d})h(d)\\ &g(x)=\sum_{d\mid x}f(d)\\ f(x)&=\sum_{d\mid x}\mu(\frac{x}{d})g(d)\\ &=\sum_{d\mid x}\mu(\frac{x}{d})\sum_{e\mid d}\mu(\frac{d}{e})h(e) \end{aligned} \]直接枚举三层约数求即可,题解说时间复杂度上界 \(O(d(m)(d(m)^2+\sqrt m))\),但是很松。
也可以把最外层的莫反换成容斥,设 \(W(x)\) 表示 \(\gcd\) 是 \(x\) 的倍数的方案数,\(W(i)=g(\frac{x}{i}),x=\prod_{i=1}^{cnt}p_i^{c_{i}},f(x)=g(x)-W(p_1)\cdots\cup W(p_{cnt})\),处理好 \(g(x)\) 之后,直接对后面的并集进行容斥即可。时间复杂度一样,但是不知道为什么跑的比上面慢很多。
#include<bits/stdc++.h>
#define int long long
typedef long long ll;
typedef unsigned long long ull;
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;}
const int N=1e5+10;
int n,m,mod,ans,jc[N],ny[N],f[N],u[N],p[N],tot;
std::vector<int> d[N];
bool vis[N];
inline int mo(int x){return x<0?(x%mod+mod)%mod:(x<mod?x:x%mod);}
inline int qpow(int a,int b){int res=1;for(;b;b>>=1,a=mo(a*a))if(b&1)res=mo(res*a);return res;}
inline int C(int x,int y){if(x<y)return 0;return jc[x]*ny[y]%mod*ny[x-y]%mod;}
inline void init(){
int mx=1e5;
for(int i=1;i<=mx;++i)jc[i]=mo(jc[i-1]*i);
ny[mx]=qpow(jc[mx],mod-2);for(int i=mx-1;i;--i)ny[i]=mo(ny[i+1]*(i+1));
}
inline int F(int x){
int res=0;
for(int i=1;i*i<=x;++i){
if(x%i==0){
res+=1+(i*i!=x);
}
}
return C(res,n);
}
inline int U(int x){
int res=0;
for(int i=2;i*i<=x;++i){
if(x%i==0){
res++;
x/=i;
if(x%i==0)return 0;
}
}
res+=(x!=1);
if(res&1)return -1;else return 1;
}
inline int G(int x){
int res=0;
for(int i=1;i*i<=x;++i){
if(x%i==0){
int j=x/i;
if(U(j))res=mo(res+U(j)*F(i));
if(i!=j&&U(i))res=mo(res+U(i)*F(j));
}
}
return res;
}
inline void work(int x){
if(!x)return ;
int res=0;
for(int i=1;i*i<=x;++i){
if(x%i==0){
int j=x/i;
if(U(j))res=mo(res+U(j)*G(i));
if(j!=i&&U(i))res=mo(res+U(i)*G(j));
}
}
ans=mo(ans+res);
}
signed main(){
// freopen("in.in","r",stdin);freopen("out.out","w",stdout);
std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
n=read(),m=read(),mod=read();jc[0]=ny[0]=1;
init();
for(int i=1;i*i<=m;++i){
if(m%i==0){
int j=m/i;
work(i-1);if(i!=j)work(j-1);
}
}
std::cout<<ans<<'\n';
}
T3 我愿相信由你所描述的童话
trick 题,设 \(pre_i\) 表示以位置 \(i\) 结尾从左往右构成上升序列的方案数,\(suc_i\) 表示以位置 \(i\) 结尾从右往左构成上升序列的方案数,两者的处理方法相同。
处理完之后,所有合法答案数量为 \(\sum_i^npre_i\cdot suc_i\),简单观察后发现一个合法序列中的 \(t\) 值是连续的一段相同的数,所以上面的答案算重,考虑容斥,钦定序列中的第一个 \(t\) 才有贡献,则上面的答案需要减去 \(\sum_i^npre_{fm_{a_i}}suc_i\),其中 \(fm_i\) 表示 \(i\) 之前出现过的位置,前缀和处理后整个过程的复杂度为 \(\mathcal{O}(n)\),所以现在只需要考虑处理 \(pre\) 和 \(suc\)。
首先有一个显然的 \(\mathcal{O}(2^mn)\) 做法,设 \(f_i\) 表示到当前位置,以 \(i\) 的子集结尾的构成上升序列的方案数,然后枚举并集来更新,这样的更新是 \(\mathcal{O}(2^m)\),查询只有 \(\mathcal{O}(1)\)。
考虑将更新和查询的复杂度平衡,设 \(f_{i,j}\) 表示到当前位置,以前半部分是 \(i\),后半部分为 \(j\) 的子集的数结尾的构成上升序列的方案数,此时的更新和查询都是 \(\mathcal{O}(2^{(\frac{m}{2})})\),整个算法复杂度为 \(\mathcal{O}(2^{\frac{m}{2}}n)\)。
#include<bits/stdc++.h>
#define int long long
typedef long long ll;
typedef unsigned long long ull;
inline int read(){char ch=getchar();int x=0,f=1;for(;ch<'0'||ch>'9';ch=getchar())if(ch=='-')f=-1;for(;ch>='0'&&ch<='9';ch=getchar())x=(x<<3)+(x<<1)+(ch^48);return x*f;}
const int N=1.2e6+10,mod=1e9+7;
int f[2050][2050],n,a[N],pre[N],suc[N],m,n1,n2,last[N],ans;
inline int mo(int x){return x<0?(x%mod+mod)%mod:(x<mod?x:x%mod);}
inline void insert(int x,int k){
int fi=x>>n1,se=x^(fi<<n1);
int zc=~se;zc=zc^(zc>>n1<<n1);
for(int s=zc;;s=(s-1)&zc){
f[fi][se|s]=mo(f[fi][se|s]+k);
if(!s)break;
}
}
inline int query(int x){
int fi=x>>n1,se=x^(fi<<n1),res=0;
for(int s=fi;;s=(s-1)&fi){
res=mo(res+f[s][se]);
if(!s)break;
}
return res;
}
signed main(){
// freopen("in.in","r",stdin);freopen("out.out","w",stdout);
std::ios::sync_with_stdio(false);std::cin.tie(0);std::cout.tie(0);
n=read();m=read();n1=(m-1)/2+1,n2=m/2;
for(int i=1;i<=n;++i)a[i]=read();
for(int i=1;i<=n;++i){
pre[i]=mo(1+query(a[i]));
insert(a[i],pre[i]);
}
memset(f,0,sizeof(f));std::reverse(a+1,a+n+1);
for(int i=1;i<=n;++i){
suc[i]=mo(1+query(a[i]));
insert(a[i],suc[i]);
}std::reverse(suc+1,suc+n+1);
std::reverse(a+1,a+n+1);
for(int i=1;i<=n;++i){
ans=mo(ans+pre[i]*suc[i]);
ans=mo(ans-last[a[i]]*suc[i]);
last[a[i]]=mo(last[a[i]]+pre[i]);
}
std::cout<<ans<<'\n';
}