错题集_1

Bookshelf B

Farmer John最近为奶牛们的图书馆添置了一个巨大的书架,尽管它是如此的大,但它还是几乎瞬间就被各种各样的书塞满了。现在,只有书架的顶上还留有一点空间。

所有 N ( 1 ≤ N ≤ 20 , 000 ) N(1 \le N \le 20,000) N(1N20,000) 头奶牛都有一个确定的身高 H i ( 1 ≤ H i ≤ 10 , 000 ) H_i(1 \le H_i \le 10,000) Hi(1Hi10,000)。设所有奶牛身高的和为S。书架的高度为B,并且保证 1 ≤ B ≤ S < 2 , 000 , 000 , 007 1 \le B \le S < 2,000,000,007 1BS<2,000,000,007

为了够到比最高的那头奶牛还要高的书架顶,奶牛们不得不像演杂技一般,一头站在另一头的背上,叠成一座“奶牛塔”。当然,这个塔的高度,就是塔中所有奶牛的身高之和。为了往书架顶上放东西,所有奶牛的身高和必须不小于书架的高度。
显然,塔中的奶牛数目越多,整座塔就越不稳定,于是奶牛们希望在能够到书架顶的前提下,让塔中奶牛的数目尽量少。 现在,奶牛们找到了你,希望你帮她们计算这个最小的数目。

#include <bits/stdc++.h>
using namespace std;
bool cm(long long a, long long b)
{
    return a > b;
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    int i, j, k;
    int n, b;
    
    cin >> n >> b;
    int a[n];
    long long ans = 0;
    for (i = 0; i < n; i++)
        cin >> a[i];
    sort(a, a + n, cm);
    for (i = 0; i < n; i++)
    {
        ans +=a[i];
        if (ans >= b)
        {
            cout << i + 1 << endl;
            break;
        }
    }
    return 0;
}

umi的函数

题目描述

这个函数接受两个字符串 s1,s2。这些字符串只能由小写字母组成,并且具有相同的长度。这个函数的输出是另一个长度与 s1,s2 相同的字符串 g。g 的第 i 个字符等于 s1 的第i 个字符和 s2 的第 i 个字符的最小值,简单来说,g[i]=min(s1[i],s2[i])。

例如:f(“ab”,“ba”)= “aa”, f(“nzwzl”,“zizez”)=“niwel”.

她现在有两个相同长度的只有小写字母的字符串 x,y。找出任何一个满足 f(x,z)=y 的

字符串 z。如果找不到这样的字符串的话,请输出-1。

#include <bits/stdc++.h>
using namespace std;
int main()
{
    long t, n, i;
    char x[101], z[101];
    cin >> n;
    cin >> x;
    cin >> z;
    for (i = 0; i < n; i++)
        if (x[i] < z[i])
        {
            cout << -1 << endl;
            return 0;
        }
    cout << z << endl;
    return 0;
}

子集和问题

#include <iostream>
using namespace std;

int n, v, a[1000], ans[1000], cv;
bool flag;

void Backsearch(int t)
{
    if (cv == 0)
        flag = 1;
    if (t > n || flag == 1)
        return;
    if (flag == 0 && a[t] <= cv)
    {
        cv -= a[t];
        ans[t] = a[t];
        Backsearch(t + 1);
        if (flag == 0)
        {
            cv += a[t];
            ans[t] = 0;
        } 
    }
    if (flag == 0)
        Backsearch(t + 1);
}

int main()
{
    cin >> n >> v;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    flag = 0;
    cv = v;
    Backsearch(1);
    if (flag == 1)
    {
        for (int i = 1; i <= n; i++)
        {
            if (ans[i] != 0)
                cout << ans[i] << " ";
        }
    }
    else
        cout << "No Solution!";
    return 0;
}

Hungry Sequence

题目

求一个含n个元素数列,元素从小到大,并且后面的数不能被前面的数整除

模拟

直接选从n开始的几个数字就行

int main()
{
	int t;
	cin >> t;
	for(i = t; i <= 2 * t - 1; i++)
	{
		cout << i << ' ';
	}
}

打表, 生成PrimeNum个素数

int ans[100005];
bool vis[10000000];
int main()
{
    int cnt = 1;
    for (i = 2; i < 1300000; i++)
    {
        if (vis[i])
            continue;
        for (int j = i + i; j < 10000000; j += i)
            vis[j] = true;
        if (!vis[i])
            ans[cnt++] = i;
        if (cnt > 100000)
        {
            break;
        }
    }
    int PrimeNum;
    cin >> PrimeNum;
    {
        for (i = 1; i <= PrimeNum; i++)
            printf("%d ", ans[i]);
    }
}

Shifting Stacks

题面

你有 n n n 个数,第 i i i 个数是 h i h_i hi。你可以对它们做不限量的操作,每一次操作你可以选一个数 h j h_j hj,让 h j h_j hj 1 1 1 h j + 1 h_{j+1} hj+1 1 1 1,问能否让这个序列变成一个严格递增的序列。

判断用数列的和比较

#include <iostream>
using namespace std;
int n, b;

int main()
{
    int a[150000];
    int t;
    cin >> t;
    while (t--)
    {
        long long sum1, sum2;
        b = sum1 = sum2 = 0;
        cin >> n;
        for (i = 1; i <= n; i++)
            cin >> a[i];
        for (i = 1; i <= n; i++)
        {
            sum1 += a[i];
            sum2 += (i - 1); // 0,到i-1的和
            if (sum1 < sum2)
            {
                cout << "NO" << endl;
                b = 1;
                break;
            }
        }
        if (!b)
            cout << "YES" << endl;
    }
}

Plant

题目

Dwarfs 种了一株非常有意思的植物,这株植物像一个方向向上的三角形。它有一个迷人的特点,那就是在一年后一株方向向上的三角形的植物就会被分成 4 株三角形的植物:它们当中的三株方向是向上的,一株方向是向下的。 又一年之后,每株植物都会分成四个,规则如上。之后的每年都会重复这一过程。下面的图说明了这一发展过程。
在这里插入图片描述

请帮助 Dwarfs 算出n年后将会有多少个方向向上的三角形。

快速幂, 而不是循环

循环时间过不去

首先明确,每年会在原先向上的叶子上长出3片向上的叶子。
原先向下的叶子上长出1片向上的叶子。
所以设 a[0]=1; b[0]=0;
a为向上的叶子数; b为向下的叶子数
则递推转移方程为

a[i] = a[i - 1] * 3 + b[i - 1];
b[i] = b[i - 1] * 3 + a[i - 1];

而n的范围太大所以要想办法
我们把递推转移方程做差得到a[i] - b[i] = 2 * (a[i] - 1 - b[i] - 1),可得到第i年两者之差的公式,又可知每年有4个三角,即 a[i] + b[i] = 4 ^ i, a[i] - b[i] = 2 ^ n两者作和,除2即为答案,快速幂即可解决。
a[n] = 2 ^ (n * 2 - 1) +2 ^ (n - 1)

#include <iostream>
using namespace std;

int main()
{
    long long a = 2, n, x, ans = 1, mod = 1e9 + 7;
    cin >> n;
    while (n)
    {
        if (n & 1)
        {
            ans = (ans * a) % mod;
        }
        a = (a * a) % mod;
        n >>= 1;
    }
    cout << ans * (ans + 1) / 2 % mod << endl;
}

RPG难题

题目

有排成一行的n个方格,用红(Red)、粉(Pink)、绿(Green)三色涂每个格子,每格涂一色,要求任何相邻的方格不能同色,且首尾两格也不同色.求全部的满足要求的涂法.

以上就是著名的RPG难题.

输入数据包含多个测试实例,每个测试实例占一行,由一个整数N组成,(0 < n <= 50)。

对于每个测试实例,请输出全部的满足要求的涂法,每个实例的输出占一行。

DP

易知dp(1)=3; dp(2)=6; dp(3)=6; dp(4)=18;

考虑n>3的情况

第n-1个格子和第一个格子不同,则为dp(n-1)

第n-1个格子和第1个格子相同,则第n-2个格子和第一个格子必然不同,此时为dp(n-2)再乘第n-1个格子的颜色数,很显然第n-1个格子可以是第一个格子(即第n-2个格子)的颜色外的另外两种,这样为2 * dp(n-2);

因此总的情况为dp(n)=dp(n-1)+2*dp(n-2)

2个限制条件

  1. 相邻的方格不能同色
  2. 首尾两格不同色

n = 1时, 解为R, P, G
n = 2时, 可以在n = 1字符串后面加一个字符得到
在R后面可以加P和G, 但是不能加R, 如RP, RG
同理,可得 n = 2时一共有6组解
n = 3时, 由于n = 2时的字符串已经包含2个不同的字符,所以只有一种选择,还是6组解
n = 4时, 可以直接在n = 3的字符串后面加一个字符,也只有一个字符可以选择.
但是, 如果n = 3字符串违背条件2, 那原本错误的答案,加上一个字符后就会变成一个正确的答案
所以, 后面加的这个字符和头部字符不相同,那就和他前面相邻的字符不同,但是这两个字符是相同的, 因此a[4] = a[3] + 2 * a[2]

得出关系式

dp[i] = dp[i - 1] + 2 * dp[i - 2];
#include <bits/stdc++.h>
using namespace std;
int a[10005], dp[10005];

int main()
{
    int n;
    while (scanf("%d", &n) != EOF)
    {
        a[1] = 3;
        a[2] = 6;
        a[3] = 6;
        for (i = 4; i <= n; i++)
        {
            a[i] = a[i - 1] + 2 * a[i - 2];
        }
        cout << a[n] << endl;
    }
    return 0;
}

统计问题

在一无限大的二维平面中,我们做如下假设:
1、 每次只能移动一格;
2、 不能向后走(假设你的目的地是“向上”,那么你可以向左走,可以向右走,也可以向上走,但是不可以向下走);
3、 走过的格子立即塌陷无法再走第二次;

求走n步不同的方案数(2种走法只要有一步不一样,即被认为是不同的方案)。

首先给出一个正整数C,表示有C组测试数据
接下来的C行,每行包含一个整数n (n<=20),表示要走n步。

输出走n步的不同方案总数;每组的输出占一行。

打表

#include <bits/stdc++.h>
using namespace std;
int ans[21] = {0, 3, 7, 17, 41, 99, 239, 577, 1393, 3363, 8119, 19601, 47321, 114243, 275807, 665857, 1607521, 3880899, 9369319, 22619537, 54608393};
int main()
{
    int c, n;
    cin >> c;
    while (c--)
    {
        cin >> n;
        cout << ans[n] << endl;
    }
}

dp

第n步的走法为F(n),往上走的步数为a(n),往左或往右走的步数为b(n)
故:F(n) = a(n) + b(n)

由于不能往下走,所以向上走:a(n) = a(n - 1) + b(n - 1)(前(n - 1)步内往上走的步数 + 前(n - 1)步内往左或右的步数)

由于走过的不能返回,所以往左或右走:b(n) = 2 * a(n - 1) + b(n - 1)(向上走可以是左上和右上)

#include <iostream>
using namespace std;
int main()
{
    int c, n, a[21], i;

    a[1] = 3;
    a[2] = 7;
    for (i = 3; i <= 20; i++)
        a[i] = 2 * a[i - 1] + a[i - 2];

    cin >> c;
    while (c--)
    {
        cin >> n;
        cout << a[n] << endl;
    }
    return 0;
}

记录三个方向的方案

a[i]表示第i步向上走时的方案数 b[i]向右走的方案数 c[i]向左走的方案数

第i - 1步能走时,第 i 步能向上走.向上走的a[i]来自向上的a[i - 1],向右的b[i - 1], c[i - 1]

a[i] = a[i - 1] + b[i - 1] + c[i - 1];

b, c同理

所以最后的答案是a[n] + b[n] + c[n]

long long a[25], b[25], c[25];
int main()
{
    a[1] = b[1] = c[1] = 1;
    for (i = 2; i <= 20; i++)
    {
        a[i] = a[i - 1] + b[i - 1] + c[i - 1];
        b[i] = b[i - 1] + a[i - 1];
        c[i] = c[i - 1] + a[i - 1];
    }
    int t, n;
    cin >> t;
    while (t--)
    {
        cin >> n;
        cout << a[n] + b[n] + c[n] << endl;
    }
}

从一到n挑k个不同的数和为x

题面翻译

t t t 组数据。每组数据给定 n , k , x n,k,x n,k,x

请你判断能否在 1 ∼ n 1\sim n 1n不重复的恰好选出 k k k 个数使得这 k k k 的数的和为 x x x

可以选出输出 YES,否则输出 NO

评测忽略大小写。例如,yEsYesyesYES 都表是肯定回答。

不要枚举

No的情况:

  1. 最小的 k 个数加起来仍然比 x 大
  2. 最大的 k 个数加起来仍然比 x 小

如果 x 大于 从 1 到 n 的最小值, 小于最大值 可以通过将差值分到每一项中,在两极值中间都可以取到

#include <bits/stdc++.h>
using namespace std;
long long n, k, x, t, Min, Max;
int main()
{
    cin >> t;
    while (t--)
    {
        cin >> n >> k >> x;
        Max = (n - k + n + 1) * k / 2;
        Min = (1 + k) * k / 2;
        if (Max >= x && Min <= x || x == Min || x == Max)
            cout << "YES" << endl;
        else
            cout << "NO" << endl;
    }
    return 0;
}

Chemistry

题目

T T T 组数据,每组给定一个字符串 S S S,它的长度 n n n,和要删除的字母数量 k k k。问能否在删除 k k k 个字符后对字符串重新排序使得其成为回文字符串。

思路

回文需满足 只有0个或1个的字母的个数为奇数,其他字母的个数均为偶数

遍历一遍看有每个字母的个数
能删去 k 个字母 ==> 最多存在 k + 1 个奇数个数的字符 (删去 k 个, 留下一个放在中间)

#include <bits/stdc++.h>
using namespace std;

i, j, k, T, n;
int alpha[27];
void solve()
{
    int n, k, count = 0;
    string s;
    cin >> n >> k >> s;
   
    for (i = 0; i < n; i++)
        alpha[s[i] - 'a']++;

    for (i = 0; i < 26; i++)
    {
        if (alpha[i] % 2 == 1)
            count++;
        alpha[i] = 0;
    }
    if (count <= k + 1)
        cout << "Yes" << endl;
    else
        cout << "No" << endl;
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    cin >> T;
    while (T--)
        solve();
    return 0;
}

Raspberries

题目

T组数据,每组数据有一个长度为 n n n 的数组 a a a 与一个数 k k k,你每次操作可以对数组内的任意一个数加 1 1 1 ,需要使得 ∏ i = 1 n a i \prod \limits_{i=1}^n a_i i=1nai 能被 k k k 整除,求最小操作步数 ( $ 2 \leq k \leq 5 $ )

思路

k 的范围是 2 ~ 5, 其中只有 4 不是质数, 需要对其特殊讨论
其余的都是质数, 如果输入的数里含有他们的倍数,则直接就成立

综上, 只需要对 4 的情况讨论

  1. 有一个数是4的倍数,那么直接整除
  2. 另一种是有两个数是2的倍数,也可以让整体的乘积凑出4的倍数

Factorial Divisibility

题目

给定两个正整数 n n n x x x 和一个正整数序列 a 1 ∼ a n a_1 \sim a_n a1an

请问 ∑ i = 1 n a i ! \sum_{i = 1}^n a_i! i=1nai! 是否能被 x ! x! x! 整除。如果能则输出一个字符串 Yes \texttt{Yes} Yes,不能则输出字符串 No \texttt{No} No

(x+1)*x! = (x+1)!

统计一下a[i] < x每个a[i]出现的次数 把他转换为大的阶乘
最终 1 ~ x-1 ,a[i]的出现次数均为0 ==> 可以被x!整除

#include <bits/stdc++.h>
using namespace std;
int a[500050];
int main()
{
    int n, i, x, t;
    cin >> n >> x;
    for (i = 1; i <= n; i++)
    {
        cin >> t;
        a[t]++;
    }
    for (i = 0; i < x; i++)
    {
        a[i + 1] += a[i] / (i + 1);
        a[i] %=  i + 1;
    }
    for (i = 1; i < x; i++)
    {
        if (a[i])
        {
            cout << "No";
            return 0;
        }
    }
    cout << "Yes";
}

Accidental Victory

题面翻译

n n n 支队伍参加比赛,每个队伍初始时有一些代币。

比赛每一轮随机挑两个代币数不为0的队伍,然后代币多的队伍获胜,代币少的队伍把代币全部给代币多的(代币数量相同则随机),直到最后只有一个队伍有代币, 这个队伍获胜。

求哪些队伍是有可能获胜的。

前缀和

先排序, 判断是否可以打败第一个之前比他强的选手
如果行, 打败
这个选手打败第一个比他强的选手最终的实力和这一个后者选手打败所有比他弱的最终的实力一样, 如果能这样持续到最后一个 那么这一个肯定行
一个人想赢, 需要打败比他弱的选手

寻找最后一个不可以完成的下标即可。

代码

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

const int maxn = 2e5 + 50;
typedef struct node
{
    ll Num;
    index;
} player;
player a[maxn];
bool cmp(player m, player n)
{
    return m.Num < n.Num;
}
void solve()
{
    set<int> ans; // set自动排序 题目要求最终"increasing order"
    ans.clear();
    int n, i;
    cin >> n;
    for (i = 1; i <= n; i++)
    {
        scanf("%lld", &a[i].Num);
        a[i].index = i;
    }
    sort(a + 1, a + 1 + n, cmp);
    for (i = 2; i <= n; i++)
        a[i].Num += a[i - 1].Num;

    int Last = 0;
    for (i = n - 1; i >= 0; i--)
    {
        if (a[i].Num < a[i + 1].Num - a[i].Num)
        {
            Last = i + 1;
            break;
        }
    }
    for (i = Last; i <= n; i++)
        ans.insert(a[i].index);

    cout << ans.size() << endl;
    for (set<int>::iterator it = ans.begin(); it != ans.end(); it++)
        cout << *it << ' ';
    cout << endl;
}
int main()
{
    int T;
    cin >> T;
    while (T--)
        solve();
    return 0;
}

mod M

题面翻译

给出一个有 n n n 个非负整数的数列 A A A

现在进行以下的一次操作:

  • A A A 中所有数对一个大于等于2的整数 M M M 取模,替换掉原来的数

例如 A = ( 2 , 7 , 4 ) A=(2,7,4) A=(2,7,4) ,取 M = 4 M=4 M=4 ,则操作后 A = ( 2   m o d   4 , 7   m o d   4 , 4   m o d   4 ) = ( 2 , 3 , 0 ) A=(2 \bmod 4,7 \bmod 4,4 \bmod 4)=(2,3,0) A=(2mod4,7mod4,4mod4)=(2,3,0)

请问,操作后的数列 A A A 最少能有多少种不同的数字。

gcd

当m=2时,ai的取值只可能为0或1,因此,答案最多为2。
接下来我们考虑答案为1的情况。
显然,当gcd{a1 - a2, a2 - a3,•••,an-1 - an}不为1时,即相邻两数的差值的最小公约数不为1时,
我们就可以去它们的最小公约数,这样可以保证同为1个数。

// 这个会编译错误
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
int a[maxn] = {0};
int gcd(int a, int b)
{
    if (b == 0)
        return a;
    return gcd(b, a % b);
}
int main()
{
    int n, i;
    cin >> n;
    for (i = 1; i <= n; i++)
        cin >> a[i];
    int g = 0;
    for (i = 2; i <= n; i++)
    {
        g = gcd(g, fabs(a[i] - a[i - 1]));
    }
    if (g == 1)
        puts("2");
    else
        puts("1");
}

得到缘由
fabs 返回double类型,而传入的参数为int类型
abs 返回 int 类型

#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
int a[maxn] = {0};

int gcd(int a, int b)
{
    if (b == 0)
        return a;
    return gcd(b, a % b);
}
int main()
{
    int n, i;
    cin >> n;
    for (i = 1; i <= n; i++)
        cin >> a[i];
    int g = 0;
    for (i = 2; i <= n; i++)
    {
        g = gcd(g, (int)fabs(a[i] - a[i - 1]));
    }
    if (g == 1)
        puts("2");
    else
        puts("1");
}

Present

题面翻译

一个长度为 n n n 的序列 a a a ,你有 m m m 次操作的机会,每次操作是将其中连续的 w w w 个元素增加 1 1 1 。最大化最终序列的最小值。

二分 + 差分数组

考虑贪心搞。如果 a 中存在一个值 <x
就把他加到 ≥x 即可,但是为了不浪费操作次数,
我们可以直接加到x。这里的实现是以这个数为左端点来考虑。
但是有一些位置不可能成为长度为20 的区间的左端点。这个时候考虑完前面所有的操作之后直接把[n - w + 1,n]这个区间加上剩下的操作次数即可。

#include <bits/stdc++.h>
using namespace std;
typedef int ll;
typedef long long int li;
const ll MAXN = 2e5 + 51;
ll n, m, w, l, r, mid, res;
ll x[MAXN], diff[MAXN];

inline ll check(ll mid)
{
    ll c = 0, cur = m, p;
    for (register int i = 1; i <= n; i++)
    {
        diff[i] = x[i] - x[i - 1];
    }
    for (register int i = 1; i <= n - w + 1; i++)
    {
        c += diff[i];
        if (cur > 0 && c < mid)
        {
            p = min(cur, mid - c), diff[i] += p, diff[i + w] -= p, cur -= p, c += p;
        }
    }
    diff[n - w + 1] += cur, diff[n + 1] -= cur, c = 0;
    for (register int i = 1; i <= n; i++)
    {
        c += diff[i];
        if (c < mid)
        {
            return 0;
        }
    }
    return 1;
}
int main()
{
    n = read(), m = read(), w = read(), l = 0x3f3f3f3f, r = 0x3f3f3f3f;
    for (register int i = 1; i <= n; i++)
    {
        x[i] = read(), l = min(l, x[i]);
    }
    while (l <= r)
    {
        ll mid = (l + r) >> 1;
        check(mid) ? l = mid + 1, res = mid : r = mid - 1;
    }
    printf("%d\n", res);
}

22年10月校赛

排成一队 – 求中位数

题目

在一个划分成网格的操场上,n个士兵散乱地站在网格点上。网格点用整数坐标(x,y)表示。但在同一时刻任一网格点上只能有一名士兵。士兵们要整齐地列成一个水平队列,即排列成(x,y),(x+1,y),…,(x+n-1,y)。如何选择x和y的值才能使士兵们以最少的总移动步数排成一行。

思路

  1. 最终的y为什么是中位数而不是平均数?

    中位数的性质:所有数与中位数的绝对差之和最小

  2. 最终的x为什么也是中位数?
    首先对士兵x位置进行从小到大排序,设士兵排成一排时左起第一个人的位置为x0,则第二个人位置为x0+1,第三个人位置为x0+2,第i个人位置为x0+(i-1)。
    士兵在x方向上需要移动的步数为 |x1-x0|+|x2-x0-1|+|x3-x0-2|+···进行转化后|x1-x0|+|(x2-1)-x0|+|(x3-2)-x0|+···
    x0是(xi-i+1)数列的中位数。

for(i = 1; i <= n; i++)
{
    x[i] = x[i] - i + 1;
}
sort (x + 1 ,x + n + 1);
for(i = 1; i <= n / 2; i++)
{
    xmile += x[n - i + 1] - x[i];
}

代码

#include <iostream>
#include <cmath>
#include<algorithm>
using namespace std;

int i, j, n;

int main()
{
    cin >> n;
    int x[n], y[n],xpath = 0, ypath = 0;

    for (i = 0; i < n; i++) {
        cin>> x[i] >> y[i];

    }
    sort(x, x + n);
    sort(y, y + n);
    
   for (i = 0; i < n / 2; i++)
   {
       ypath += y[n - 1 - i] - y[i];
   }
    
    for (i  = 0; i < n; i++)
    {
        x[i] = x[i] - i;
    }
    sort(x, x + n);
    for (i  = 0; i < n / 2;i++)
    {
        xpath += x[n - i - 1] - x[i]; 
    }
    cout << xpath + ypath;
}

子序列的平均值

题目

给定一个长度为n的非负序列A,请你找出一个长度不小于L的子段,使得子段中数值的平均值最大。

最终输出这个最大的平均值。

思路

题目中的n较大,进行循环时,会超时

所以需要用二分

精度控制在1e-5内,直接取上界处理可以通过
但如果数据刁钻,二分法其实没有办法保证前几位数字与最优解一致

一组数据的平均值一定在该组数据的最大值和最小值之间,那么可以通过二分法获得中间的数字mid,再判断是否有一组长度不小于L的子数组的平均值为mid。

怎么判断子数组的平均值是否是mid,可以先让每个数字减去mid,然后再看是否有一组长度不小于L的子数组的和大于等于0。
如果有,那么说明该子数组的平均值要大于mid,这样一来二分就可以进行下去了,平均值大于mid,那么就要让二分法左边的值为mid,如果没有任何一组长度不小于L的子数组的和大于等于0,那么说明平均值肯定要小于mid,这时候就要让二分法右边的值为mid。

Code

#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 100005;
int n, L;
int v[N];
double sum[N];

int main()
{
	cin >> n >> L;
	double l = 0, r = 0;
	for (int i = 1; i <= n; i++)
	{
		cin >> v[i];
		r = max(r, double(v[i]));
	}
	sum[0] = 0;
	while (r - l > 1e-5)
	{
		double mid = (l + r) / 2;
		for (int i = 1; i <= n; i++)
			sum[i] = sum[i - 1] + v[i] - mid;
		double mins = 1e9;
		bool isExisted = false;
		for (int i = L; i <= n; i++)
		{
			mins = min(mins, sum[i - L]);
			if (sum[i] - mins > 0)
			{
				isExisted = true;
				break;
			}
		}
		if (isExisted)
			l = mid;
		else
			r = mid;
	}
	cout << int(r * 1000) << endl;
	return 0;
}

坐飞机

时间复杂度问题

if (right - left >= 100)
{
    cout << "YES" << endl;
    return;
}

非常重要, 并且放到cin完之后紧接的地方
考虑最坏的情况 如果每次询问的区间都是left = 1,right = n,那每次查询都要暴力操作,复杂度至少是n*q,所以就会超时
36500 / 365=100,如果区间>=100,那么肯定有年龄少于365的

#include <bits/stdc++.h>
using namespace std;

int n, q;
int a[MAX];
vector<int> v;
void solve()
{
    int left, right, i, j;
    cin >> left >> right;

    if (right - left >= 100)
    {
        cout << "YES" << endl;
        return;
    }

    v.clear();
    for (i = left; i <= right; i++)
        v.push_back(a[i]);
    sort(v.begin(), v.end());

    for (i = 0; i < v.size() - 1; i++)
        v[i] = v[i + 1] - v[i];
    v.pop_back();
    sort(v.begin(), v.end());

    for (i = 0; i < v.size(); i++)
    {
        if (v[i] <= 365)
        {
            cout << "YES" << endl;
            return;
        }
    }
    cout << "NO" << endl;
}

signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    cin >> n >> q;
    for (int i = 1; i <= n; i++)
        cin >> a[i];

    while (q--)
        solve();
    return 0;
}

A-B 数对

给出一串正整数数列以及一个正整数 C C C,要求计算出所有满足 A − B = C A - B = C AB=C 的数对的个数(不同位置的数字一样的数对算不同的数对)。
正整数中包含的满足 A − B = C A - B = C AB=C 的数对的个数。

lower_bound / upper_bound

取得a数组中最小数的下标

lower_bound(a,a+n,x)-a      //下标从0开始
lower_bound(a+1,a+n+1,x)-a  //下标从1开始
upper_bound(a,a+n,x)-a      //下标从0开始
upper_bound(a+1,a+n+1,x)-a  //下标从1开始

如果这个数组是有序的,那么对于每一个A的值,在它的后方就只有一个数值B满足,A,B的差值为C。
所以只需要求出数组中每个A, A + C 的两个位置, 它们的差就是数组中值为 A + C的元素个数

for (int i = 1; i <= N; i++)
{
    ans += ((upper_bound(a + 1, a + N + 1, a[i] + C) - a) - (lower_bound(a + 1, a + N + 1, a[i] + C) - a));
}
#include <bits/stdc++.h>
using namespace std;
long a[200001];
long N, C, ans;
int main()
{
    cin >> N >> C;
    for (int i = 1; i <= N; i++)
    {
        cin >> a[i];
    }
    sort(a + 1, a + N + 1);
    for (int i = 1; i <= N; i++)
    {
        ans += ((upper_bound(a + 1, a + N + 1, a[i] + C) - a) - (lower_bound(a + 1, a + N + 1, a[i] + C) - a));
    }
    cout << ans;
    return 0;
}

Quality vs Quantity

给定一个长度为 n n n 的数列 a 1 , a 2 , ⋯   , a n a_1,a_2,\cdots,a_n a1,a2,,an。你可以将数列中的某些元素涂成红色或者蓝色。请判断是否有一个涂色方案,使得被涂成红色的元素的和大于被涂成蓝色的元素的和,且被涂成红色的元素个数小于被涂成蓝色的元素个数。

感悟

正常思路走, 当认为代码写的真的是对的,就是WA的时候,不妨调换一下退出循环的条件
我觉得这里会WA因为if位置不对,到i<j那里就出问题了

void solve()
{
    int n;
    cin >> n;
    for (i = 1; i <= n; i++)
        cin >> a[i];
    sort(a + 1, a + n + 1);

    int Blue = a[1], Red = 0;
    int i = 2, j = n;
    while (i < j)
    {
 
        Blue += a[i];
        Red += a[j];
        i++;
        j--;
        if (Red > Blue)
        {
            cout << "YES" << endl;
            return;
        }
    }
    cout << "NO" << endl;
} 

Snuke the Wizard

从左到右有 N N N 个从 1 1 1 N N N 的正方形,每个正方形上都有一个字符,第 i i i 个正方形上有一个字母 s i s_i si。此外,每个正方形上最初都有一个魔像。

斯努克施放 Q Q Q 个法术移动魔像。

i i i 个咒语由两个字符 t i t_i ti d i d_i di 组成,其中 d i d_i diLR.当 Snuke 施放此咒语时,对于每个具有字符 t i t_i ti 的方块,如果 d i d_i diL,并且如果 d i d_i diR,则移至右侧附近的正方形。

但是,当魔像试图从第 1 个方块左移或从第 N 个方块右移时,它消失了。

找到施努克施放 Q Q Q 咒语后剩余的魔像数量

二分

移出去的一定是一段前缀和一段后缀, 可以二分
可以计算有几个魔像没有到达 0 或 n + 1 方格,由于移动的时候魔像没有跳过另一个魔像
,所以当一个魔像到达0方格并消失时,它左边的魔像也会消失, 同理右侧也是
==> 只需要知道最右边起始位置到达0方块魔像的个数和最左边初始位置到达n+1方块的魔像个数, ==>判断二分的位置是否最终会消失

#include <bits/stdc++.h>
using namespace std;
int n, m, l, r, mid, ansl, ansr;
char a[2333333], b[2333333];
string s;
int check(int x)
{
    for (int i = 1; i <= m; i++)
    {
        if (s[x - 1] == a[i])
        {
            if (b[i] == 'R')
                x++;
            else
                x--;
        }
        if (x > n)
            return -1;
        if (x < 1)
            return 1;
    }
    return 0;
}
int main()
{
    cin >> n >> m >> s;
    for (int i = 1; i <= m; i++)
        cin >> a[i] >> b[i];
    
    l = 1;
    r = n;
    ansr = n + 1;
    while (l <= r)
    {
        mid = (l + r) / 2;
        if (check(mid) == 1)
        {
            ansl = mid;
            l = mid + 1;
        }
        else
            r = mid - 1;
    }
    l = 1;
    r = n;
    while (l <= r)
    {
        mid = (l + r) / 2;
        if (check(mid) == -1)
        {
            ansr = mid;
            r = mid - 1;
        }
        else
            l = mid + 1;
    }
    cout << ansr - ansl - 1 << endl;
    return 0;
}

Snuke Numbers

定义函数 S ( x ) \rm S(x) S(x) 表示 x x x 各个位上数字之和。求前 k k k 个这样的 n n n
∀ m > n , n S ( n ) ≤ m S ( m ) \forall m>n,\frac{n}{\mathrm{S}(n)}\leq \frac{m}{\mathrm{S}(m)} m>n,S(n)nS(m)m
k ≥ 1 k\geq 1 k1

找规律

9的位数应该多一点
后缀为9999…999的形式

#include <iostream>
#define ll long long
using namespace std;

ll Sum(ll x)
{
    ll res = 0;
    while (x)
        res += x % 10, x /= 10;
    return res;
}

int main()
{
    ll n, d = 1, ans = 1;
    cin >> n;

    while (n--)
    {
        cout << ans << endl;
        if ((ans + 10 * d) * Sum(ans + d) < (ans + d) * Sum(ans + 10 * d))
            d *= 10;
        ans += d;
    }
    return 0;
}

Permutation Partitions

给定一个长度为 n n n排列 { a n } \{a_n\} {an}。要求将这个序列分成互不相交的 k k k 段。记第 p p p 段的左端点和右端点分别为 l p , r p l_p,r_p lp,rp 。要求最大化 ∑ i = 1 k max ⁡ j = l i r i { a j } \sum_{i=1}^k\max_{j=l_i}^{r_i}\{a_j\} i=1kmaxj=liri{aj}

输出最大化的值可以最大化该值的方案数。方案数对 998 , 244 , 353 998,244,353 998,244,353 取模。

前k大的数肯定不能在相同区间内, 应该把它们划到不同区间
用数列b记录前k个值的下标

Tips:
不能使用如下一形式
Num *= (b[i] - b[i - 1]) % MOD;
应该老实点, 一步一步来, 用下面的
Num = Num * (b[i] - b[i - 1]) % MOD;

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define MOD 998244353
#define MAX 200010
int i, j = 1, k, T, n;
int b[MAX] = {0}, sum = 0, Num = 1;
struct point
{
    int num, id;
} p[MAX];
bool cm(point a, point b)
{
    if (a.num != b.num)
        return a.num > b.num;
    return a.id < b.id;
}
void solve()
{
    cin >> n >> k;
    for (i = 1; i <= n; i++)
        cin >> p[i].num, p[i].id = i;
    sort(p + 1, p + 1 + n, cm);
    for (i = 1; i <= k; i++)
    {
        sum += p[i].num;
        b[i] = p[i].id;
    }
    sort(b + 1, b + k + 1);
    for (i = 2; i <= k; i++)
        Num = Num * (b[i] - b[i - 1]) % MOD;

    cout << sum << " " << Num << endl;
}
#include <bits/stdc++.h>
using namespace std;
const int MOD = 998244353;
int n, k, a;
int main()
{
    ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
    cin >> n >> k;
    int p = -1;
    int ans = 1,i;
    long long sum = 0;
    for ( i = 0; i < n; i++)
    {
        cin >> a;
        if (a >= n - k + 1)
        {
            if (p != -1)
                ans = 1LL * ans * (i - p) % MOD;
            sum += a;
            p = i;
        }
    }
    cout << sum << " " << ans << endl;
    return 0;
}

采蜜

春天到了,群蜂公司(SBC)迎来了辛勤工作的季节。随着花园里 N N N 朵美丽花朵的绽放,每朵花都拥有一定数量的花粉粒。为了让蜜蜂勤劳地采集花粉,SBC 执行了严格的规定。

  1. 第一条规则与采集花粉粒的数量有关:当蜜蜂访问一朵花时,它必须采集其当前花粉数量的数字之和。例如,如果蜜蜂光顾一朵有 123 123 123 粒花粉的花,它必须收集 1 + 2 + 3 = 6 1 + 2 + 3 = 6 1+2+3=6 粒花粉,使这朵花剩下 123 − 6 = 117 123 - 6 = 117 1236=117 粒花粉。同样,如果花朵有 201 201 201 粒花粉,蜜蜂就必须收集 2 + 0 + 1 = 3 2 + 0 + 1 = 3 2+0+1=3 粒花粉,剩下 198 198 198 粒花粉。
    1. 一天开始时,所有蜜蜂必须排成一队;排在最前面的蜜蜂必须从花粉量最大的一朵花上采集花粉。如果一只蜜蜂去一朵有 0 0 0 粒花粉的花朵,它采集的花粉量为零。从一朵花上采集完花粉后,蜜蜂就结束工作返回蜂巢。

格特鲁德对这些规则感到困惑,于是寻求帮助,以确定轮到自己时必须采集的花粉量。格特鲁德的视力非常敏锐,她注意到自己目前是 SBC 定义顺序中的第 K K K 只蜜蜂。

给定整数n和k,以及n个整数x1、x2、…、xn。现在需要从这n个整数中选取k个整数,使得它们的各位数字之和最大。

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
#define MAX 20000005
int i, j, k, n;
int a[MAX], b[MAX], maxB = -1;

int sum(int n)
{
    int res = 0;
    while (n)
    {
        res += n % 10;
        n /= 10;
    }
    return res;
}
void solve()
{
    cin >> n >> k;

    for (i = 1; i <= n; i++)
        cin >> a[i], b[a[i]]++, maxB = fmax(maxB, a[i]);

    for (i = maxB; i >= 1; i--)
    {
        b[i - sum(i)] += b[i];
        k -= b[i];
        if (k <= 0)
        {
            cout << sum(i);
            return;
        }
    }
    cout << 0 << endl;
}

二进制

给一个长度为N的二进制数组。每个数字要么是0,要么是1。算出来这串数字的所有包含奇数个1的子序列的个数是多少.
子数组由其起始和结束数字来定义。例如,在序列[b1, b2, b3J中,子数组包括[b1],[b2],[b3],[b1, b2], [b2, b3] 和 [b1, b2, b3]

计数dp

#include <bits/stdc++.h>
using namespace std;
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    int n, i, x;
    cin >> n;
    int sum = 0;
    long long a[2] = {1, 0};
    long long ans = 0;
    while (n--)
    {
        cin >> x;
        sum += x;
        ans += a[sum & 1 ^ 1];
        // sum 为奇数是, sum & 1 ^ 1 = 1
        // [sum & 1 ^ 1]的值要么是a[0],要么是a[1]
        a[sum & 1]++;
    }
    cout << ans << "\n";
    return 0;
}

美妙魔法

通过一段时间的学习,ArcticBa 掌握了一种魔法,这种魔法能够将一个只包含小写字母的字符串 𝑆 的内部元素进行互换。
ArcticBa只能在给定整数 𝐾的前提下,选择字符串内任意一个𝑆𝑖和 𝑆𝑖+𝐾进行互换。你找出在对字符串进行任意次数这种互换后,字典序最小的字符串。

#include <bits/stdc++.h>
using namespace std;
vector<char> a[100005];
int cnt[100005];
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    string s;
    cin >> s;
    int K;
    cin >> K;
    int len = s.size();
    for (int i = 0; i < len; i++)
    {
        a[i % K].emplace_back(s[i]);
    }
    for (int i = 0; i < K; i++)
    {
        sort(begin(a[i]), end(a[i]));
    }
    for (int i = 0; i < len; i++)
    {
        cout << a[i % K][cnt[i % K]++];
    }
    return 0;
}
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
 
int main(){
    string str, str2;
    ll k;
    cin >> str >> k;
    vector<string> arr (k);
    //ll size = str.size() -1;
    for(int i = 0; i < k; i++) {
        for(int j = 0; j < str.size(); j += k){
            arr[i] += str[j +i];
        }
        sort(arr[i].begin(), arr[i].end());
        while(arr[i][0] == NULL){
            arr[i] = arr[i].substr(1);
        }
    }
    for(int i = 0; i < str.size(); i++) {
        int v = floor( (i)/k);
        int r = i%k;
        str2 += arr[r][v];
        //cout << r << " " << v << " " << arr[r][v] << endl;
    } 
    cout << str2 << endl;
}

航班价格

第一行输入包含一个整数 N N N ( 1 ≤ N ≤ 100 ) (1 \le N \le 100) (1N100) ( 1 ≤ N ≤ 100 ) (1 \le N \le 100) (1N100) ,Quadradonia 有航班服务的城市数量。接下来还有 N N N 行, L 1 , L 2 , … , L N L_1, L_2, \dots, L_N L1,L2,,LN 。第 L i L_i Li 行包含 N N N 个整数, C i 1 , C i 2 , … , C i N C_{i1}, C_{i2}, \dots, C_{iN} Ci1,Ci2,,CiN ,其中 C i j C_{ij} Cij 是城市 i i i j j j 之间直飞航班的费用。

两个城市之间的去程航班和回程航班的成本总是相同的,即 i , j {i,j} i,j 1 ≤ i ≤ N 1 \le i \le N 1iN 1 ≤ j ≤ N 1 \le j \le N 1jN 之间所有航班对的 C i j = C j i C_{ij} = C_{ji} Cij=Cji 。当 i = j i=j i=j C i j = 0 C_{ij} = 0 Cij=0 时。当 i ≠ j i \ne j i=j 1 ≤ C i j ≤ 1 0 3 1 \le C_{ij} \le 10^3 1Cij103 时。
验证表格的一致性,如果是一致的,则告知国王在不增加任何行程成本的情况下可以取消多少直飞航班。

Floyed

#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
int a[105][105];
int f[105][105];
int ff[105][105];
int main()
{
    int n, i, j, k;
    cin >> n;
    for (i = 1; i <= n; i++)
    {
        for (j = 1; j <= n; j++)
        {
            cin >> a[i][j];
            f[i][j] = a[i][j];
            ff[i][j] = INF;
        }
    }
    for (k = 1; k <= n; k++)
        for (i = 1; i <= n; i++)
            for (j = 1; j <= n; j++)
                f[i][j] = fmin(f[i][j], f[i][k] + f[k][j]);

    for (i = 1; i <= n; i++)
    {
        for (j = 1; j <= n; j++)
        {
            if (a[i][j] > f[i][j])
            {
                cout << -1;
                return 0;
            }
        }
    }
    for (i = 1; i <= n; i++)
        for (j = 1; j <= n; j++)
            for (k = 1; k <= n; k++)
            {
                if (k == i || k == j)
                    continue;
                ff[i][j] = fmin(ff[i][j], a[i][k] + a[k][j]);
            }

    for (k = 1; k <= n; k++)
        for (i = 1; i <= n; i++)
            for (j = 1; j <= n; j++)
                ff[i][j] = fmin(ff[i][j], ff[i][k] + ff[k][j]);

    int ans = 0;
    for (i = 1; i <= n; i++)
        for (j = i + 1; j <= n; j++)
            if (ff[i][j] == a[i][j])
                ans++;

    cout << ans << endl;
    return 0;
}

关鸡

在一条宽为 2 长为 2 * 10 ^ 9 + 1 的管道中,有一只鸡和若干着火点,鸡可以上下左右移动一格、不能出管道上下边界、不能进入着火地点。

鸡初始在 处,现在给出若干个着火点的坐标,请求出为了不让鸡逃出管道(即到达管道最左端或最右端),最少需要添加多少个着火点。

set <pair <int, int>> 或 map

#include <bits/stdc++.h>
using namespace std;

const int N = 1e5 + 10;

struct Coordinate
{
    // r代表纵坐标,c代表横坐标
    int r, c;
} s[N];
// 根据横坐标大小从小到大排序
bool cmp(Coordinate A, Coordinate B) 
{ 
    return A.c < B.c; 
}

void solve()
{
    int maxn = 3, n, i;
    cin >> n;
    for (i = 1; i <= n; i++)
        cin >> s[i].r >> s[i].c;
    sort(s + 1, s + n + 1, cmp); 
    int flag_0 = 2, flag_1 = 2;
    
    for (i = 1; i < n; i++)
    {
        auto [r1, c1] = s[i];
        auto [r2, c2] = s[i + 1];
        if (c1 == c2) // 如果横坐标相等
        {
            // 如果是左边的路被封锁,则左边的无需添加着火点。
            if (c1 < 0)
                flag_0 = 0;
                
            // 如果是右边的路被封锁,则右边的无需添加着火点。
            if (c2 > 0)
                flag_1 = 0;
        }
        if (c1 + 1 == c2)
            if (r1 != r2) // 如果相邻
            {
                // 如果是左边的路被封锁,则左边的无需添加着火点。
                if (c1 < 0)
                    flag_0 = 0;
                // 如果是右边的路被封锁,则右边的无需添加着火点。
                else
                    flag_1 = 0;
            }
    }
    for (i = 1; i <= n; i++)
    {
        auto [r1, c1] = s[i];

        // 如果为鸡的初始坐标下面
        if (r1 == 2 && c1 == 0)
            maxn--;

        // 如果为鸡的初始坐标右边
        if (r1 == 1 && c1 == 1)
            maxn--;

        // 如果为鸡的初始坐标左边
        if (r1 == 1 && c1 == -1)
            maxn--;

        // 如果左边没有被封锁,且有一个着火点
        if (flag_0 > 0 && c1 <= 0)
            flag_0 = 1;

        // 如果右边没有被封锁,且有一个着火点
        if (flag_1 > 0 && c1 >= 0)
            flag_1 = 1;
    }
    // 两种关住鸡的方法的所需最小数量的对比,选小的
    cout << min(maxn, flag_0 + flag_1) << endl;
}

int main()
{
    int t, cin >> t;
    while (t--)
        solve();
    return 0;
}

本题又主要考察了贪心

n个人参加比赛,当前第i个人已经得到了a i分,接下来还有m轮比赛 每轮两个人PK,赢的人+3分;平局则每人+1分 给定m轮比赛的名单,问1号选手能取得的最高名次

DFS

每组用例的比赛局数m都很小, 直接dfs枚举每一局的结果
当看到数据范围是dfs可过, 这个dfs还不难写 ===> dfs启动

vector<pll> v; // 记录每轮比赛的参赛选手
ll n, m, ans;

void DFS(vector<ll> a, ll i)
{
    if (i == m)
    {
        ll cnt = 0;
        for (auto x : a)
            if (x > a[0])
                cnt++;
        ans = min(ans, cnt);
        return;
    }
    vector<ll> aa = a, ab = a, ac = a;
    aa[v[i].first - 1] += 3; // a赢
    DFS(aa, i + 1);
    ab[v[i].second - 1] += 3; // b赢
    DFS(ab, i + 1);
    ac[v[i].first - 1] += 1; // 平局
    ac[v[i].second - 1] += 1;
    DFS(ac, i + 1);
}
void solve()
{
    cin >> n >> m;
    ans = INF;
    vector<ll> a(n);
    for (auto &x : a)
        cin >> x;
    v.clear();
    ll u, v;
    FORLL(i, 1, m)
    {
        cin >> u >> v;
        if (u > v)
            swap(u, v);
        v.pb({u, v});
    }
    DFS(a, 0);
    cout << ans + 1 << endl;
}

买外卖

有n张外卖券,第i张满ai减bi,所有券可以叠加使用,手上有m元,最多可以购买到原价为多少的餐品

前缀

void solve()
{
    int n, m, i;
    cin >> n >> m;
    vector<pair<int, int>> a(n);
    for (i = 0; i < n; i++)
        cin >> a[i].first >> a[i].second;
    sort(begin(a), end(a));

    vector<long long> sum(n + 1);
    for (i = 0; i < n; i++)
        sum[i + 1] = sum[i] + a[i].second;

    long long ans = m;
    for (i = 0; i < n; i++)
    {
        if (a[i].first - sum[i + 1] <= m)
            ans = sum[i + 1] + m;
    }
    cout << ans << endl;
}

鸡数题

n个不同的小球放在m个相同的箱子里,求方案数

第二类斯特林数

把n个不同的数划分为m个集合的方案数(S(n, m), 与第一类不同, 划分集合不需要考虑排列次序)

递推关系
S(n - 1, m - 1) 将n - 1个不同元素划分为m - 1个集合, 则第n个元素必须单独放入第m个集合
S(n - 1, m) * m 将 n - 1个不同元素已经划分为m个集合, 则第n个元素可以放在m个集合中任意一个里面

得关系式 S(n, m) = S(n - 1, m - 1) + S(n - 1, m) * m

S ( 0 , 0 ) S(0, 0) S(0,0) = 1
S ( n , 0 ) S(n, 0) S(n,0) = 0
S ( n , 1 ) S(n, 1) S(n,1) = 1
S ( n , n ) S(n, n) S(n,n) = 1
S ( n , 2 ) S(n, 2) S(n,2) = 2 ^(n - 1) - 1
S ( n , n − 1 ) S(n, n - 1) S(n,n1) = n * (n - 1) / 2

分析

m个数按位或结果是2 ^ n - 1 ==> 右边前n位都至少有一个数该为为1
任意两个数按位与等于0 ==> 任意两个数都没有任意一位相同, 右侧前n位每一位有且只有一个数该位为1
m个数升序 ==> 只能升序排列
m个数都不为0 ==> 每个数都至少有一个数位为1

#include <bits/stdc++.h>
using namespace std;
const int P = 1000000007;

long long *fac, *ifac;

long long power(long long a, long long b, int p = P)
{
    long long res = 1;
    for (; b; b >>= 1, a = a * a % p)
        if (b & 1)
            res = res * a % p;

    return res;
}

long long inv(long long x)
{
    return power(x, P - 2);
}

void init(int N)
{
    int i;
    fac = new long long[N + 1];
    ifac = new long long[N + 1];
    fac[0] = 1;
    for ( i = 1; i <= N; i++)
        fac[i] = fac[i - 1] * i % P;
    
    ifac[N] = inv(fac[N]);
    for ( i = N - 1; i >= 0; i--)
        ifac[i] = ifac[i + 1] * (i + 1) % P;
    
}

long long C(int n, int m)
{
    if (m < 0 || m > n || n < 0)
    {
        return 0;
    }
    return fac[n] * ifac[m] % P * ifac[n - m] % P;
}

int main()
{
    int n, m, k;
    cin >> n >> m;

    init(m);

    long long ans = 0;
    for ( k = 0; k <= m; k++)
    {
        if (k % 2 == 0)
            ans = (ans + C(m, k) * power(m - k, n) % P) % P;
        else
            ans = (ans - C(m, k) * power(m - k, n) % P + P) % P;
        
    }
    cout << ans * ifac[m] % P << '\n';

}

It’s bertrand paradox. Again!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第一种方法x,y是均匀的,但第二种方法,当x,y靠近边界时,更有可能被舍掉,因此越靠近边界密度越低。因此可以统计一下落在某个区间里的点数,如果和均匀分布的期望相差超过一定值,就可以判定是第二种,否则是第一种。

根据抽样分布原理,大量独立同分布随机变量和的极限分布是正态分布。 因此对样本建立统计量,使得两种算法下,该统计量有显著不同即可。

统计量选择圆心到原点的距离均值 A A A( x 2 + y 2 \sqrt{x ^ 2 + y ^ 2} x2+y2 )
生成样本得到 U b u a a _{buaa} buaa ≈ \approx 56, U b i t _{bit} bit ≈ \approx 75

void solve()
{
    ll n;
    cin >> n;
    double sum = 0;
    ll x, y;
    FORLL(i, 1, n)
    {
        cin >> x >> y;
        sum += sqrt(x * x + y * y);
        cin >> x;
    }
    sum /= n;
    if (sum < 65)
        cout << "buaa-noob" << endl;
    else
        cout << "bit-noob" << endl;
}

要有光

给了一些几何形状的方程,求在地面(z=0)上的最大阴影面积

当光源高度低于2ℎ时,可以发现在俯视图中,绿墙对应的线段总是阴影三角形的中位线,如下图所示,此时阴影部分即图中等腰矩形面积计算可得3𝑐𝑤;
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
当光源高度高于2ℎ时,俯视图如下图所示,此时阴影没有打到墙上,阴影部分是图中蓝色和绿色所组成的等腰梯形,由于蓝色线段小于2*绿色线段长,所以此时等腰梯形面积一定小于3𝑐𝑤;
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

总把光源贴地放做到3𝑐𝑤就是最优的

求一个 X O Y XOY XOY平面上梯形阴影的面积. 上底 2 w 2w 2w, 下底 4 w 4w 4w, 高 c c c ==> 面积为 3 c w 3cw 3cw

重铸璃月

给定一个长度为 n 的序列,问是否存在一个该序列的子序列使得该子序列的元素按位或的结果等于 k
子序列的定义:对于一个数组,在保持其元素顺序不变的情况下,删去任意个数的元素,得到的为该数组的子序列。

a i _i i | k == k

若k的某一位为0, 则选取的子序列中不能包含该位为1的元素 ==> a i _i i | k == k
或运算只会让1变多, 把所有的元素都 | ,看最后的结果是不是 k

#include <bits/stdc++.h>
using namespace std;
signed main()
{
    int n, k, x, now = 0;
    cin >> n >> k;
    while (n--)
    {
        cin >> x;
        if ((x | k) == k)
            now |= x;
    }
    cout << (now == k ? "YES" : "NO");
}

稻妻夏日祭

这里有n个地区存放烟花,初始时每个地区的烟花个数已知,现在告诉你每个地区需要烟花数的上限R和下限L,每次我们可以将某个地区中选出一个烟花移到另一个地区中,问至少需要移动多少次才能使每个地区的烟花数都在 [L, R] 別中。如果多少次都无法成功,输出-1

直接模拟, 注意数组开的不能太大, 会RuntimeError

移动次数最少的方法就是用超出的烟花数量去补缺少的烟花数量,使两者都清零。

如果两者不相等,要取较大的数才能保证两者清零。

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define endl "\n"
#define MAX 2000005

int a[MAX], sum[MAX];
void solve()
{
    int i, j, n, l, r, ans = 0;
    cin >> n;
    for (i = 1; i <= n; i++)
    {
        cin >> a[i];
        sum[i] = sum[i - 1] + a[i];
    }
    cin >> l >> r;
    if (sum[n] > r * n || sum[n] < l * n)
    {
        cout << -1 << endl;
        return;
    }
    int left = 0, right = 0;
    for (i = 1; i <= n; i++)
    {
        if (a[i] > r)
            right += (a[i] - r);
        else if (a[i] < l)
            left += (l - a[i]);
    }

    cout << fmax(left, right) << endl;
}

雷电将军的烦恼

雷电将军在 个餐厅点了料理,现在准备去取料理,对于取第i个餐厅的料理,下列有两种选择:
•让此餐厅的快递员把料理送到家里,花费Qi分钟。
•自己去餐厅取料理然后返回到家,来回总共花费6;分钟。
现在雷电将军想知道在家吃到所有料理的最少分钟数。
注意:所有快递员都是并行工作的。且雷电将军无法同一时间内去取多家餐厅的料理。

枚举

首先按花费快递时间排序,然后枚举一段前缀 i i i, 此时快递总时间为 a i a_i ai ,所以后缀和 ∑ j = i + 1 n b j \sum^n_{j = i + 1} b_j j=i+1nbj a i a_i ai取最大值更新答案即可。

#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main()
{
    int n, i, sum = 0;
    cin >> n;
    vector<pair<int, int>> a(n);
    for (i = 0; i < n; i++)
        cin >> a[i].first;
    
    for (i = 0; i < n; i++)
        cin >> a[i].second, sum += a[i].second;

    sort(a.begin(), a.end());
        
    int res = 0x3f3f3f3f, s = 0;
    for (i = 0; i < n; i++)
    {
        s += a[i].second;
        res = min(res, max(a[i].first, sum - s));
    }
    cout << min({res, sum, a[n - 1].first}) << endl;
}

182376

有一个长度为 N N N 的数组,数组中的每个元素由 0∼9之间的数字组成,现在需要你求出最短的子数组长度,使得该子数组中包含形如 [1,8,2,3,7,6] 的子序列,不存在则回答 no
子数组的定义:指在一个数组中,选择一些连续的元素组成的新数组。

子序列的定义:对于一个数组,在保持其元素顺序不变的情况下,删去任意个数的元素,得到的为该数组的子序列。

即子数组必须连续,子序列可以不连续。

用辅助数组

满足要求的子数组一定是以 1 为起点的,因此遍历时每次遇到1,依次找出距离最近的 8 2 3 7 6,即可找到以该1 为起点,满足要求的最短子数组

int a[N], ne[N], la[10];
void solve()
{
    int n, i;
    cin >> n;
    memset(la, -1, sizeof la);
    for (i = 1; i <= n; i++)
        cin >> a[i];

    int to[10] = {0, 8, 3, 7, 0, 0, 0, 6, 2, 0};
    for (i = n; i >= 1; i--)
    {
        int x = a[i];
        ne[i] = la[to[x]];
        la[x] = i;
    }
    // ne[i] 记录了数列中以数字1开头的子序列中,从第i个位置开始,下一个出现数字1的位置。如果不存在下一个数字1,即在数列的末尾时,ne[i] 被初始化为 -1
    // la[i]记录了数字i在数列中最后一次出现的位置。用于帮助在遍历中寻找以1开头的子序列中的数字6

    int ans = inf;
    for (i = 1; i <= n; i++)
    {
        if (a[i] == 1)
        {
            int j = i;
            while (j != -1 && a[j] != 6)
                j = ne[j];

            if (j != -1)
                ans = min(ans, j - i + 1);

        }
    }

    if (ans != inf)
        cout << ans << endl;
    else
        cout << "no" << endl;
}

水的女儿

将最大的前 k k k个免费拿去,后面依次取 b b b中最高未使用的优惠券即可。

#include <bits/stdc++.h>
#define int long long
using namespace std;
signed main()
{
    int n, m, k, i;
    cin >> n >> m >> k;
    vector<int> a(n), b(m);
    for (i = 0; i < n; i++)
        cin >> a[i];

    for (i = 0; i < m; i++)
        cin >> b[i];

    b.resize(max(n, m));
    sort(a.begin(), a.end(), greater<int>());
    sort(b.begin(), b.end(), greater<int>());
    int res = 0;
    for (i = k, j = 0; i < n; i++, j++)
        res += max(0LL, a[i] - b[j]);

    cout << res << endl;
}

那维莱特

那维莱特有 n枚源水之滴,第 枚源水之滴的水元素伤害为ai,如果两枚源水之滴i, j(i < j)的水元素伤害满足 ai mod aj=0),那么这两枚源水之滴就可以合并变成新的源水之滴且
水元素伤害为 a = ai +aj,同时第j枚源水之滴a;也会消失。
那维莱特需要汇聚一枚源水之滴使得水元素伤害尽可能大。

枚举

考虑值域为 10 5 ^5 5,由于合并有顺序要求,所以我们枚举每一枚源水之滴,然后扫一遍值域检查是否可达。

#include <bits/stdc++.h>
using namespace std;
#define pb push_back
const int N = 1e5 + 10;
bool f[2][N], i, j.k;
int main()
{
    int n, m = 1000 * 100; // m 值域
    f[0][0] = 1;
    cin >> n;
    for (i = 1; i <= n; i++)
    {
        cin >> k;
        for (j = 0; j <= m; j++)
            f[i % 2][j] = 0;
        for ( j = 0; j <= m; j++)
        {
            if (f[(i + 1) % 2][j])
            {
                if (j % k == 0)
                    f[i % 2][j + k] = 1;
            }
        }
        f[i % 2][k] = 1;
        for ( j = 0; j <= m; j++)
            if (f[(i + 1) % 2][j])
                f[i % 2][j] = 1;
    }
    for ( j = m; j >= 0; j--)
        if (f[n % 2][j])
        {
            cout << j << endl;
            return 0;
        }
}

消除

Tokitsukaze 正在玩一个消除游戏。初始有n 个宝石从左到右排成一排,第之个宝石的颜色为 coli。 Tokitsukaze 可以进行若干次以下操作:
• 任选一种颜色 2,将颜色为 2的最右边那颗宝石、以及该宝石右边的所有宝石全部消除。
Tokitsukaze 想知道至少需要几次操作才能把 n个宝石全部消除。

模拟

从后遍历, 注意前面几个一样的特殊情况
count 记录剩下来几个都是相同的个数

void solve()
{
    cin >> n;
    for (i = 1; i <= n; i++)
        cin >> a[i];
    int pre = a[n], ans = 0, count = 0;
    for (i = n; i >= 1; i--)
    {
        if (a[i] != pre)
        {
            ans++;
            count = 0;
            pre = a[i - 1];
        }
        else
            count++;
    }
    cout << ans + count<< endl;
}

以下为hard版本(数量级变大)
只需要维护每种颜色的最后一个宝石出现的位置,每次操作选择位置最靠前的那种颜色,这样一次就能消除尽可能多的宝石
第一次我们先遍历n种颜色,找到最靠前的下标
接着从后往前遍历宝石,遍历到的宝石一定在对应颜色vector的最末尾,消除它,就是把它从 vector 中 p o p b a c k pop_back popback
在这同时维护一个变量,表示此次遍历的宝石所对应的颜色最后一个宝石出现的位置。然后用这个变量进行下一次消除,直到所有宝石都被消除

// 用来记录每种颜色的坐标
vector<int> pos[N];
void solve()
{
    int n, ans, i;
    cin >> n;
   
    // 清除pos[i], 避免上一轮的数据干扰
    for (i = 1; i <= n; i++)
        cin >> a[i], pos[i].clear();
    
    // 把每组颜色的坐标存储起来
    for (i = 1; i <= n; i++)
        pos[a[i]].push_back(i);
    
    // 初始化maxn, maxn代表颜色中最大坐标的最小值
    int maxn = n;
    
    // 统计需要选择是数量
    ans = 0;
    
    // 找到所有颜色中最大坐标的最小值,并存储于maxn中
    for (i = 1; i <= n; i++)
        if ((int)pos[i].size())
            maxn = min(maxn, pos[i].back());
   
    // now用来表示目前的颜色中的最大坐标的最小值
    // pre表示上一次的最大坐标的最小值
    int now = maxn, pre = n;
    while (pre >= 1)
    {
        ans++;
        maxn = now;
        for (i = pre; i >= now; i--)
        {
            // 把坐标比i大的都砍掉
            pos[a[i]].pop_back();
            // 寻找下一个所有颜色当中最大坐标的最小值
            if ((int)pos[a[i]].size())
                maxn = min(maxn, pos[a[i]].back());
        }

        // 更新pre和now的值
        pre = now - 1;
        now = maxn;
    }
    cout << ans << '\n';
}

最短路径plus

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

void solve()
{
    int n, i;
    cin >> n;
    for (i = 0; i < n; i++)
        cin >> a[i];
    sort(a, a + n);
    int ans = 0;
    // 点权的位次也是这个点权对答案贡献的次数。
    for (i = 0; i < n; i++)
        ans += a[i] * i;
    cout << ans * 4 << endl;
    // *4的含义:边权有系数2;每两个点互相到达要计算2次
}

最短路径minus

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传
不同于plus, 此题可以绕路, 但最多只能多走一个节点
a [ i ] a[i] a[i] > 2 * a [ i ] a[i] a[i], 就应该绕路了

void solve()
{
    ll sum = 0, i;
    cin >> n;
    for (i = 1; i <= n; i++)
        cin >> a[i];
    sort(a + 1, a + n + 1);

    for (i = 2; i < n; i++)
        if (a[i] > 2 * a[1])
            a[i] = 2 * a[1];

    for (i = 1; i < n; i++)
        sum += a[i] * 2 * (n - i);

    cout << sum * 2 << endl;
}

数字手串

有一串首尾相连的数字,两个玩家轮流操作
当且仅当相邻2个数字之和为偶数时,可以消除其中一个, 然后可以交换剩下的数字中任意两个数字的位置(也可以不交换) 特别的,如果只有1个数字,可以直接消除。 最先无法操作的玩家输。
问对于给定的数字串,qcjj(先手)和zn(后手)谁会赢。

直接判断原始数字串长度的奇偶性

游戏结束时, 手串上数字排布为: 奇偶奇偶…奇偶, 数量为偶数
数量为奇数时, 一定存在两个相邻的数, 它们的和为偶数

void solve()
{
    ll n, i;
    cin >> n;
    for (i = 0; i < n; i++)
        cin >> a[i];
    if (n % 2 == 1)
        cout << "qcjj" << endl;
    else
        cout << "zn" << endl;
}

36倍数

给定 n n n个10 1 8 {^18} 18范围内的非负整数。
求在其中取不同的2个元素,拼接起来后能被36整除的方案数

枚举

a 和 b 拼接, 拼接后的数为 c = a ∗ 1 0 k + b c = a * 10^k + b c=a10k+b, 其中k 为b的位数

$ c % 36 = $

void solve()
{
    int i, j, n, x, ans = 0;
    cin >> n;
    for (i = 1; i <= n; i++)
        cin >> a[i], num[a[i] % 36]++;
    for (i = 1; i <= n; i++)
    {
        int temp = a[i], count = 1;
        while (temp)
        {
            count *= 10;
            temp /= 10;
        }
        for (x = 0; x <= 35; x++)
        {
            if ((x * (count % 36) % 36 + a[i] % 36) % 36 == 0)
                ans += num[x] - (a[i] % 36 == x);
        }
    }
    cout << ans << endl;
}

冒泡排序和最长子段和

给定一个长度为 n n n的数组, 求恰好执行“交换任意相邻元素”操作k次后,数组的最大非空子段和。

k ∈ [ 0 , 1 ] \in[0, 1] [0,1] 暴力枚举

最大非空子段和 ==> 贪心
c u r cur cur为 包含当前位置元素的最大子段和
a 2 a_2 a2开始遍历数组, 则当前最大子段和有2种情况

  1. 将当前位置的元素加入到最大子段和, c u r + a i cur + a_i cur+ai
  2. 当前位置的元素为起点, 重新开始计算最大字段和, 值为 a i a_i ai
    c u r cur cur取这2种情况的较大值, 每个位置 c u r cur cur的最大值即为最大非空子段和
void solve()
{
    int i, j, n, k;
    cin >> n >> k;
    for (i = 1; i <= n; i++)
        cin >> a[i];
    for (i = 1; i <= n - 1; i++)
    {
        if (k == 1)
            swap(a[i], a[i + 1]);
        for (j = 1; j <= n; j++)
        {
            sum[j] = max(sum[j - 1] + a[j], a[j]);
            ans = max(ans, sum[j]);
        }
        if (k == 1)
            swap(a[i], a[i + 1]);
    }
    cout << ans << endl;
}

看到数据范围小就直接暴力, 不考虑算法

比较函数

给定3个数字的n对关系x y z
z = 1 z = 1 z=1 表示 x < y; z = 0 z = 0 z=0表示 x ≥ y
问这n对关系是否存在逻辑矛盾

n <=2 直接暴力枚举

守恒

阿宁有一个长度为 n 正整数数组a。可以进行任意次操作,每次操作选择数组a 的两个元素,其中一个加 1,另一个减 1,要求每次操作后a 的各元素仍然是正整数。
阿宁想知道操作结束后,数组的最大公约数可能有多少种不同的值?
数组的最大公约数指,该数组的所有数共有约数中最大的那个数。
例如数组|20,12,16,共有的约数有1,2,4,最大的数是4,因此[20,12,16] 的最大公约数是4。

最大公约数

+1 -1 操作后, 总和不变
从最终情况考虑。假设数组的 gcd 是 g g g。那么数组里面至少有一个数是 g,并且最小是 g 。其它数如果不是 g,那就是 g 的倍数。

数组求和后考虑分成n个数, 使gcd(a) = 最小的数, 对sum根号下分解因子, 记因子≥n的个数
一个数能是这个数组的最大公因数:

  1. sum是g的倍数。因为所有数都是g的倍数。
  2. sum / g ≥ n。如果 < ,那么最小就不是 g。
void solve()
{
    cin >> n;
    int sum = 0, i;
    for (i = 1; i <= n; i++)
    {
        int x;
        cin >> x;
        sum += x;
    }
    if (n == 1)
    {
        cout << 1 << endl;
        return;
    }
    int ans = 0;
    int d = sum / n;
    for (i = 1; i <= d; i++)
        if (sum % i == 0)
            ans++;
    cout << ans << endl;
}

漂亮数组

给定一个长度为n的整数数组a和一个正整数k。可以将a划分成任意个非空子串
如果子串和能被k整除,则称这个子串是漂亮的
求使得漂亮子串最多的方案 得到的漂亮子串的个数

贪心 + set

问最多能分几个, 那从左到右的时候, 就是能分就分
如果当前位置的前缀和余数 p r e i pre_i prei在上个割点之后的位置j有出现过, 即 p r e i = = p r e j pre_i == pre_j prei==prej, 则子串 [j, i]区间和能被k整除
记录每个前缀和对的余数在上个割点之后是否出现过,贪心的求出最多的漂亮子串个数

void solve()
{
    int i, j, n, k;
    cin >> n >> k;
    for (i = 1; i <= n; i++)
        cin >> a[i], sum[i] = sum[i - 1] + a[i];

    int ans = 0;
    set<int> s;
    for (i = 1; i <= n; i++)
    {
        s.insert(sum[i - 1] % k);

        //当前前缀和对k的余数在上个割点之后出现过
        if (s.count(sum[i] % k))
        {
            s.clear();
            s.insert(sum[i - 1]);
            ans++;
        }
    }

    cout << ans;
}

25 万吨 TNT

亚历克斯正在参与拍摄布尔马斯特的另一个视频,布尔马斯特让亚历克斯准备25万吨TNT炸药,但亚历克斯没有听清楚,于是他准备了 n n n 个箱子,并把它们摆成一排等待卡车。左边的 i i i 个箱子重 a i a_i ai 吨。

亚历克斯要使用的所有卡车都装有相同数量的箱子,用 k k k 表示。装载过程如下:

  • 第一个 k k k 个箱子装到第一辆卡车上、
  • 第二个 k k k 箱子装到第二辆卡车上、
  • ⋯ \dotsb
  • 最后 k k k 个箱子装到第 n k \frac{n}{k} kn 辆卡车上。

装载完成后,每辆卡车上必须有 k k k 个箱子。换句话说,如果在某一时刻无法将 k k k 个箱子准确地装入卡车,那么 k k k 个箱子的装载选项就无法实现。

亚历克斯讨厌公正,所以他希望两辆卡车总重量的最大绝对值差越大越好。如果只有一辆卡车,这个值就是 0 0 0

亚历克斯有很多关系,所以每 1 ≤ k ≤ n 1 \leq k \leq n 1kn ,他都能找到一家公司,使其每辆卡车正好能装载 k k k 个箱子。打印任意两辆卡车总重量的最大绝对差值。

前缀和

INF大小注意一下

void solve()
{
    cin >> n;
    for (i = 1; i <= n; i++)
        cin >> t, sum[i] = sum[i - 1] + t;
    int ans = -INF; 
    for (i = 1; i <= n; i++)
    {
        if (n % i == 0)
        {
            int Max = -INF;
            int Min = INF;

            for (j = i; j <= n; j += i)
            {
                int temp = sum[j] - sum[j - i];
                Max = max(Max, temp);
                Min = min(Min, temp);
            }
            ans = max(ans, Max - Min);
        }
    }
    // if (ans == 2938890433)
    //     ans = 0;
    cout << ans << endl;
}

Laura and Operations

可以删除 2 个不同的数字再加上 1 个与删除的不同的数字,进行若干次操作,能否剩下一种数字

数学

先看最后全是1的情况,如果其他两数的数量和是偶数,就可以一直把其他两数给减掉,所以可以留下 1,但不是偶数就不可以全部消除,就算消到最少也会留下一个非 1的数

void solve()
{
    cin >> a >> b >> c;

    if ((b + c) % 2 == 0)
        cout << "1 ";
    else
        cout << "0 ";

    if ((a + c) % 2 == 0)
        cout << "1 ";
    else
        cout << "0 ";

    if ((a + b) % 2 == 0)
        cout << "1";
    else
        cout << "0";

    cout << endl;
}

爱恨的纠葛

给定2个长度为n的数组a, b
调整a数组使得 ∣ a i − b i ∣ |a_i - b_i| aibi最小値最小

upper_bound

void solve()
{
    int n, t, i;
    cin >> n;

    for (i = 1; i <= n; i++)
        cin >> a[i];
    for (i = 1; i <= n; i++)
        cin >> b[i];
    sort(a + 1, a + 1 + n);
    int Min = INF, ida = 0, idb = 0;

    for (i = 1; i <= n; i++)
    {
        int *it = upper_bound(a + 1, a + 2 + n, b[i]);
        if (it != a + 1 + n)
        {
            t = abs(b[i] - *it);
            if (t < Min)
            {
                Min = t;
                ida = i;
                idb = it - a;
            }
        }
        if (it != a + 1)
        {
            it--;
            t = abs(b[i] - *it);
            if (t < Min)
            {
                Min = t;
                ida = i;
                idb = it - a;
            }
        }
    }
    swap(a[ida], a[idb]);
    for (i = 1; i <= n; i++)
        cout << a[i] << " ";
}

心绪的解剖

给定一个数x,问是否可以表示为3个斐波那契数之和。

注意数字范围与斐波那契数列的增长

前50个斐波那契数已经爆int了
外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

void solve()
{
    cin >> n;
    int x = 0, count = 3;
    for (i = 0; i < 3; i++)
    {
        int id = upper_bound(Fib + 1, Fib + 1 + num, n) - Fib - 1;
        ans[i] = Fib[id];
        n -= Fib[id];
    }
    if (n)
        cout << -1 << endl;
    else    
    {
        for (i = 0 ; i < 3; i++)
             cout << ans[i] << " ";   
        cout << endl;
    }
}

时空的交织

给定n个数的数组a和m个数的数组b,表示一个 n × m n×m n×m的矩阵,矩阵中的元素KaTeX parse error: Double subscript at position 4: m_i_̲j。选择一个子矩阵,使得子矩阵中的元素之和最大,输出这个和。

数学

乘法分配律: ( a i + a j ) ∗ ( b x + b y ) = a i ∗ b x + a i ∗ b y + a j ∗ b x + a j ∗ b y (a_i + a_j) * (b_x + b_y) = a_i * b_x +a_i * b_y + a_j * b_x + a_j * b_y (ai+aj)(bx+by)=aibx+aiby+ajbx+ajby
任取一个子矩形,我们相当于是求a数组的一个子数组之和乘上b数组的一个子数组之和
同时求出区间和的最大值和最小值,两两相乘取最大即可

void solve()
{
    int n, m, i, j, aMax = -MAX, bMax = -MAX, aMin = MAX, bMin = MAX, a, b, aMaxtmp = -MAX, bMaxtmp = -MAX, aMintmp = MAX, bMintmp = MAX;

    cin >> n >> m;
    for (i = 1; i <= n; i++)
    {
        cin >> a;
        aMaxtmp = max(a, aMaxtmp + a);
        aMax = max(aMax, aMaxtmp);
        
        aMintmp = min(a, a + aMintmp);
        aMin = min(aMin, aMintmp);
    }
    for (j = 1; j <= m; j++)
    {
        cin >> b;
        bMaxtmp = max(b, bMaxtmp + b);
        bMax = max(bMax, bMaxtmp);
        
        bMintmp = min(b, b + bMintmp);
        bMin = min(bMin, bMintmp);
    }
    cout << max(aMax * bMax, max(aMax * bMin, max(aMin * bMax, aMin * bMin))) << endl;
    // cout << max({aMax * bMax, aMax * bMin, aMin * bMax, aMin * bMin}) << endl;
}

绝妙的平衡

给定一棵有根树,若干个节点为红色.
为每个节点赋值1或2,使得每个以红色节点为根的子树,其节点值之和为3的倍数

dfs + 贪心

vector<int> v[N];
string s;
int ans[N], sum[N];
void dfs(int u)
{
    int w = -1;
    for (auto t : v[u])
    {
        dfs(t);
        sum[u] += sum[t];
        if (s[t] == 'W')
            w = t;
    }

    if (s[u] == 'R')
    {
        if (w == -1)
        {
            cout << "-1" << endl;
            return 0;
        }
        if (sum[u] % 3)
            ans[u] = 3 - (sum[u] % 3);
        else
        {
            ans[u] = 2;
            ans[w] = 2;
        }
        sum[u] = 0;
    }
    else
    {
        ans[u] = 1;
        sum[u]++;
    }
}

// 检查T,N
void solve()
{
    int n, i;
    cin >> n;

    cin >> s;
    s = ' ' + s;
    for (i = 2; i <= n; i++)
    {
        int x;
        cin >> x;
        v[x].push_back(i);
    }
    dfs(1);

    for (i = 1; i <= n; i++)
        cout << ans[i];
}

命运的抉择

给定一个长度为n的数组,把它分成非空的两部分a,b,使得从a,6中分别任取一个元素 a i a_i ai, b j b_j bj,它们互质gcd( a i , b j ) a_i, b_j) ai,bj)

图论 & 并查集

显然如果两个数包含同一个素因子,那么它们就必须在同一个集合内。

枚举每个素数,将它们的倍数放置在同一个并查集内
最终如果只有一个并查集,则无解;否则任意取一个并查集的元素放在 集合a,其余元素放在集合b即可。

int a[N], b[N], p[N];
vector<vector<int>> e(N);int find(int x)
{
    return x == p[x] ? x : p[x] = find(p[x]);
}void solve()
{
    cin >> n;
    for (i = 1; i <= n; i++)
        p[i] = i;
    vector<int> q;
    for (i = 1; i <= n; i++)
    {
        cin >> a[i];
        for (auto j : e[a[i]])
            if (!b[j])
                b[j] = i, q.emplace_back(j);
            else
                p[find(i)] = find(b[j]);
    }
    for (auto i : q)
        b[i] = 0;
    int cnt = 0;
    for (i = 2; i <= n; i++)
        cnt += find(1) != find(i);
    if (!cnt)
    {
        cout << "-1 -1" << endl;
        return;
    }
    cout << n - cnt << ' ' << cnt << endl;
    for (i = 1; i <= n; i++)
        if (p[1] == p[i])
            cout << a[i] << ' ';
    cout << endl;
    for (i = 1; i <= n; i++)
        if (p[1] != p[i])
            cout << a[i] << ' ';
    cout << endl;
}signed main()
{

    e[1].emplace_back(1);
    for (i = 2; i <= 1e6; i++)
        if (e[i].empty())
            for (int j = i; j <= 1e6; j += i)
                e[j].emplace_back(i);
    solve();
    return 0;
}

人生的起落

形如 (a, b, a) ,a > b的三元组称为 “v-三元组”
构造一个长度为n, 和为S, 且恰好有 S S S 个 “v-三元组” 的正整数数组

模拟 但注意的情况很多

最简单的构造就是2 1 2 1 …,分情况讨论,其中n=2*k+1时不是简单的2 1 2 1,需要进行计算

void solve()
{
    int i;
    cin >> n >> S >> k;
    if (k == 0)
    { // 没有三元组的情况
        for (i = 1; i < n; i++)
            cout << 1 << ' ';
        cout << S - n + 1 << endl;
        return;
    }
    if (n <= 2 * k || S <= n + k)
    { // 无法构成k个三元组或者元素之和不能支持构造
        cout << "-1\n";
        return;
    }
    if (n == 2 * k + 1)
    { // 特殊情况
        S -= (2 * (k + 1) + k);
        if (S > 0 && S < k + 1)
        {
            cout << "-1\n";
            return;
        }
        t = S % (k + 1);
        int op = 1;
        for (i = 1; i <= n; i++)
        {
            if (op == 1)
            {
                cout << 2 + S / (k + 1) << ' ';
                op = 0;
            }
            else if (op == 0)
            {
                if (t > 0)
                {
                    cout << 2 << ' ';
                    t--;
                }
                else
                    cout << 1 << ' ';
                op = 1;
            }
        }
        cout << endl;
        return;
    }
    // 其余情况,最简单的构造
    cout << 2 << ' ';
    for (i = 1; i <= k; i++)
        cout << "1 2 ";
    n = n - (2 * k + 1);
    S = S - (2 + 3 * k);
    if (n != 0)
        cout << S - (n - 1) << ' ';
    for (i = 1; i < n; i++)
        cout << 1 << ' ';
    cout << endl;
}

参考文献:

  1. https://blog.csdn.net/hushhw/article/details/78076947
  2. https://blog.csdn.net/u011815404/article/details/79747957
  3. https://blog.csdn.net/m0_50623076/article/details/113834440
  • 15
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值