2023牛客寒假算法基础集训营3

文章目录

  • A
  • B
  • C
  • D
  • E
  • F
  • G
  • I
  • K

A

思路:我们直接暴力的去找就行,如果是正数的话,我们直接除到他不是偶数为止,如果是负数的话,我们为了保证总和是最小的,我们不对负数进行操作。

void solve()
{
    int n;
    cin >> n;
    long long ans = 0;
    for (int i = 1; i <= n; i++)
    {
        int x;
        cin >> x;
        if (x >= 0)
        {
            while (x % 2 == 0)
                x /= 2;
            ans += x;
        }
        else
            ans += x;
    }
    cout << ans << endl;
}

B

思路:找规律或者二分
首先我们看看找规律,首先我们可以手推出前面的面积,然后我们发现是

然后我们通过观察就是6个为一组,然后前面四个是两两不变的,然后后面两个是不同的,总体是递增的。

int main()
{
    int T;
    cin >> T;
    while (T--)
    {
        LL n;
        cin >> n;
        if (n == 2)
        {
            cout << -1 << "\n";
            continue;
        }
        int h = (6 - n % 6);
        LL k;
        if (n % 6 == 0)
            k = n;
        else
            k = n + (6 - n % 6);
        LL cnt;
        cnt = k / 6;
        LL start = max((LL)0, (cnt - 1) * 4) + 1;
        int yu = n % 6;
        if (yu == 1 || yu == 2)
            cout << start << "\n";
        else if (yu == 3 || yu == 4)
            cout << start + 1 << "\n";
        else if (yu == 5)
            cout << start + 2 << "\n";
        else
            cout << start + 3 << "\n";
 
    }
    return 0;
}

二分:二分的话我们可以假设我们能凑出来的最大的正方形的边长是a,那么比a小的我们一定也可以凑出来,因为a已经是我们可以凑出的最大的所以比a大的一定不可以被凑出来。因此答案具有单调性。

我们考虑二分答案
2023牛客寒假算法基础集训营3_第1张图片
上图显示了我们一共用了 3 ∗ a n s − 2 ∗ ( n + 1 ) / 2 3 * ans - 2 * (n + 1) / 2 3ans2(n+1)/2快木棒,我们只需要二分的时候和n比较一下大小即可。
注:2的情况特判即可。

void solve()
{
	int n;
	cin >> n;
	if (n == 2)
	{
		cout << -1 << "\n";
		return ;
	}
	auto check = [&](int x)
	{
		return 3 * x - 2 * ((n + 1) / 2) <= n;
	};
	int l = (n + 1) / 2, r = n;
	while (l < r)
	{
		int  mid = l + r + 1 >> 1;
		if (check(mid))
			l = mid;
		else
			r = mid - 1;
	}
	cout << l << "\n";
}

C

思路:本题我分了四种情况。
第一种n可以被4整除,那么我们发现4个为一组,1和3,2和4相互配对即可。

第二种是是偶数但是不能被4整除的,我们依旧按照4个进行配对即可,但是我们需要调整最后6个的位置。1和4,2和5,3和6配对即可。

然后是奇数减去一可以被4整除的情况,那我们把前n-1个数按照4组进行配对即可,然后我们在更改一下最后三个数的位置1对2,2对3,3对1即可。

最后是是奇数但是(n-1)不能被4整除的情况,此时呢我们调整一下前5个数的顺序,使得前5个数满足条件,之后的数按照情况2把后面的数调整完成即可。

正常的思路是按照n%4,n%5和n%6进行的分类。

#include 
using namespace std;

unordered_map<int, int> mp;

int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int n;
    cin >> n;
    if (n <= 3 || n == 7)
    {
        cout << -1 << "\n";
        return 0;
    }
    if (n % 4 == 0)
    {
        for (int i = 1; i <= n; i += 4)
        {
            cout << i + 2 << ' ' << i + 3 << ' ' << i << ' ' << i + 1 << ' ';
        }
    }
    else if (n % 2 == 0)
    {
        int flag = 1;
        for (int i = 1; i <= n; i += 2)
        {
            if (i == n - 1)
            {
                mp[i] = i - 2;
                mp[i + 1] = i - 1;
                continue;
            }
            if (flag % 2 == 1)
            {
                mp[i] = i + 2;
                mp[i + 1] = i + 3;
            }
            else
            {
                mp[i] = i - 2;
                mp[i + 1] = i - 1;
            }
            flag++;
        }
        int start = n - 5;
        mp[start] = start + 3;
        mp[start + 1] = start + 1 + 3;
        mp[start + 2] = start + 2 + 3;
        mp[start + 3] = start + 3 - 3;
        mp[start + 4] = start + 4 - 3;
        mp[start + 5] = start + 5 - 3;
        for (int i = 1; i <= n; i++)
            cout << mp[i] << ' ';
    }
    else if (n % 2 == 1)
    {
        if ((n - 1) % 4 == 0)
        {
            for (int i = 1; i <= n - 1; i += 4)
            {
                mp[i] = i + 2;
                mp[i + 1] = i + 3;
                mp[i + 2] = i;
                mp[i + 3] = i + 1;
            }
            int a = mp[n - 2];
            int b = mp[n - 1];
            mp[n - 2] = n;
            mp[n - 1] = a;
            mp[n] = b;
            for (int i = 1; i <= n; i++)
                cout << mp[i] << ' ';
        }
        else
        {
            cout << 3 << ' ' << 4 << ' ' << 5 << ' ' << 1 << ' ' << 2 << ' ';
            int flag = 1;
            for (int i = 6; i <= n; i += 2)
            {
                if (i == n - 1)
                {
                    mp[i] = i - 2;
                    mp[i + 1] = i - 1;
                    continue;
                }
                if (flag % 2 == 1)
                {
                    mp[i] = i + 2;
                    mp[i + 1] = i + 3;
                }
                else
                {
                    mp[i] = i - 2;
                    mp[i + 1] = i - 1;
                }
                flag++;
            }
            int start = n - 5;
            mp[start] = start + 3;
            mp[start + 1] = start + 1 + 3;
            mp[start + 2] = start + 2 + 3;
            mp[start + 3] = start + 3 - 3;
            mp[start + 4] = start + 4 - 3;
            mp[start + 5] = start + 5 - 3;
            for (int i = 6; i <= n; i++)
                cout << mp[i] << ' ';
        }
    }
    return 0;
}

D

思路:赛时直接猜的结论偶数小红胜,奇数小紫胜。

int main()
{
	LL n;
	cin >> n;
	if (n % 2 == 0)
		cout << "kou" << "\n";
	else
		cout << "yukari" << "\n";
	return 0;
}

E

思路:这个题的话我们翻译一下题意就是,能否将给我们的两个点,以给我们两个点的中点为中心旋转90后得到的点x,y均为整数(不存在小数)。

我们可以知道一个点(x,y)按照另一个点(x1,y1)顺时针旋转90度后的坐标是(y-y1+x1,x1-x+y1),然后我们一开始判断一下是否是小数即可。细节见代码。

#include 
using namespace std;

typedef long long LL;

int main()
{
	LL xa, ya, xb, yb;
	cin >> xa >> ya >> xb >> yb;
	int h = (xa + ya) % 2;
	int k = (xb + yb) % 2;
	if ((h && !k) || (!h && k))
	{
		cout << "No Answer!" << "\n";
		return 0;
	}
	double x = ((double)xa + (double)xb) / 2.0;
	double y = ((double)ya + (double)yb) / 2.0;
	double ansx = ya - y + x;
	double ansy = x - xa + y;
	cout << (LL)ansx << ' ' << (LL)ansy << "\n";
	return 0;
}

F

cout<<42;

G

思路:我们可以dfs出每种情况,然后判断是否可行,可行的话直接输出,不可行的话最后输出-1即可。
注意我们这里的快速幂是有可能爆longlong的,所以我们在对a进行操作的时候要先模上模数

#include 
using namespace std;

#define int long long
vector<int> v;
vector<char> ch;
int ans;
int flag;
char d[4] = {' ', '+', '-', '#'};
char a[15];
int n;

int qmi(int a, int k, int p)
{
    int res = 1;
    a %= p;//要先%p不然会爆longlong
    while (k)
    {
        if (k & 1)
            res = res * a % p;
        a = a * a % p;
        k >>= 1;
    }
    return res;
}

void dfs(int u, int now)
{
    if (u == n)
    {
        if (now == ans)
        {
            flag = 1;
            for (int i = 0; i < n - 1; i++)
            {
                cout << v[i] << ch[i];
            }
            cout << v[n - 1] << '=' << ans << "\n";
            exit(0);
        }
        return ;
    }
    for (int i = 1; i <= 3; i++)
    {
        if (i == 3 && ((now <= 0) || (v[u] <= 0)))
            continue;
        ch.push_back(d[i]);
        if (i == 1)
            dfs(u + 1, now + v[u]);
        if (i == 2)
            dfs(u + 1, now - v[u]);
        if (i == 3)
            dfs(u + 1, qmi(now, now, v[u]));
        ch.pop_back();
    }
}

void get(string s)
{
    int res = 0;
    for (int i = 0; i < (int)s.size(); i++)
    {
        res *= 10;
        res += s[i] - '0';
    }
    v.push_back(res);
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    string s;
    cin >> s;
    int id = 0;
    string ss = "";
    for (int i = 0; i < (int)s.size(); i++)
    {
        if (s[i] == '=' || s[i] == '?')
        {
            get(ss);
            ss = "";
        }
        else
            ss += s[i];
        if (s[i] == '=')
        {
            id = i + 1;
            break;
        }
    }
    for (int i = id; i < (int)s.size(); i++)
        ss += s[i];
    get(ss);
    ans = v.back();
    v.pop_back();
    n = (int)v.size();
    dfs(1, v[0]);
    if (!flag)
        cout << -1 << "\n";
    return 0;
}

I

思路:题目中给了我们三种情况,我们大致可以猜测答案就是按这三总情况来分类。

我们看第一类,给我们的数是偶数n并且n-1是素数,那么素数的约数只有1和它本身,我们发现1+n-1=n这就是我们想要的情况,但是题目中要求等于自身的那个约数不算。那么怎么办呢,我们直接 ( n − 1 ) ∗ ( n − 1 ) (n-1)*(n-1) (n1)(n1)即可。那么约数只有1,n-1,(n-1)*(n-1)三个,并且最后一个不算。因此这种情况答案就是 ( n − 1 ) ∗ ( n − 1 ) (n-1)*(n-1) (n1)(n1)

我们看第二类,第二类是给我们一个偶数n并且呢(n-3)是素数。我们发现如果按n-3的情况来看的话我们少了一个约数,这个约数是2,也就是说1+n-3+2=n,那么此时怎么办呢,我们发现直接 2 ∗ ( n − 3 ) 2*(n-3) 2(n3)即可,这样的话,我们会得到1,2,(n-3)和2*(n-3)这四个约数,但是呢最后那一个不算,那么也就正好凑出了我们想要的三个约数。因此答案是 2 ∗ ( n − 3 ) 2*(n-3) 2(n3)

那么最后一种也就是奇数的情况,我们考虑哥德巴赫猜想任意一个大于2的偶数都可以写成两个质数之和,即 p r i m e s 1 + p r i m e s 2 = n ( 偶数 ) primes1+primes2 = n(偶数) primes1+primes2=n(偶数),我们转化一下就是 1 + p r i m e s 1 + p r i m e s 2 = n ( 奇数 ) 1+primes1+primes2 = n(奇数) 1+primes1+primes2=n(奇数)。那我们直接线性筛,筛完之后暴力的一对一对的去找即可。

#include 
using namespace std;

#define int long long
const int N = 1e6 + 10;
int primes[N], cnt;
bool st[N];


void get_primes(int n)
{
    for (int i = 2; i <= n; i++)
    {
        if (!st[i])
            primes[cnt++] = i;
        for (int j = 0; i * primes[j] <= n; j++)
        {
            st[i * primes[j]] = true;
            if (i % primes[j] == 0)
                break;
        }
    }
}

void solve()
{
    int n;
    cin >> n;
    if (n % 2 == 0)
    {
        if (n == 2)
        {
            cout << 3 << "\n";
            return ;
        }
        if (st[n - 1] == false)
            cout << (n - 1) * (n - 1) << "\n";
        else
            cout << 2 * (n - 3) << "\n";
        return ;
    }
    else if (n == 1)
    {
        cout << 2 << "\n";
        return ;
    }
    else if (n == 3)
    {
        cout << 4 << "\n";
        return ;
    }
    else if (n == 5)
    {
        cout << -1 << "\n";
        return ;
    }
    else if (n == 7)
    {
        cout << 8 << "\n";
        return ;
    }
    else
    {
        for (int i = 2; i <= n; i++)
        {
            if (!st[i] && !st[n - i - 1])
            {
                cout << i*(n - i - 1) << "\n";
                return ;
            }
        }
    }
    cout << -1 << "\n";
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    int T;
    get_primes(1000000);
    cin >> T;
    while (T--)
        solve();
    return 0;
}

K

思路:首先呢,我们分析一下样例,可以猜测我们按顺序放置质因子是最大值。然后我们通过约数定理可以知道,约数个数等于全部质因子的(指数+1)的乘积。我们现在以样例为例子。 2 , 3 , 7 , 2 , 3 , 3 2, 3, 7, 2, 3, 3 2,3,7,2,3,3对于前三个数来说呢,因为2和3和7都出现了一次,那么对于前三个数来说约数个数依次是 ( 1 + 1 ) = 2 (1+1)=2 (1+1)=2, ( 1 + 1 ) ∗ ( 1 + 1 ) = 4 (1+1)*(1+1)=4 (1+1)(1+1)=4, ( 1 + 1 ) ∗ ( 1 + 1 ) ∗ ( 1 + 1 ) = 8 (1+1)*(1+1)*(1+1)=8 (1+1)(1+1)(1+1)=8,然后把 2 , 3 2,3 2,3看做一组,此时2和3均出现了两次,那么约数依次就是 ( 1 + 1 + 1 ) ∗ ( 1 + 1 ) ∗ ( 1 + 1 ) = 12 (1+1+1)*(1+1)*(1+1)=12 (1+1+1)(1+1)(1+1)=12 ( 1 + 1 + 1 ) ∗ ( 1 + 1 + 1 ) ∗ ( 1 + 1 ) = 18 (1+1+1)*(1+1+1)*(1+1)=18 (1+1+1)(1+1+1)(1+1)=18,最后的3是一组约数是 ( 1 + 1 + 1 + 1 ) ∗ ( 1 + 1 + 1 ) ∗ ( 1 + 1 ) = 24 (1+1+1+1)*(1+1+1)*(1+1)=24 (1+1+1+1)(1+1+1)(1+1)=24整个的和就是68.

然后我们可以发现每段之间都是一个等比数列段的公比是 ( i + 1 ) / i (i+1)/i (i+1)/i,我们可以利用差分数组c来存,c[i]为出现次数大于等于i的数的质因子的个数。那么我们分段的时候一段就是对应的c[i]个数。我们可以利用等比数列的求和公式来求解一段的和,然后整体加起来就是整个的答案。

求每一段的首项的话,我们可以用前一段的末项。(具体详见代码)

因为此题的话要求我们在模数下求,所以我们要用到的逆元。

#include 
using namespace std;

#define int long long
const int N = 2e5 + 10, mod = 1e9 + 7;
int a[N], n;
int c[N];

int qmi(int a, int k, int p)
{
    int res = 1;
    while (k)
    {
        if (k & 1)
            res = res * a % p;
        a = a * a % p;
        k >>= 1;
    }
    return res;
}

signed main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr), cout.tie(nullptr);
    cin >> n;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
        c[1]++;
        c[a[i] + 1]--;
    }
    for (int i = 1; i <= 200005; i++)  c[i] = c[i] + c[i - 1];
    int ans = 0, start = 2, en = 1;
    for (int i = 1; i <= 200005; i++)
    {
        if (!c[i])
            break;
        int q = (i + 1) * qmi(i, mod - 2, mod) % mod;
        en = start * max((long long)1, qmi(q, c[i] - 1, mod)) % mod;
        int a = (qmi(q, c[i], mod) - 1) % mod;
        int b = (q - 1) % mod;
        int g = a * qmi(b, mod - 2, mod) % mod;
        ans += start * g % mod;
        start = (en * qmi(i + 1, mod - 2, mod) % mod) * (i + 2) % mod;
    }
    cout << ans % mod << "\n";
    return 0;
}

你可能感兴趣的:(训练赛补题,算法,c++,图论)