ブルーブリッジカップ 22日目 (Python) (クレイジークエスチョン 5日目)

質問の種類:

1. 思考問題・雑問題:数式、問題の意味分析、パターン発見

2. BFS/DFS: ワイド検索 (再帰的実装)、ディープ検索 (deque 実装)

3. 簡単な整数論:法、素数(int(sqrt(n))+1まで判定するだけ)、gcd、lcm、高速べき乗(ビット演算シフト演算)、大数分解(素数の積への分解)数字)

4. 単純なグラフ理論: 最短パス (1 対多 (Dijstra、隣接テーブル、行列の実装)、多対多 (Floyd、行列の実装))、最小スパニング ツリー (および検索セットの実装)

5. 単純な文字列処理: リスト操作に切り替えるのが最善です

6. DP: 線形 DP、最長共通部分列、0/1 ナップザック問題、最長連続文字列、最大増加部分文字列

7. 基本アルゴリズム: バイナリ、グリーディ、組み合わせ、順列、プレフィックス合計、差分

8. 基本的なデータ構造: キュー、セット、辞書、文字列、リスト、スタック、ツリー

9. よく使用されるモジュール: math、datetime、sys での最大再帰深さの設定 (sys.setrecursionlimit(3000000))、collections.deque (キュー)、itertools.combinations (list, n) (組み合わせ)、itertools.permutations (list) 、n) (順列) heapq (スモールトップヒープ)

目次

1. ペーパーナイフ(思考)

2. 整数を求める

3.「素因数の数」実問題演習(大数の分解)

 4.「長方形モザイク」の実際の実践(列挙型走査)

5.「エリミネーションゲーム」(暴力サイクル)編集

6. 並べ替え (差分配列、貪欲)

7.「完全順列の価値」練習問題(数学の定理、考え方)

 8. 「最長の非減少部分列」の実際の実践 (DP)

 9.「最適クリアリングスキーム」実問演習(暴力、線分ツリー)

 10. 「数字の分割」の実際の練習


1. ペーパーナイフ(思考)

 カット数は確実!ルールを見つけて出力を印刷するだけです。

2. 整数を求める

import sys  #设置递归深度
import collections  #队列
import itertools  # 排列组合
import heapq  #小顶堆
import math
sys.setrecursionlimit(300000)
import functools   # 自定义比较函数  -1不变,1交换


def lcm(x,y):
   return x//math.gcd(x,y)*y

x=3
step=1

b=[0,0,
   1,2,1,4,5,4,1,2,9,0,5,10,
   11,14,9,0,11,18,9,11,11,15,17,9,
   23,20,25,16,29,27,25,11,17,4,29,22,
   37,23,9,1,11,11,33,29,15,5,41,46
   ]

'''
3 5 7 8 9 11 13 15 17 19 21 23 25 27      2递增
5 8 11 14 17 20 23 26 29 32     3递增筛选    5 11 17 23  
5 9 13 17 21 25 29 33     4递增    5 17 29

'''


for i in range(2,50):  # 类似埃式筛法
   while x %i !=b[i]:   
      x+=step
   step=lcm(step,i)  # 更新步长
print(x)



 法を見つけるための以前のいくつかによると、暴力的な移動または法を見つける

3.「素因数の数」実問題演習(大数の分解)

標準スケジュール:
 

n = int(input())
ans = 0
#从2开始进行质因子分解
i = 2
while i * i <= n:
    if n % i == 0:
        ans += 1
        while n % i == 0:
            n //= i
    i += 1
if n != 1:
    ans += 1
print(ans)
from random import randint
from math import gcd

def witness(a, n):
    u = n - 1
    t = 0
    while u % 2 == 0:
        u = u // 2
        t += 1
    x1 = pow(a, u, n)
    for i in range(1, t + 1):
        x2 = x1 * x1 % n
        if x2 == 1 and x1 != 1 and x1 != n - 1:
            return True
        x1 = x2
    if x1 != 1:
        return True
    return False

#miller_rabin素性测试 对数字n进行s次测试
def miller_rabin(n, s = 5):
    if n < 2:
        return False
    if n == 2:
        return True
    if n % 2 == 0:
        return False
    for i in range(s):
        a = randint(1, n - 1)
        if witness(a, n):
            return False
    return True

#返回一个因子,不一定是素因子
def pollard_rho(n):
    i, k = 1, 2
    c = randint(1, n - 1)
    x = randint(0, n - 1)
    y = x
    while True:
        i += 1
        x = (x * x + c) % n
        d = gcd(abs(x - y), n)
        if d != 1 and d != n:
            return d
        if y == x:
            return n
        if i == k:
            y = x
            k = k * 2

factor = []
#找所有的素因子
def findfac(n):
    if miller_rabin(n):
        factor.append(n)
        return
    p = n
    while p >= n:
        p = pollard_rho(p)
    findfac(p)
    findfac(n // p)

n = int(input())
findfac(n)
print(len(set(factor)))

 4.「長方形モザイク」の実際の実践(列挙型走査)

 

 4面、6面、8面の場合を順番に考えて数え上げて横断する

標準スケジュール:

T = int(input())
while T != 0:
    T -= 1
    a = list(map(int, input().split()))
    a = [[a[0],a[1]], [a[2],a[3]], [a[4],a[5]]]

    ans = 8
    #枚举第一个矩形下标为i,第二个矩形下标为j,第三个矩形下标为k
    for i in range(3):
        for j in range(3):
            for k in range(3):
                if i == j or i == k or j == k:
                    continue
                #枚举三个矩形的两条边
                for ii in range(2):
                    for jj in range(2):
                        for kk in range(2):
                            if a[i][ii] == a[j][jj]:
                                ans = min(ans, 6)
                                if a[i][ii] == a[k][kk]:
                                    ans = min(ans, 4)
                            if a[i][ii] == a[j][jj] + a[k][kk]:
                                ans = min(ans, 6)
                                if a[j][1 - jj] == a[k][1 - kk]:
                                    ans = min(ans, 4)

    print(ans)

5.「エリミネーションゲーム」(暴力のサイクル)

暴力的なサイクル、1 ラウンドをスキャン、マージン文字を確認、添字を記録、スキャン後に削除、完了後にサイクルを継続、終了条件: 現在の文字が空であるか、1 サイクル後も長さが変化しません。

標準スケジュール:

s = list(input())
last_length = 0

while True:
    length = len(s)
    #如果长度等于0,终止
    if length == 0:
        print("EMPTY")
        break
    #如果长度未发生变化,终止
    if length == last_length:
        print("".join(s))
        break
    vis = [0] * length
    #根据题意找出边缘字符
    for i in range(length):
        if (i - 1) >= 0 and (i + 1) < length and s[i] == s[i - 1] and s[i] != s[i + 1]:
            vis[i] = vis[i + 1] = 1
        if (i - 1) >= 0 and (i + 1) < length and s[i] != s[i - 1] and s[i] == s[i + 1]:
            vis[i] = vis[i - 1] = 1
    #将边缘字符去除
    tmp_s = []
    for i in range(length):
        if vis[i] == 0:
            tmp_s.append(s[i])
    s = tmp_s
    last_length = length

6. 並べ替え (差分配列、貪欲)

 暫定的なアイデア: 再クエリされた間隔を記録して交差があるかどうかを確認し、交差間隔を最大値に置き換えます。

差分配列

 肯定的な解決策: 間隔を読み取り、間隔の訪問数 (差分配列によって実現) をマークし、貪欲なアイデアに従って、訪問数が最も多い位置に大きな値を置きます。

標準スケジュール:

import sys  #设置递归深度
import collections  #队列
import itertools  # 排列组合
import heapq  #小顶堆
import math
sys.setrecursionlimit(300000)
import functools   # 自定义比较函数  -1不变,1交换


# 总体思路:查询最多的那一个放最大值,不需要序号,只需要记录最大次数
n = int(input())
a = list(map(int,input().split()))
a=[0]+a
b=[0]*(n+10)
s=[0]*(n+1)

m=int(input())
for i in range(m):
   # 差分数组实现区间加法更新
   l,r = map(int,input().split())
   b[l]+=1
   b[r+1]-=1

#对差分数组前缀和,得到每个数字的查询次数
for i in range(1,n+1):
   s[i]=s[i-1]+b[i]

# sum1为原始和,sum2为贪心后的最大值
sum1,sum2=0,0
for i in range(1,n+1):
   sum1+=a[i]*s[i]

# 贪心思想,大对大,小对小
a.sort()
s.sort()

# 计算重新排序后的
for i in range(1,n+1):
   sum2+=a[i]*s[i]
print(sum2-sum1)



7.「完全順列の価値」練習問題(数学の定理、考え方)

予備的な考え:パターンを探していますか? ルールがない場合は、すべての順列をリストし、暴力を繰り返し、できるだけ多くのポイントを獲得してください。

 法則を見つけてデータの 20% を正しく取得する

import sys  #设置递归深度
import collections  #队列
import itertools  # 排列组合
import heapq  #小顶堆
import math
sys.setrecursionlimit(300000)
import functools   # 自定义比较函数  -1不变,1交换


#4 0+1*3+2*3+3*3+4*3+5*3+6
#3 0+1+1+2+2+3
#2 0+1

ans=0
n = int(input())
for i in range(1,n*(n-1)//2):
   ans+=(n-1)*i%998244353
print((ans+n*(n-1)//2)%998244353)

 肯定的な解: 順序数と順序数を反転したものとの合計は n*(n-1)//2、順序数と順序数を反転したものは等しく、n 個の数値には n という共通点があります。フル配列、つまり順序​​と逆順の合計は n!*n*(n-1)//2 なので、値の合計は上記の式を 2 で割ったものになります。

標準スケジュール:

mod = 998244353
n = int(input())
ans = n * (n - 1) // 2 % mod
for i in range(3, n + 1):
    ans = ans * i % mod
print(ans)

 8. 「最長の非減少部分列」の実際の実践 (DP)

暫定的なアイデア: DP アルゴリズム、つまり最長の増分サブシーケンス テンプレートを使用してこれを実行し、最長のサブシーケンスの添え字を記録し、DP 配列を使用して後ろから前まで検索して走査し、存在するかどうかを確認します。要素が最長のサブシーケンスより小さく、間隔が K より大きい場合、直接 +k?

問題があるはず、この考え方には問題がある!すべてのデータを渡すことは不可能であり、検討は不完全です。

肯定的な解決策: DP 最長増分テンプレートを使用するというアイデアは似ていますが、線分ツリー テンプレートを使用して維持する必要があるため、この質問には満点を付けずにスキップしてください。

 9.「最適クリアリングスキーム」実問演習(暴力、線分ツリー)

 暫定的なアイデア:最初に操作 2 を選択し、次に操作 1 では暴力的なサイクルで十分です

 肯定的な解決策:考え方は同じですが、私の考えではすべてのデータを通過することはできず、線分ツリーで処理する必要がありますが、線分ツリーを学習していません。

maxn = 1000000 + 10
tree_mi = [0] * (maxn * 4)
tree_add = [0] * (maxn * 4)

n, k = list(map(int, input().split()))
a = list(map(int, input().split()))
a = [0, *a]

#线段树模板
#利用左右儿子信息更新节点o
def push_up(o):
    tree_mi[o] = min(tree_mi[o << 1], tree_mi[o << 1 | 1])

#利用节点o的lazy标记add更新左右儿子
def push_down(o):
    if tree_add[o] != 0:
        tree_add[o << 1] += tree_add[o]
        tree_mi[o << 1] += tree_add[o]
        tree_add[o << 1 | 1] += tree_add[o]
        tree_mi[o << 1 | 1] += tree_add[o]
        tree_add[o] = 0

#建树
def build(o, l, r):
    tree_add[o] = 0
    if l == r:
        tree_mi[o] = a[l]
        return
    mid = (l + r) >> 1
    build(o << 1, l, mid)
    build(o << 1 | 1, mid + 1, r)
    push_up(o)

#查询区间[L,R]的最小值
def query(o, l, r, L, R):
    if L <= l and r <= R:
        return tree_mi[o]
    push_down(o);
    mid = (l + r) >> 1
    ans = 1000000000;
    if L <= mid:
        ans = min(ans, query(o << 1, l, mid, L, R))
    if R > mid:
        ans = min(ans, query(o << 1 | 1, mid + 1, r, L, R))
    return ans

#区间更新[L,R]统一加上val
def update(o, l, r, L, R, val):
    if L <= l and r <= R:
        tree_mi[o] += val
        tree_add[o] += val
        return
    push_down(o);
    mid = (l + r) >> 1
    if L <= mid:
        update(o << 1, l, mid, L, R, val)
    if R > mid:
        update(o << 1 | 1, mid + 1, r, L, R, val)
    push_up(o);


build(1, 1, n)
ans = 0
for i in range(1, n - k + 2):
    #查询区间[i, i+k-1]的最小值
    mi = query(1, 1, n, i, i + k - 1)
    if mi == 0:                     #无法进行区间消除
        #res表示当前的a[i]
        res = query(1, 1, n, i, i)
        #把当前的a[i]置为0
        update(1, 1, n, i, i, -res)
        ans += res
    else:
        ans += mi
        #区间消除
        update(1, 1, n, i, i + k - 1, -mi)
        #res表示当前的a[i]
        res = query(1, 1, n, i, i)
        #把当前的a[i]置为0
        update(1, 1, n, i, i, -res)
        ans += res
for i in range(n - k + 2, n + 1):
    ans += query(1, 1, n, i, i)
print(ans)

 10. 「数字の分割」の実際の練習

 

 暫定的なアイデア:大きな数の分解ができるはずです。2 つの素数または 1 つの素数に分解できるかどうかを確認します。分解できる場合は、yes を出力し、そうでない場合は、no を出力します。

import os
import sys
import math

# 请在此输入您的代码
t= int(input())
def check(n):
  count=[]  # 记录有多少个素数
  cur=0  # 指向列表,方便记录幂次
  for i in range(2,int(math.sqrt(n))+1):
    if n%i==0:
      count.append(0)
      if len(count)>2:
        print("no")
        return
      while n%i==0:
        n=n//i
        count[cur]+=1
      cur+=1  #记录下一个质数幂次
  if n>1:
    count.append(1)
    if len(count)>2:
      print("no")
      return
  #print(count)
  if len(count)==1 and count[0]>=2:
    print('yes')
    return
  if  len(count)==2 and count[0]>=2 and count[1]>=2:
    print('yes')
    return
  print('no')

for i in range(t):
  a=int(input())
  check(a)

肯定的な解決策:質問の意味が間違っているようですが、この質問が尋ねているのは、分解された素数の累乗が 2 より大きければそれでよいということです。(標準的な方法は、エスペラントふるいを使用して 4000 以内の素数をすべて取得することです。各数値を調べるには、最初に平方と立方体を判断し、次に 4000 以内の素数因数を列挙します。1 に等しい累乗がある場合、ループから直接ジャンプして no を出力します)

import os
import sys
import math

# 请在此输入您的代码
t= int(input())
def check(n):
  count=0  # 记录幂次
  for i in range(2,int(math.sqrt(n))+1):
    if n%i==0:
      while n%i==0:
        n=n//i
        count+=1
      if(count<2):
        print('no')
        return
      count=0   # 复位,记数下一个质数
  if n>1:  # 剩下一个质数,幂次肯定为1,不满足
      print("no")
      return
  print('yes')

for i in range(t):
  a=int(input())
  check(a)

標準スケジュール:

import os
import sys
import math

not_prime = [0]*4010
prime = []
# 预处理400以内的素数
for i in range(2,4001):
  if not_prime[i]==0:
    prime.append(i)
    for j in range(2*i,4001,i):  #埃式筛
        not_prime[j]=1

# 判断平方数
def square_number(x):
  y=int(x**0.5)
  return y*y==x or (y+1)*(y+1)==x   #防止精度出问题


# 判断立方数
def cubic_number(x):
  y=int(x**(1/3))
  return y**3==x or (y+1)**3 ==x
t = int(input())
for i in range(t):
  a=int(input())
  # 判断平方和立方
  if square_number(a) or cubic_number(a):
    print('yes')
    continue
  # 枚举4000以内因子
  falg=True
  for i in prime:
    if a%i==0:
      mi=0
      while a%i==0:
        a=a//i
        mi+=1
      if mi==1:
        falg=False
        break
  if falg:
    print('yes')
  else:
    print('no')
      
      

おすすめ

転載: blog.csdn.net/weixin_52261094/article/details/129931434