AtCoder ABC172

C - Tsundoku
不能贪心
例如第一组是8 1 1 1 1 1 1…
第二组是7 7 7 7 7 7 7…
如果贪心去选,始终会选第二组。而正确答案显然是选第一组
正确的方法是二分答案,并在答案n中遍历第一组选取的i与第二组选取的n-i个数

**# -*- coding: utf-8 -*-
# @time     : 2023/6/2 13:30
# @file     : atcoder.py
# @software : PyCharm

import bisect
import copy
import sys
from itertools import permutations
from sortedcontainers import SortedList
from collections import defaultdict, Counter, deque
from functools import lru_cache, cmp_to_key
import heapq
import math
sys.setrecursionlimit(1000)


def main():
    items = sys.version.split()
    if items[0] == '3.10.6':
        fp = open("in.txt")
    else:
        fp = sys.stdin
    n, m, k = map(int, fp.readline().split())
    a = list(map(int, fp.readline().split()))
    b = list(map(int, fp.readline().split()))
    sa = [0] * (n + 1)
    sb = [0] * (m + 1)
    for i in range(n):
        sa[i + 1] = a[i] + sa[i]
    for i in range(m):
        sb[i + 1] = b[i] + sb[i]

    def check(l):
        for i in range(0, l + 1):
            if 0 <= i <= n and 0 <= l - i <= m and sa[i] + sb[l - i] <= k:
                return True
        return False

    lo, hi = 0, n + m + 1
    while lo < hi:
        mi = lo + hi >> 1
        if check(mi):
            lo = mi + 1
        else:
            hi = mi
    print(lo - 1)


if __name__ == "__main__":
    main()
**

D - Sum of Divisors
简单数论题
虽然N很大,但是可以在 O ( n ∗ l o g ( n ) ) O(n *log(n)) O(nlog(n))时间内暴力解决,即调和函数
这里采用了另一种解法,在 O ( k n ) 时间内暴力解决 O(kn)时间内暴力解决 O(kn)时间内暴力解决,k大约在30左右
首先可以得到 F ( n ) F(n) F(n)是积性函数,即 F ( a b ) = F ( a ) F ( b ) F(ab)=F(a)F(b) F(ab)=F(a)F(b)如果 a b ab ab互质
这样就可以通过质因数分解的方式,枚举出能整除n的质数a,然后通过上述公式计算 F ( n ) F(n) F(n)

// atcoder.cpp : 
//
#define _CRT_SECURE_NO_WARNINGS
#include <cstdio>
#include <map>
#include <set>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <vector>
#include <regex>
#include <queue>
#include <climits>
using namespace std;

typedef pair<int, int> pii;
typedef long long LL;
typedef vector<int> vi;


int n;
vi p;
LL f[10000001];


int main()  
{
	//freopen("in.txt", "r", stdin);
	for (int i = 2; i < 10000; ++i) {
		bool flag = 1;
		for (int j = 0; j < p.size(); ++j)
		{
			if (i % p[j] == 0) {
				flag = 0;
				break;
			}
			if (p[j] * p[j] >= i)
				break;
		}
		if (flag)
			p.push_back(i);
	}
	LL ans = 0;
	scanf("%d", &n);
	f[1] = 1;
	ans = 1;
	for (int i = 2; i <= n; ++i) {
		int t = i;
		for (int j : p) {
			if (t % j == 0) {
				int c = 0;
				while (t % j == 0) {
					t /= j;
					c++;
				}
				f[i] = f[t] * (c + 1);
				break;
			}
			if (j * j >= i)
				break;
		}
		if (t == i) f[i] = 2;
		ans += i * f[i];
	}
	printf("%lld\n", ans);
	return 0;
}

E - NEQ
一道不错的组合数学题目
解法1:重排问题
我们先考虑标准的重排问题:对一个排列 1,2,3,4…n,求重排后没有数字相同的排列数

在这里插入图片描述

设f(n)为该问题解
考虑数字n,如图中,6有n-1处可以放置
如果6放置在4处,那么考虑4,
4有两种安排方法
第一种是和6交换位置,原问题变成了f(n-2)
第二种是不放在6的位置,那么原问题变成了排列一个1 2 3 5 4,长度为5的子问题,答案是f(n-1)
所以 f ( n ) = ( n − 1 ) ( f ( n − 1 ) + f ( n − 2 ) ) f(n)=(n-1)(f(n-1)+f(n-2)) f(n)=(n1)(f(n1)+f(n2))

然后我们考虑修改过的重排问题,即本题要求
对一个排列A 1,2,3…n
B排列可以选取 [1,m],要求每个位置不能与A中数字相同

在这里插入图片描述

每次有两种选法
第一种选N,因此可以转换为重排问题的做法
f ( n ) = ( n − 1 ) ( f ( n − 1 ) + f ( n − 2 ) ) f(n)=(n-1)(f(n-1)+f(n-2)) f(n)=(n1)(f(n1)+f(n2))
第二种选N数字范围外的
如上图,将原A数组的6用7代替,然后问题就转换为f(n-1)
注意,这里的 f ( n ) f(n) f(n)函数代表意义为排列集合{1…n},并且留有t个多余的比n大的数的情况下所能组成的排列数,使用上图的构造方法,这里的t永远都是M-N
为什么7不能放在位置1.2.3.4.5?因为如果这样进行交换后,新交换进来的数可能比集合里的数要大,不能满足 f ( n ) f(n) f(n)函数的定义。

// atcoder.cpp : 
//
#define _CRT_SECURE_NO_WARNINGS
#include <cstdio>
#include <map>
#include <set>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <vector>
#include <regex>
#include <queue>
#include <climits>
using namespace std;

typedef pair<int, int> pii;
typedef long long LL;
typedef vector<int> vi;


int n, m;
LL f[500005];
int mod = int(1e9) + 7;


int main()  
{
	//freopen("in.txt", "r", stdin);
	scanf("%d%d", &n, &m);
	f[0] = 1;
	f[1] = m - n;
	for (int i = 2; i <= n; ++i) {
		f[i] = ((m - n) * f[i - 1] % mod + (i - 1) * ((f[i - 1] + f[i - 2]) % mod) % mod) % mod;
	}
	LL ans = f[n];
	for (int i = m, j = 0; j < n; i--, j++) {
		ans = ans * i % mod;
	}
	printf("%lld\n", ans);
	return 0;
}

解法2:容斥原理
第一种解法其实比较难想,第二种解法更为容易一点
首先确定A数组的排列,和解法1是一致的。然后看B数组。
当难以求“所有位置都不重复”时,我们可以求确定重复一个位置(可能重复更多位置),确定重复两个位置,重复三个。。。一直到重复N个位置的解法。
确定i个位置重复的方案数为 C ( n , i ) ∗ A ( m − i , n − i ) C(n,i)*A(m-i,n-i) C(n,i)A(mi,ni)
例如 1 2 3 4与 1 3 2 4是确定重复位置1,而1 2 3 4与 1 2 3 4也是确定重复位置1
因为解法集合是偏序包含的,因此可以采用容斥原理
f ( n ) = ∑ i = 0 n A ( m − i , n − i ) C ( n , i ) ( − 1 ) i f(n)=\sum_{i=0}^{n} A(m-i,n-i)C(n,i)(-1)^i f(n)=i=0nA(mi,ni)C(n,i)(1)i

// atcoder.cpp : 
//
#define _CRT_SECURE_NO_WARNINGS
#include <cstdio>
#include <map>
#include <set>
#include <cstring>
#include <string>
#include <cmath>
#include <iostream>
#include <vector>
#include <regex>
#include <queue>
#include <climits>
using namespace std;

typedef pair<int, int> pii;
typedef long long LL;
typedef vector<int> vi;


int n, m;
int mod = int(1e9) + 7; 
LL fac[500005], inv[500005];


LL pw(LL a, LL x) {
	if (x == 0) return 1;
	LL temp = pw(a, x >> 1);
	if (x & 1) {
		return temp * temp % mod * a % mod;
	}
	else {
		return temp * temp % mod;
	}
}


LL cmb(LL x, LL y) {
	return fac[x] * inv[y] % mod * inv[x - y] % mod;
}

LL perm(LL x, LL y) {
	return fac[x] * inv[x - y] % mod;
}



int main()  
{
	//freopen("in.txt", "r", stdin);
	scanf("%d%d", &n, &m);
	fac[0] = 1;
	for (int i = 1; i <= m; ++i)
		fac[i] = fac[i - 1] * i % mod;
	inv[m] = pw(fac[m], mod - 2LL);
	for (int i = m - 1; i >= 0; --i)
		inv[i] = inv[i + 1] * (i + 1LL) % mod;
	
	LL ans = 0;
	for (int i = 0; i <= n; ++i) {
		if (i % 2 == 0)
			ans += perm(m - i, n - i) * cmb(n, i) % mod;
		else
			ans -= perm(m - i, n - i) * cmb(n, i) % mod;
		ans %= mod;
		if (ans < 0) ans += mod;
	}
	ans = ans * perm(m, n) % mod;
	printf("%lld\n", ans);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值