来自学长的馈赠4 社论
A. 活动投票
主元素问题,一个经典做法是摩尔投票法(Boyer–Moore majority vote algorithm).
大概就是维护一个计数器 和目前答案 .
每次考虑加入一个数 :
- 如果 ,则直接 , .
- 若不然,如果 ,则 ,否则 .
正确性显然 只可意会,不可言传
其他做法不想说了 .
Code
using namespace std;
typedef long long ll;
typedef double db;
typedef pair<int, int> pii;
int ans, x, n;
int main()
{
long long x = ans;
scanf("%d", &n);
int cc = 0, ans = -1;
for (int i=1; i<=n; i++)
{
scanf("%d", &x);
if (ans == -1){++cc; ans = x;}
else if (x != ans)
{
if (cc) --cc;
else{++cc; ans = x;}
}
else ++cc;
}
printf("%d\n", ans);
return 0;
}
关于摩尔:摩尔,即 mol, 是精确包含 个原子或分子等基本单元的系统的物质的量 .
B. 大佬
首先根据期望线性性,答案就是 的答案乘 .
于是问题就变成一个序列 ,每个元素在 均匀随机,问最大值期望 .
这个可以简单容斥,详见 SoyTony .
这个太 simple 了,我们如何让它 exciting 一点呢?
考虑维护序列 , 表示最大值为 的概率,于是答案就是 .
定义魔怔运算为:
令 序列是全为 的序列 .
考虑在原序列末尾追加一个数产生的贡献,根据简单容斥我们可以发现 (怎么还要简单容斥
显而易见 具有结合律,直接快速幂即可 .
感谢 zcyzcy 提出这个 exciting 的算法 .
魔怔运算可以前缀和做到线性。
时间复杂度 ,简单容斥的复杂度也是一样的 .
Code
using namespace std;
const int P = 1e9+7;
typedef vector<int> vi;
typedef long long ll;
typedef double db;
typedef pair<int, int> pii;
int n, m, k;
inline int qpow(int a, int n)
{
int ans = 1;
while (n)
{
if (n & 1) ans = 1ll * ans * a % P;
a = 1ll * a * a % P; n >>= 1;
} return ans;
}
inline int inv(int x){return qpow(x, P-2);}
inline vi conv(vi a, vi b)
{
int n = a.size(); assert(a.size() == b.size());
for (int i=1; i<n; i++) (a[i] += a[i-1]) %= P;
for (int i=1; i<n; i++) (b[i] += b[i-1]) %= P;
vi ans(n);
ans[0] = 1ll * a[0] * b[0] % P;
for (int i=1; i<n; i++) ans[i] = ((1ll * a[i] * (b[i] - b[i-1]) % P + 1ll * b[i] * (a[i] - a[i-1]) % P - 1ll * (a[i] - a[i-1]) * (b[i] - b[i-1]) % P) % P + P) % P;
return ans;
}
inline vi qpow(vi a, int n)
{
vi ans(a.size()); ans[0] = 1;
while (n)
{
if (n & 1) ans = conv(ans, a);
a = conv(a, a); n >>= 1;
} return ans;
}
int main()
{
scanf("%d%d%d", &n, &m, &k); int ii = inv(m);
vi a; a.resize(m); vi b(m, ii);
for(int i=0; i<m; i++) scanf("%d", &a[i]);
b = qpow(b, k);
int ans = 0;
for (int i=0; i<m; i++) (ans += 1ll * a[i] * b[i] % P) %= P;
printf("%lld\n", 1ll * ans * (n-k+1) % P);
return 0;
}
但是数据范围那么小为什么要 啊 .
听说有 DP 做法 /yun
C. Dp搬运工3
往空位里插排列 . 令 表示做到 ,有 个配对, 的答案 .
于是讨论一下就可以转移了, .
有一个做法是钦定 为标准排列然后算 ,没咋看 .
Code
using namespace std;
const int N = 111, P = 998244353;
typedef vector<int> vi;
typedef long long ll;
typedef double db;
typedef pair<int, int> pii;
int n, kkk, dp[N][N][N*N];
int main()
{
scanf("%d%d", &n, &kkk); dp[0][0][0] = 1;
for (int i=0; i<n; i++)
for (int j=max(0, 2*i-n); j<=i; j++)
for (int k=0; k<=n*n; k++)
{
int p = (i - j) << 1, q = n - j - p;
(dp[i+1][j][k] += 1ll * dp[i][j][k] * q % P * (q-1) % P) %= P;
(dp[i+1][j+1][k+i+1] += 1ll * dp[i][j][k] * q % P) %= P;
(dp[i+1][j+1][k+i+1] += 1ll * dp[i][j][k] * p % P * q % P) %= P;
(dp[i+1][j+2][k+2*(i+1)] += 1ll * dp[i][j][k] * (p>>1) % P * (p>>1) % P) %= P;
}
int ans = 0;
for (int i=kkk; i<=n*n; i++) (ans += dp[n][n][i]) %= P;
printf("%d\n", ans);
return 0;
}
D. Beautiful
题目链接 .
NOIP 模拟赛出 *2900 正常吗?
试图魔改 Cantor 展开计算带限制排列字典序,失败
下面复读一波题解 .
约定:错排数为 .
广义错排数 表示 的排列有 个限制(形如第 位不能为 )的方案数 .
边界:
- : .
- : .
于是只剩下 情况,钦定 不被限制,于是考虑第 位放的是被限制的还是不被限制的即可,递推式:
这个可以直接 干 .
考虑直接扫,然后后面的贡献就是错排数的次幂,前面已经算过了,只需要算当前行的贡献(类似 Cantor 展开)
设当前扫到 ,于是对于当前行剩下 个元素,它们的填法受到 的限制,但不完全受到限制。具体地,求出有多少个数 使得 没有 在 当中出现过,且没有在 当中出现过,记作 ,那么相当于是 阶排列有多少个限制了 个位置的错排,就是广义错排数 .
为了方便记 为 组成的集合 .
现在考虑如何快速求 . 因为唯一不确定因素是 ,所以我们先求出满足 的 的个数 , 对真正 的贡献最多只是当 时要 .
因此,只需在 的前提下,维护存在多少 使得 ,并且支持查询 的 的个数 .
对当前行和上一行分别开一个权值树状数组就可随手维护,注意 时要减一下,后面还得加回去 .
时间复杂度 ,注意原题是 0-indexed,模拟赛模数变一下,并且 1-indexed,不要直接 cv 了 .
Code (CF1085G)
using namespace std;
const int N = 2222, P = 998244353;
typedef long long ll;
typedef double db;
typedef pair<int, int> pii;
int n, k, fac[N], a[N][N], d[N][N], pwd[N];
struct FenwickTree
{
int a[N];
inline void clear(){memset(a, 0, sizeof a);}
inline int lowbit(int x) {return x & -x;}
inline void add(int i, int x){if (!i) return ; for (; i<=n; i+=lowbit(i)) (a[i] += x) %= P;}
inline int query(int i){int ans = 0; for (; i; i&=i-1) (ans += a[i]) %= P; return ans;}
}null, full, T, nT;
void init()
{
d[0][0] = pwd[0] = 1;
for (int i=1; i<=n; i++)
for (int j=0; j<=i; j++)
{
if (!j) d[i][j] = 1ll * d[i-1][0] * i % P;
else if (i==j) d[i][i] = ((i == 1)? 0 : 1ll * (i-1) * (d[i-1][i-1] + d[i-2][i-2]) % P);
else d[i][j] = (1ll * j * d[i-1][j-1] % P + 1ll * (i-j) * d[i-1][j] % P) % P;
}
for (int i=1; i<=n; i++) pwd[i] = 1ll * pwd[i-1] * d[n][n] % P;
for (int i=1; i<=n; i++) full.add(i, 1);
}
bool A[N], B[N];
int main()
{
scanf("%d", &n); init();
for (int i=1; i<=n; i++)
for (int j=1; j<=n; j++) scanf("%d", a[i]+j);
int ans = 0;
for (int i=1; i<=n; i++)
{
memset(A, false, sizeof A); memset(B, false, sizeof B);
int L = n; T = null; nT = full;
ll _ = 0;
for (int j=1; j<=n; j++)
{
if (!B[a[i-1][j]]){--L; nT.add(a[i-1][j], -1);}
(_ += 1ll * T.query(a[i][j] - 1) * d[n-j][(i>1) * L] % P) %= P;
if (L || (i == 1)) (_ += 1ll * nT.query(a[i][j] - 1) * d[n-j][(i>1) * (L-1)] % P) %= P;
if (A[a[i][j]]) T.add(a[i][j], -1);
else nT.add(a[i][j], -1);
A[a[i-1][j]] = B[a[i][j]] = 1;
if (!B[a[i-1][j]]) T.add(a[i-1][j], 1);
if (!A[a[i][j]]) --L;
}
(ans += 1ll * _ * pwd[n-i] % P) %= P;
} printf("%d\n", ans);
return 0;
}
以下是博客签名,正文无关
本文来自博客园,作者:yspm,转载请注明原文链接:https://www.cnblogs.com/CDOI-24374/p/16515493.html
版权声明:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0)进行许可。看完如果觉得有用请点个赞吧 QwQ
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】