2023百度之星第三场
2023百度之星第三场
BD202321新材料
解题思路:
对于每一个种类的材料(该种类的材料有很多个,在不同位置),如果存在两个个体之间距离小于等于\(k\),那么我们最终答案就要异或上该种类的编号。
滑动窗口维护一个长度为\(k\)的区间即可。
对于每个新加入的元素,判断当前窗口内是否存在同类材料,若存在答案异或上他的种类并标记,以防重复计算。
代码:
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
ll n,k;
#define fi first
#define se second
void solve()
{
scanf("%lld %lld",&n,&k);
vector<int> a(n + 1);
for(int i = 1;i<=n;i++)
{
scanf("%d",&a[i]);
}
map<int,int> mp,cnt,st;
vector<int> q(n + 1);
int hh = 0;
int tt = -1;
ll ans = 0;
for(int i = 1;i<=n;i++)
{
while(hh <= tt && tt - hh + 1 > k)
{
cnt[a[q[hh]]] --;
hh ++;
}
if(tt - hh + 1 <= k)
{
if(cnt[a[i]] > 0 && !st[a[i]])
{
ans ^= a[i];
st[a[i]] = true;
}
q[++tt] = i;
cnt[a[i]]++;
}
}
cout<<ans<<endl;
}
int main()
{
int t = 1;
while(t--)
{
solve();
}
return 0;
}
BD202319新的阶乘
解法一:
若存在
我们要记录的信息,就是每个\(p_i\)和其对应的\(\alpha_i\).
则\(a^b\)对答案的贡献为
其中的\(p_i\)不变,但是\(\alpha_i \times b\)。
由于:
我们只需要将每个数\(x ,x-1,...,1\)进行质因子拆分,然后将他们质因子原本的指数乘上最初的次数,然后对应累加起来就是答案。
我们设\(minp[x]\)为\(x\)的最小质因子。
那么
其中\(x^k对minp[x]的贡献为k,对(x/minp[x])的贡献也为k\)。
我们从大到小不断对当前数进行拆分,最终会不断拆分最小质因子,直至完成对所有质因子的累加。
开始的时候,我们要初识化所有数\((x,x-1,...2,1)\)的次数,也就是他们根据质因子唯一分解后的质因子的指数要乘的倍数。
注意:初始化所有数的次数后,质数不必再进行拆分,因为它会加到自己身上,也就是重复计算。
举例:\(12^2 = 2 ^2 *6^2 = 2^2 * 2 ^2 * 3 ^ 2 = 2 ^4 * 3 ^ 2\),其中对\(2\)的贡献分别来自第一的最小质因数转移和\(6\)的最小质因数转移,这个过程中,开始的次数\(2\)一直随之移动。
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 1e7 + 10;
ll primes[N];
bool st[N];
int cnt = 0;
ll minp[N];
ll n;
void init()
{
for(int i = 2;i<=N-5;i++)
{
if(!st[i])
{
primes[cnt++] = i;
minp[i] = i;
st[i] = true;
}
for(int j = 0;primes[j] <= N / i;j++)
{
minp[i * primes[j]] = primes[j];
st[i * primes[j]] = true;
if(i % primes[j] == 0)
{
break;
}
}
}
}
void solve()
{
ll n;
scanf("%lld",&n);
if(n == 1)
{
printf("f(1)=0\n");
return;
}
vector<ll> f(n + 1,1);
for(int i = 2;i <= n;i++)
{
f[i] *= (n - i + 1);
}
for(int i = n;i > 1;i--)
{
if(minp[i] == i)
{
continue;
}
f[minp[i]] += f[i];
f[i / minp[i]] += f[i];
}
printf("f(%lld)=",n);
bool start = false;
for(int i = 0;i<cnt && primes[i] <= n;i++)
{
int p = primes[i];
if(f[p] > 0)
{
if(!start)
{
start = true;
}
else
{
printf("*");
}
printf("%lld",p);
if(f[p] > 1)
{
printf("^%lld",f[p]);
}
}
}
}
int main()
{
int t = 1;
init();
while(t--)
{
solve();
}
return 0;
}
解法二:
我们求\(1 \sim n\)中有多少个数是\(p\)的倍数,答案为\(\frac{n}{p}\)。
我们在阶乘分解中将\(n!\)质因数分解使用的方法是\(\alpha_i= \frac{n}{p_i} + \frac{n}{p_i^2} + .. .+ \frac{n}{p_i^k}\)。
本题中我们要将\(f(x)\)质因数分解。
其中,我们求取\(\alpha_i\)
如果求取$\frac{f(x)}{p_i} \(的效率能达到\)O(1)\(,那么整体时间复杂度就能达到\)O(nloglogn)$,复杂度不准,只是参考埃筛的感觉。本题能过。
所以我们开始思考如何\(O(1)\)求取\(\frac{f(x)}{p_i}\)。
\(f(x) = 1!\times2!\times3!\times...\times x!\).对于质数\(p\),我们在\(p!,(p + 1)!,... x!\)中都能够找到一个\(p\),所以起码能找到\(st = x + 1- p\)个1倍\(p\)。其中,\(x + 1\)是因为算上左右端点。比如\(2,3,4\)是\(4 - 2 + 1 = 3\)三个数。
找完一倍\(p\)后我们找两倍\(p\),即\(2*p\)。
那么同理,我们在\(2p!,(2p + 1)!,...,x!\)中都起码能找到\(t = x + 1 - 2p\)个\(2p\),我们发现\(t = st - p\)。
接下来找\(3p,4p,5p,...,kp\),其中,\((k + 1)p > x\)。就是把所有\(p\)的倍数找齐。我们发现,\(p\)前的系数每加1,我们找到的数量就减少\(p\).
所以最后我们找到的\(p,2p,3p,...,kp\)的数量\(x+1-p,x+1-2p,x+1-3p,...,x+1-kp\)会构成一个公差为\(p\)的等差数列,这个等差数列的和就是\(\frac{f(x)}{p}\)。
设
根据高斯公式求等差数列和
其中,项数\(\frac{st - ed + d}{d}\),中分母的\(+d\)同之前\(x +1-p\),是考虑加上端点的\(+1\)操作。
所以
接下来同阶乘分解一样,一个个求即可。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solve()
{
int n;
cin >> n;
vector<int> primes, minp(n + 1);
for (int i = 2; i <= n; i++)
{
if (!minp[i])
{
minp[i] = i;
primes.push_back(i);
}
for (auto p : primes)
{
if (i * p > n)
{
break;
}
minp[i * p] = p;
if (minp[i] == p)
{
break;
}
}
}
printf("f(%d)=", n);
for (auto p : primes)
{
ll sum = 0;
for (ll d = p; d <= n; d *= p)
{
int st = n + 1 - d;
int ed = n + 1 - n / d * d;
sum = sum + (ll)(st + ed) * (st - ed + d) / d / 2;
}
if (sum == 1)
{
printf("%d", p);
}
else
{
printf("%d^%lld", p, sum);
}
if (p == primes.back())
{
printf("\n");
}
else
{
printf("*");
}
}
}
int main()
{
int t = 1;
while (t--)
{
solve();
}
return 0;
}
BD202322与蝴蝶一起消散吧
解题思路:
我们在每一波怪物来袭开始都可能有两种情况(第一波只有一种情况):必杀技可用和不可用。
对于第\(i\)波怪物,如果\(a_i >= k\),那么我们一定是有必杀技就放,因为放了一定就能节省\(k\)分钟。
其中,如果\(a_i > k\),那么无论我们开始是否有技能,这波怪物打完,我们手里一定是有技能的。若\(a_i == k\),那么这波结束是否有必杀,取决于我们的开始状态和怪物数量。
如果\(a_i < k\),那我们分情况操作。
首先,如果当前有必杀技并且需要消灭的怪物的数量是偶数,那么我们一定是有必杀放必杀。因为这样在结束时一定有必杀。既保留了必杀的选择权又最大化减少了时间。
若当前没有必杀技,那么我们只能平\(A\)。若平\(A\)后进入有必杀加偶数怪物环节,同上。否则会转入下一个情况。
若当前有必杀技且剩余怪物数量为奇数,我们有两种选择。
- 有必杀用必杀。这样这波结束时我们是没有必杀技的。
- 平\(A\)一轮,进入有必杀加偶数环节,这样能保证进入下一轮时有必杀技可用。
定义\(f[i][j][k]:在进入第i轮时状态为j,在消灭掉第i轮怪物后,状态为k的花费时间。其中状态0为有必杀,1位无必杀\)。
注意:奇偶性不同花费的时间会有变化,最终答案为\(ans = min\{f[n][0][0] ,f[n][0][1],f[n][1][0],f[n][1][1]\}\)
代码:
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
ll n,k;
const int N = 1e5 + 10;
ll f[N][4][4];
void solve()
{
scanf("%lld %lld",&n,&k);
vector<ll> a(n + 1),m(n + 1);
for(int i = 1;i<=n;i++)
{
scanf("%lld %lld",&a[i],&m[i]);
}
ll ans = 1e18;
for(int i = 1;i<=n;i++)
{
for(int j = 0;j<2;j++)
{
for(int k = 0;k<2;k++)
{
f[i][j][k] = 1e18;
}
}
}
//0 you
//1 wu
for(int i = 1;i<=n;i++)
{
if(a[i] > k)
{
f[i][0][0] = min(f[i-1][1][0],f[i-1][0][0]) + (m[i] * (a[i] - k));
f[i][1][0] = min(f[i-1][1][1],f[i-1][0][1]) + (a[i] + (m[i] - 1) * (a[i] - k));
}
else if(a[i] == k)
{
if(m[i] & 1)
{
f[i][1][0] = min(f[i-1][1][1],f[i-1][0][1]) + (a[i] + (m[i] / 2) * a[i]);
f[i][0][1] = min(f[i-1][1][0],f[i-1][0][0]) + ((m[i] / 2) * a[i]);
}
else
{
f[i][1][1] = min(f[i-1][1][1],f[i-1][0][1]) + ((m[i] / 2) * a[i]);
f[i][0][0] = min(f[i-1][1][0],f[i-1][0][0]) + ((m[i] / 2) * a[i]);
}
}
else
{
if(m[i] & 1)
{
f[i][1][0] = min(f[i-1][1][1],f[i-1][0][1]) + (a[i] + (m[i] / 2) * a[i]);
f[i][0][1] = min(f[i-1][1][0],f[i-1][0][0]) + ((m[i] / 2) * a[i]);
f[i][0][0] = min(f[i-1][1][0],f[i-1][0][0]) + (a[i] + (m[i] - 1) / 2 * a[i]);
}
else
{
f[i][1][1] = min(f[i-1][1][1],f[i-1][0][1]) + ((m[i] / 2) * a[i]);
f[i][1][0] = min(f[i-1][1][1],f[i-1][0][1]) + (2 * a[i] + ((m[i] - 2) / 2) * a[i]);
f[i][0][0] = min(f[i-1][1][0],f[i-1][0][0]) + ((m[i] / 2) * a[i]);
}
}
}
ans = min(ans,f[n][1][0]);
ans = min(ans,f[n][0][1]);
ans = min(ans,f[n][1][1]);
ans = min(ans,f[n][0][0]);
cout<<ans<<endl;
}
int main()
{
int t = 1;
while(t--)
{
solve();
}
return 0;
}
BD202317石碑文
解题思路:
状态转移:
从\(\varnothing\)转移\((0,3,6)\):
从\(s\)转移\((1,4,7)\):
从\(sh\)转移\((2,5,8)\):
从满足三个\(shs\)转移:
代码:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int mod = 1e9 + 7;
void solve()
{
int n;
scanf("%d", &n);
vector<vector<ll>> f(n + 10, vector<ll>(12));
// 初始化
f[0][0] = 1;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < 10; j++)
{
if (j == 9)
{
// 9 -> 9;
f[i + 1][j] = (f[i + 1][j] + 26ll * f[i][j]) % mod;
}
else if (j % 3 == 1)
{
// s -> s;
f[i + 1][j] = (f[i + 1][j] + f[i][j]) % mod;
// s -> sh;
f[i + 1][j + 1] = (f[i + 1][j + 1] + f[i][j]) % mod;
// s -> kong;
f[i + 1][j - 1] = (f[i + 1][j - 1] + f[i][j] * 24ll) % mod;
}
else if (j % 3 == 2)
{
// sh -> shs;
f[i + 1][j + 1] = (f[i + 1][j + 1] + f[i][j]) % mod;
// sh -> kong(sha);
f[i + 1][j / 3 * 3] = (f[i + 1][j / 3 * 3] + f[i][j] * 25ll) % mod;
}
else
{
// kong -> s;
f[i + 1][j + 1] = (f[i + 1][j + 1] + f[i][j]) % mod;
// kong -> kong;
f[i + 1][j] = (f[i + 1][j] + f[i][j] * 25ll) % mod;
}
}
}
cout << f[n][9] << endl;
}
int main()
{
int t = 1;
while (t--)
{
solve();
}
return 0;
}
BD202318染色游戏
解题思路:
我们可以按行染色或者按列染色。
举例,如果我们枚举所有列可能的染色情况进行染色,就是先确定一列的状态。根据题意相邻两列完全一致或者完全不一致,不难得知,当一列确定后,所有列一共只有两种状态,和确定列完全一致或者完全不一致。
枚举行染色同理。
一列一共有\(m\)个空格,我们可以枚举染\(0 \sim m\)个颜色。那么完全不一致就是染\(m \sim 0\)个空格。
假设我们当前枚举到染\(i\)个空格,根据题目要求要将\(k\)用完,得到式子\(i * t + (m - i)*(n - t) = k\)。
其中,\(t\)是有多少个列要染\(i\)个格子,剩下的\(n - t\)列就是完全不一致了,每列要染\(m - i\)个格子。
公式移项得:
我们设
其中若$b = 0 $,我们发现进行除法运算时会出问题,所以单独拿出来讨论。
通过观察,不难发现当\(b = 0\)时,所有列的染色格子数量都是一样的。所以我们直接计算加上即可。\(ans = ans +C_m^i \times 2^n\)。
其中,\(C_m^i\)是说\(m\)个格子中取\(i\)个格子进行染色的方案数,\(2^n\)是\(n\)列的状态都有两种合法的选法。
对于$ t \leq0 和t \geq0$的非法情况我们自然可以直接跳过。
合法情况答案处理:\(ans = ans + C_m^i \times C_n^t\)。其中,\(C_m^i\)同上,\(C_n^t\)指从\(n\)列中选出\(t\)列染\(i\)个格子,剩下\(n - t\)列染\((m - i)\)个格子。
注意:组合数学经典重复计算,\(C_m^i和C_m^{m - i}\)其实是一样的,要记得除\(2\)。\(ans = ans +C_m^i \times 2^n\)自然也重复计算了。
求组合数建议用线性求逆预处理。
代码:
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int mod = 998244353;
ll qmi(ll a, ll b)
{
ll res = 1;
while (b)
{
if (b & 1)
{
res = (res * a) % mod;
}
b >>= 1;
a = (a * a) % mod;
}
return res;
}
void solve()
{
ll n, m, k;
scanf("%lld %lld %lld", &n, &m, &k);
ll len = max(n, m);
vector<ll> f(len + 1);
f[0] = 1;
for (int i = 1; i <= len; i++)
{
f[i] = f[i - 1] * i % mod;
}
vector<ll> invs(len + 1);
invs[len] = qmi(f[len], mod - 2);
for (int i = len; i; i--)
{
invs[i - 1] = invs[i] * (ll)i % mod;
}
auto C = [&](ll a, ll b) -> ll
{
return (f[a] * invs[a - b] % mod) * invs[b] % mod;
};
ll ans = 0;
for (int i = 0; i <= m; i++)
{
ll a = k - (m - i) * n;
ll b = 2 * i - m;
// 根据公式推导,我们发现存在分母为0的情况需要特殊讨论
// 当分母为0时,不难发现相同和完全相反染色数一样
// 所以怎么选需要的总颜色数都是一样的。可直接计算出。
if (b == 0)
{
ll t = (n * i);
if (t == k)
{
// qmi(2,n)
// 由于每列选完全相同和完全不同都合法。
ans = (ans + C(m, i) * qmi(2, n)) % mod;
}
continue;
}
// 说明无解
if (a % b)
{
continue;
}
// 非法
if (a / b < 0 || a / b > n)
{
continue;
}
// 有t - 1列和第一列一样
ll t = a / b;
ans = (ans + C(m, i) * C(n, t)) % mod;
}
// 处理重复计算
ans = ans * qmi(2, mod - 2) % mod;
printf("%lld\n", ans);
}
int main()
{
int t = 1;
while (t--)
{
solve();
}
return 0;
}