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新的阶乘

解法一:

若存在

\[a = p_1^{\alpha_1}p_2^{\alpha_2}...p_k^{\alpha_k} \]

我们要记录的信息,就是每个\(p_i\)和其对应的\(\alpha_i\).

\(a^b\)对答案的贡献为

\[a^b = p_1^{\alpha_1b}p_2^{\alpha_2b}...p_k^{\alpha_kb} \]

其中的\(p_i\)不变,但是\(\alpha_i \times b\)

由于:

\[f(x) = x^1 * (x - 1)^2 * (x - 2)^3 * ... *2^{x-1} * 1^x \]

我们只需要将每个数\(x ,x-1,...,1\)进行质因子拆分,然后将他们质因子原本的指数乘上最初的次数,然后对应累加起来就是答案。

我们设\(minp[x]\)\(x\)的最小质因子。

那么

\[\begin{align*} x &= minp[x] * (x/minp[x]) \\ x^k &= minp[x]^k * (x/minp[x])^k \end{align*} \]

其中\(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)\)质因数分解。

\[\begin{align*} f(x) &= x * (x-1)^2...*1^x\\ &= p_1^{\alpha_1}p_2^{\alpha_2}...p_k^{\alpha_k}\\ &= 1!\times2!\times3!\times...\times x! \end{align*} \]

其中,我们求取\(\alpha_i\)

\[\alpha_i= \frac{f(x)}{p_i} + \frac{f(x)}{p_i^2} + .. .+ \frac{f(x)}{p_i^k} \]

如果求取$\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}\)

\[\begin{align*} & st= x + 1 - p\\ &ed = x + 1 - kp\\ &d = p\\ \end{align*} \]

根据高斯公式求等差数列和

\[sum = \frac{(st + ed) * \frac{st - ed + d}{d}}{2} \]

其中,项数\(\frac{st - ed + d}{d}\),中分母的\(+d\)同之前\(x +1-p\),是考虑加上端点的\(+1\)操作。

所以

\[\frac{f(x)}{p} = \frac{(st + ed) * \frac{st - ed + d}{d}}{2} \]

接下来同阶乘分解一样,一个个求即可。

#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\)后进入有必杀加偶数怪物环节,同上。否则会转入下一个情况。

若当前有必杀技且剩余怪物数量为奇数,我们有两种选择。

  1. 有必杀用必杀。这样这波结束时我们是没有必杀技的。
  2. \(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)\)

\[\begin{align*} &\varnothing \rightarrow_{+s} s\\ &\varnothing \rightarrow_{+other} \varnothing \end{align*} \]

\(s\)转移\((1,4,7)\)

\[\begin{align*} &s \rightarrow_{+h} sh\\ &s \rightarrow_{+s} s\\ &s \rightarrow_{+other} \varnothing \end{align*} \]

\(sh\)转移\((2,5,8)\)

\[\begin{align*} &sh \rightarrow_{+h} \varnothing\\ &sh \rightarrow_{+s} \varnothing or9\\ \end{align*} \]

从满足三个\(shs\)转移:

\[\begin{align*} satisfy(shs) \rightarrow_{all} satisfy(shs) \end{align*} \]

代码:

#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\)个格子。

公式移项得:

\[t = \frac{k - (m - i)*n}{2*i - m} \]

我们设

\[\begin{align*} a &= k - (m - i) * n\\ b &= 2 * i - m \end{align*} \]

其中若$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;
}
posted @ 2023-09-25 15:18  value0  阅读(895)  评论(0)    收藏  举报