题解 行列式
直接辗转相除
因为模数是质数高斯消元,可以打 tag 优化
听说 70pts 可以手动解出,等讲题了补上
正解:
考虑根据行列式意义计算,但发现主对角线上的这些 1 很烦人
先考虑暴力枚举哪些位置选在主对角线上
发现并不好直接钦定剩下的都不在主对角线上,所以加一层容斥
发现还需要考虑钦定在主对角线上的元素与剩下部分的矩阵形成的逆序对
但是又发现这些元素都在主对角线上
- 在一个排列的一个位置上插入这个位置对应的数值,逆序对数不变
举个例子:31
在位置 2 插入一个 2 变为321
,插入 2 前后逆序对数不变
证明考虑若插入的数是 ,则位置 左边 的数的个数一定等于 右边 的数的个数
那么现在可以容斥了:
这里在选定了 (所有贡献为 1 的元素)后,删去了与 中元素相关的行列,并将主对角线的权值均赋为了
然后 代表原矩阵删去与 中元素相关的行列,并将主对角线的权值均赋为 后得到的矩阵
然后优化这个东西,发现 的贡献均为 1,可以去掉
那么枚举 ,得到
那么怎么优化后面那个枚举呢?
展开一下:
或者也可以直接由类似多项式的方法得到最后这个结果:
让一个位置的权值为
所以最后就是
一个这样的矩阵行列式不为 0 的条件其中的元素是两两整除的
证明考虑若有一个元素是其它所有数的倍数,这一列只有它一个是 ,可以直接删去转化子问题
否则一定有两列是相同的,行列式为 0
那么问题转化为对于其中每个元素两两整除的集合 ,求
令 为 的这个东西,强制包含 1
我最开始试图使用最大的数一定是其它所有数的倍数这个性质转化子问题,然而不可做
其实可以枚举集合中最小值来转化子问题
因为强制包含 1 所以最终答案是
然后这个 直接按照定义求是 的,还可以更快
我们看这个递归过程怎么这么像杜教筛啊?
那么尝试预处理出前 个值来
但是这个东西怎么预处理呢?
如果你做过这个题,你会发现对于这样一个函数
它虽然不是积性函数,但假如我们构造一个函数 使得
那么有
于是这个东西是可以杜教筛的
写成杜教筛的形式是(令 为 的前缀和)
可以发现与上面的式子是类似的
那么现在的问题就只是快速预处理前 个 了
怎么快速求呢?
注意到对于质因子的幂次组成的可重集(即 )相同的 和 ,有
那么只需要对这个可重集做 hash,只对本质不同的求
这个东西的量级粗估为根号级别的,所以复杂度就可以接受了
对于本题,发现 还带个系数,所以对应的 是这样的:
于是就是直接杜教筛了
复杂度
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 20000010
#define ll long long
// #define int long long
char buf[1<<21], *p1=buf, *p2=buf;
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
inline ll read() {
ll ans=0, f=1; char c=getchar();
while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
return ans*f;
}
ll n, c;
const ll mod=998244353;
inline ll qpow(ll a, ll b) {ll ans=1; for (; b; a=a*a%mod,b>>=1) if (b&1) ans=ans*a%mod; return ans;}
namespace task1{
ll mp1[N], mp2[N], val, sqr;
// unordered_map<int, ll> mp;
ll f(int n) {
// if (mp.find(n)!=mp.end()) return mp[n];
if (n<=sqr) {if (mp1[n]) return mp1[n];}
else {if (mp2[::n/n]) return mp2[::n/n];}
ll ans=0;
for (int l=2,r; l<=n; l=r+1) {
r=n/(n/l);
ans=(ans+(r-l+1)*f(n/l))%mod;
}
ans=(1+val*ans)%mod;
if (n<=sqr) mp1[n]=ans;
else mp2[::n/n]=ans;
return ans;
}
void solve() {
sqr=sqrt(n);
val=1ll*c*qpow(1-c, mod-2)%mod;
printf("%lld\n", (qpow(1-c, n)*((f(n)+f(n)*val)%mod)%mod+mod)%mod);
}
}
namespace task2{
bool npri[N];
const ll base=13131;
unordered_map<ll, ll> mp;
int pri[N], f[N], lowc[N], pcnt;
ll mp1[N], mp2[N], pw[N], h[N], val, sqr;
ll F(ll n) {
if (n<N) return f[n];
if (n<=sqr) {if (mp1[n]) return mp1[n];}
else {if (mp2[::n/n]) return mp2[::n/n];}
ll ans=0;
for (ll l=2,r; l<=n; l=r+1) {
r=n/(n/l);
ans=(ans+(r-l+1)%mod*F(n/l))%mod;
}
ans=(1+val*ans)%mod;
if (n<=sqr) mp1[n]=ans;
else mp2[::n/n]=ans;
return ans;
}
ll force_f(ll n) {
ll i, ans=f[1];
for (i=2; i*i<n; ++i) if (!(n%i)) ans=(ans+f[i]+f[n/i])%mod;
if (i*i==n) ans=(ans+f[i])%mod;
return ans*val%mod;
}
void solve() {
sqr=sqrt(n);
val=1ll*c*qpow(1-c, mod-2)%mod;
f[1]=pw[0]=1;
for (int i=1; i<=50; ++i) pw[i]=pw[i-1]*base%mod;
for (int i=2; i<N; ++i) {
if (!npri[i]) pri[++pcnt]=i, h[i]=pw[lowc[i]=1], f[i]=val;
for (int j=1,x; j<=pcnt&&1ll*i*pri[j]<N; ++j) {
npri[x=i*pri[j]]=1;
if (!(i%pri[j])) {
lowc[x]=lowc[i]+1;
h[x]=((h[i]-pw[lowc[i]]+pw[lowc[x]])%mod+mod)%mod;
break;
}
else lowc[x]=1, h[x]=(h[i]+pw[1])%mod;
}
}
for (int i=2; i<N; ++i)
if (mp.find(h[i])!=mp.end()) f[i]=mp[h[i]];
else f[i]=mp[h[i]]=force_f(i);
for (int i=2; i<N; ++i) f[i]=(f[i]+f[i-1])%mod;
printf("%lld\n", (qpow(1-c, n)*((F(n)+F(n)*val)%mod)%mod+mod)%mod);
}
}
signed main()
{
freopen("b.in", "r", stdin);
freopen("b.out", "w", stdout);
n=read(); c=read();
if (c==1 && n<=2) puts("1");
// else task1::solve();
else task2::solve();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通