GVKun编程网logo

The Swift Programming Language学习笔记(二十三)——协议(swift中的协议)

2

本文的目的是介绍TheSwiftProgrammingLanguage学习笔记的详细情况,特别关注二十三——协议的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面的了解The

本文的目的是介绍The Swift Programming Language学习笔记的详细情况,特别关注二十三——协议的相关信息。我们将通过专业的研究、有关数据的分析等多种方式,为您呈现一个全面的了解The Swift Programming Language学习笔记的机会,同时也不会遗漏关于ACM International Collegiate Programming Contest, Damascus University Collegiate Programming Cont...、ACM International Collegiate Programming Contest, Egyptian Collegiate Programming Contest (ECPC 2...、ACM International Collegiate Programming Contest, JUST Collegiate Programming Contest (2018)、Aspect-Oriented Programming : Aspect-Oriented Programming with the RealProxy Class的知识。

本文目录一览:

The Swift Programming Language学习笔记(二十三)——协议(swift中的协议)

The Swift Programming Language学习笔记(二十三)——协议(swift中的协议)

  • 协议
    • 协议语法
    • 属性要求
    • 方法要求
    • mutating方法要求
    • 构造器要求
      • 构造器要求在类中的实现
      • 可失败构造器要求
    • 协议作为类型
    • 委托代理模式
    • 通过扩展添加协议一致性
    • 通过扩展采纳协议
    • 协议类型的集合
    • 协议的继承
    • 类类型专属协议
    • 协议合成
    • 检查协议一致性
    • 可选的协议要求
    • 协议扩展
      • 提供默认实现
      • 为协议扩展添加限制条件

协议

协议定义了一个蓝图,规定了用来实现某一特定任务或者功能的方法、属性,以及其他需要的东西。类、结构体或枚举都可以采纳协议,并为协议定义的这些要求提供具体实现。某个类型能够满足某个协议的要求,就可以说该类型“符合”这个协议。

除了采纳协议的类型必须实现的要求外,还可以对协议进行扩展,通过扩展来实现一部分要求或者实现一些附加功能,这样采纳协议的类型就能够使用这些功能。

协议语法

使用protocol定义协议。协议的定义方式与类、结构体和枚举的定义非常相似。要让自定义类型采纳某个协议,在定义类型时,需要在类型名称后加上协议名称,中间以冒号(:)分隔。采纳多个协议时,各协议之间用逗号(,)分隔。拥有父类的类在采纳协议时,应该将父类名放在协议名之前,以逗号分隔。

class A { }

protocol B { }

protocol C { }

struct  D: B,C { }

class E: A,B,C { }

属性要求

协议可以要求采纳协议的类型提供特定名称和类型的实例属性或类型属性。协议不指定属性是存储型属性还是计算型属性,它只指定属性的名称和类型。此外,协议还指定属性是只读的还是可读可写的

如果协议要求属性是可读可写的,那么该属性不能是常量属性或只读的计算型属性。如果协议只要求属性是只读的,那么该属性不仅可以是只读的,如果代码需要的话,还可以是可写的

协议通常用var关键字来声明变量属性,在类型声明后加上 { set get }来表示属性是可读可写的,只读属性则用{ get }来表示。

在协议中定义类型属性时,总是使用static关键字作为前缀。当类类型采纳协议时,除了static关键字,还可以使用class关键字来声明类型属性。

protocol A {
    var a: Int { get set }
    var b: Int { get }  // 如果协议只要求属性是只读的,那么该属性不仅可以是只读的,如果代码需要的话,还可以是可写的。
}

protocol B {
    static var a: Int { get set }
}

/** * FullyNamed协议除了要求采纳协议的类型提供fullName属性外,并没有其他特别的要求。这个协议表示,任何采纳FullyNamed的类型,都必须有一个只读的String类型的实例属性fullName。 */
protocol FullyNamed {
    var fullName: String { get }
}

struct Person: FullyNamed {
    var fullName: String    // Person结构体的每一个实例都有一个String类型的存储型属性fullName。这正好满足了FullyNamed协议的要求,也就意味着Person结构体正确地符合了协议。
}

let p = Person(fullName: "Tim")

class Starship: FullyNamed {
    var prefix: String?
    var name: String
    init(name: String,prefix: String? = nil) {
        self.name = name
        self.prefix = prefix
    }

    var fullName: String {  // Starship类把fullName属性实现为只读的计算型属性。
        return (prefix != nil ? prefix! + " " : "") + name
    }
}

let s = Starship(name: "Enterprise",prefix: "USS")
print(s.fullName)

方法要求

协议可以要求采纳协议的类型实现某些指定的实例方法或类方法。这些方法作为协议的一部分,像普通方法一样放在协议的定义中,但是不需要大括号和方法体可以在协议中定义具有可变参数的方法,和普通方法的定义方式相同。但是,不支持为协议中的方法的参数提供默认值

正如属性要求中所述,在协议中定义类方法的时候,总是使用static关键字作为前缀。当类类型采纳协议时,除了static关键字,还可以使用class关键字作为前缀。

protocol A {
    static func a()
}

protocol RandomNumberGenerator {
    func random() -> Double     // 尽管这里并未指明,但是我们假设返回值在[0.0,1.0)区间内。
}

/** * 一个实现了“线性同余生成器(linear congruential generator)”的伪随机数算法 */
class LinearCongruentialGenerator: RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
        lastRandom = (lastRandom * a + c) % m
        return lastRandom / m
    }
}

let generator = LinearCongruentialGenerator()
print(generator.random())
print(generator.random())
print(generator.random())
/* 0.37464991998171 0.729023776863283 0.636466906721536 */

在上面的代码中,实现了“线性同余生成器(linear congruential generator)”的伪随机数算法

mutating方法要求

有时需要在方法中改变方法所属的实例。例如,在值类型(即结构体和枚举)的实例方法中,将mutating关键字作为方法的前缀,写在func关键字之前,表示可以在该方法中修改它所属的实例以及实例的任意属性的值

如果你在协议中定义了一个实例方法,该方法会改变采纳该协议的类型的实例,那么在定义协议时需要在方法前加mutating关键字。这使得结构体和枚举能够采纳此协议并满足此方法要求。

注意,实现协议中的mutating方法时,若是类类型,则不用写mutating关键字。而对于结构体和枚举,则必须写mutating关键字。

protocol Togglable {
    mutating func toggle()
}

enum OnOffSwitch: Togglable {
    case On,Off
    mutating func toggle() {    // 当使用枚举或结构体来实现Togglable协议时,需要提供一个带有mutating前缀的toggle() 方法。
        switch self {
        case .On:
            self = .Off
        case .Off:
            self = .On
        }
    }
}

var o = OnOffSwitch.On
o.toggle()
print(o)

构造器要求

协议可以要求采纳协议的类型实现指定的构造器。你可以像编写普通构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体。

构造器要求在类中的实现

你可以在采纳协议的类中实现构造器,无论是作为指定构造器,还是作为便利构造器。无论哪种情况,你都必须为构造器实现标上required修饰符。使用required修饰符可以确保所有子类也必须提供此构造器实现,从而也能符合协议。

如果类已经被标记为final,那么不需要在协议构造器的实现中使用required修饰符,因为final类不能有子类。

如果一个子类重写了父类的指定构造器,并且该构造器满足了某个协议的要求,那么该构造器的实现需要同时标注requiredoverride修饰符。

protocol A {
    init()
}

class B {
    init() {
        print("B.init() called")
    }
}

class C: B,A {
    required override init() {
        print("C.init() called")
    }
}

可失败构造器要求

协议还可以为采纳协议的类型定义可失败构造器要求。采纳协议的类型可以通过可失败构造器(init?)或非可失败构造器(init来满足协议中定义的可失败构造器要求。协议中定义的非可失败构造器要求可以通过非可失败构造器(init)或隐式解包可失败构造器(init!来满足。

协议作为类型

尽管协议本身并未实现任何功能,但是协议可以被当做一个成熟的类型来使用。

协议可以像其他普通类型一样使用,使用场景如下:

  • 作为函数、方法或构造器中的参数类型或返回值类型
  • 作为常量、变量或属性的类型
  • 作为数组、字典或其他容器中的元素类型

注意,协议是一种类型,因此协议类型的名称应与其他类型(例如IntDoubleString)的写法相同,使用大写字母开头的驼峰式写法,例如(FullyNamedRandomNumberGenerator)。

protocol RandomNumberGenerator {
    func random() -> Double     // 尽管这里并未指明,但是我们假设返回值在[0.0,1.0)区间内。
}

/** * 一个实现了“线性同余生成器(linear congruential generator)”的伪随机数算法 */
class LinearCongruentialGenerator: RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
        lastRandom = (lastRandom * a + c) % m
        return lastRandom / m
    }
}

/** * 一个使用了线性同余生成器(linear congruential generator) 的伪随机数算法的骰子。 */
class Dice {
    let sides: Int
    let generator: RandomNumberGenerator
    init(sides: Int,generator: RandomNumberGenerator) {
        self.sides = sides
        self.generator = generator
    }
    func roll() -> Int {
        return Int(generator.random() * Double(sides)) + 1
    }
}

let d = Dice(sides: 6,generator: LinearCongruentialGenerator())
for _ in 0..<10 {
    print(d.roll())
}
/* 3 5 4 5 4 1 4 2 1 4 */

委托(代理)模式

委托是一种设计模式,它允许类或结构体将一些需要它们负责的功能委托给其他类型的实例。委托模式的实现很简单:定义协议来封装那些需要被委托的功能,这样就能确保采纳协议的类型能提供这些功能。委托模式可以用来响应特定的动作,或者接收外部数据源提供的数据,而无需关心外部数据源的类型。

protocol RandomNumberGenerator {
    func random() -> Double     // 尽管这里并未指明,但是我们假设返回值在[0.0,generator: RandomNumberGenerator) {
        self.sides = sides
        self.generator = generator
    }
    func roll() -> Int {
        return Int(generator.random() * Double(sides)) + 1
    }
}

// 两个基于骰子游戏的协议

/** * DiceGame协议可以被任意涉及骰子的游戏采纳。 */
protocol DiceGame {
    var dice: Dice { get }
    func play()
}

/** * DiceGameDelegate协议可以被任意类型采纳,用来追踪DiceGame的游戏过程。 */
protocol DiceGameDelegate {
    func gameDidStart(game: DiceGame)
    func game(game: DiceGame,didStartNewTurnWithDiceRoll diceRoll: Int)
    func gameDidEnd(game: DiceGame)
}

/** * 蛇梯棋游戏的新版本。新版本使用Dice实例作为骰子,并且实现了DiceGame和DiceGameDelegate协议,后者用来记录游戏的过程。 */
class SnakeAndLadders: DiceGame {
    let finalSquare = 25
    let dice = Dice(sides: 6,generator: LinearCongruentialGenerator())     // dice属性在构造之后就不再改变,且协议只要求dice为只读的,因此将dice声明为常量属性。
    var square = 0
    var board: [Int]
    init() {
        board = [Int](count: finalSquare + 1,repeatedValue: 0)
        board[03] = +08; board[06] = +11; board[09] = +09; board[10] = +02
        board[14] = -10; board[19] = -11; board[22] = -02; board[24] = -08
    }
    var delegate: DiceGameDelegate?     // delegate并不是游戏的必备条件,设置为可选
    func play() {
        square = 0
        delegate?.gameDidStart(self)    // 通过可选链式调用来调用它的方法
        gameLoop: while square != finalSquare {
            let diceRoll = dice.roll()
            delegate?.game(self,didStartNewTurnWithDiceRoll: diceRoll)
            switch square + diceRoll {
            case finalSquare:
                break gameLoop
            case let newSquare where newSquare > finalSquare:
                continue gameLoop
            default:
                square += diceRoll
                square += board[square]
            }
        }
        delegate?.gameDidEnd(self)
    }
}

class DiceGameTracker: DiceGameDelegate {
    var numberOfTurns = 0
    func gameDidStart(game: DiceGame) {     // game参数是DiceGame类型而不是SnakeAndLadders类型,所以在方法中只能访问DiceGame协议中的内容。
        numberOfTurns = 0
        if game is SnakeAndLadders {
            print("Started a new game of Snakes and Ladders")
        }
        print("The game is using a \(game.dice.sides)-sided dice")
    }
    func game(game: DiceGame,didStartNewTurnWithDiceRoll diceRoll: Int) {
        ++numberOfTurns
        print("Rolled a \(diceRoll)")
    }
    func gameDidEnd(game: DiceGame) {
        print("The game lasted for \(numberOfTurns) turns")
    }
}

let game = SnakeAndLadders()
let tracker = DiceGameTracker()
game.delegate = tracker
game.play()
/*伪随机数,每次运行结果是一样的 Started a new game of Snakes and Ladders The game is using a 6-sided dice Rolled a 3 Rolled a 5 Rolled a 4 Rolled a 5 The game lasted for 4 turns */

通过扩展添加协议一致性

即便无法修改源代码,依然可以通过扩展令已有类型采纳并符合协议。扩展可以为已有类型添加属性、方法、下标以及构造器,因此可以符合协议中的相应要求。

通过扩展令已有类型采纳并符合协议时,该类型的所有实例也会随之获得协议中定义的各项功能。

通过扩展采纳并符合协议,和在原始定义中采纳并符合协议的效果完全相同。协议名称写在类型名之后,以冒号隔开,然后在扩展的大括号内实现协议要求的内容。

protocol RandomNumberGenerator {
    func random() -> Double     // 尽管这里并未指明,但是我们假设返回值在[0.0,didStartNewTurnWithDiceRoll: diceRoll)
            switch square + diceRoll {
            case finalSquare:
                break gameLoop
            case let newSquare where newSquare > finalSquare:
                continue gameLoop
            default:
                square += diceRoll
                square += board[square]
            }
        }
        delegate?.gameDidEnd(self)
    }
}

/** * 任何想要通过文本表示一些内容的类型都可以实现该协议。这些想要表示的内容可以是实例本身的描述,也可以是实例当前状态的文本描述 */
protocol TextRepresentable {
    var textualDescription: String { get }
}

/** * 可以通过扩展,令先前提到的Dice类采纳并符合TextRepresentable协议 */
extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}

let d = Dice(sides: 12,generator: LinearCongruentialGenerator())
print(d.textualDescription)     // A 12-sided dice

extension SnakeAndLadders: TextRepresentable {
    var textualDescription: String {
        return "A game of Snakes and Ladders with \(finalSquare) squares"
    }
}

let s = SnakeAndLadders()
print(s.textualDescription)     // A game of Snakes and Ladders with 25 squares

通过扩展采纳协议

当一个类型已经符合了某个协议中的所有要求,却还没有声明采纳该协议时,可以通过空扩展体的扩展来采纳该协议

即使满足了协议的所有要求,类型也不会自动采纳协议,必须显式地采纳协议

protocol TextRepresentable {
    var textualDescription: String { get }
}

struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}

extension Hamster: TextRepresentable {}

// 现在Hamster实例可以作为TextRepresentable类型使用了!
let h = Hamster(name: "Tim")
let t: TextRepresentable = h
print(t.textualDescription)     // A hamster named Tim

协议类型的集合

协议类型可以在数组或者字典这样的集合中使用。

protocol RandomNumberGenerator {
    func random() -> Double     // 尽管这里并未指明,但是我们假设返回值在[0.0,didStartNewTurnWithDiceRoll: diceRoll)
            switch square + diceRoll {
            case finalSquare:
                break gameLoop
            case let newSquare where newSquare > finalSquare:
                continue gameLoop
            default:
                square += diceRoll
                square += board[square]
            }
        }
        delegate?.gameDidEnd(self)
    }
}

/** * 任何想要通过文本表示一些内容的类型都可以实现该协议。这些想要表示的内容可以是实例本身的描述,也可以是实例当前状态的文本描述 */
protocol TextRepresentable {
    var textualDescription: String { get }
}

/** * 可以通过扩展,令先前提到的Dice类采纳并符合TextRepresentable协议 */
extension Dice: TextRepresentable {
    var textualDescription: String {
        return "A \(sides)-sided dice"
    }
}

extension SnakeAndLadders: TextRepresentable {
    var textualDescription: String {
        return "A game of Snakes and Ladders with \(finalSquare) squares"
    }
}

struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}

extension Hamster: TextRepresentable {}

let s = SnakeAndLadders()
let d = Dice(sides: 6,generator: LinearCongruentialGenerator())
let h = Hamster(name: "Tim")
let ts: [TextRepresentable] = [s,h,d]     // 必须显式声明类型!?否则报出现歧义的错误
for item in ts {
    print(item.textualDescription)  // 任何TextRepresentable的实例都有一个textualDescription属性,所以在每次循环中可以安全地访问textualDescription属性
}
/* A game of Snakes and Ladders with 25 squares A hamster named Tim A 6-sided dice */

协议的继承

协议能够继承一个或多个其他协议,可以在继承的协议的基础上增加新的要求。协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔。

protocol RandomNumberGenerator {
    func random() -> Double     // 尽管这里并未指明,但是我们假设返回值在[0.0,didStartNewTurnWithDiceRoll: diceRoll)
            switch square + diceRoll {
            case finalSquare:
                break gameLoop
            case let newSquare where newSquare > finalSquare:
                continue gameLoop
            default:
                square += diceRoll
                square += board[square]
            }
        }
        delegate?.gameDidEnd(self)
    }
}

protocol TextRepresentable {
    var textualDescription: String { get }
}

extension SnakeAndLadders: TextRepresentable {
    var textualDescription: String {
        return "A game of Snakes and Ladders with \(finalSquare) squares"
    }
}

protocol PrettyTextRepresentable: TextRepresentable {
    var prettyTextualDescription: String { get }
}

extension SnakeAndLadders: PrettyTextRepresentable {
    var prettyTextualDescription: String {
        var output = self.textualDescription + ":\n"
        for i in 1...finalSquare {
            switch board[i] {
            case let ladder where ladder > 0:
                output += "▲、"
            case let ladder where ladder < 0:
                output += "▼、"
            default:
                output += "○、"
            }
        }
        return output
    }
}

let s = SnakeAndLadders()
print(s.prettyTextualDescription)
/* A game of Snakes and Ladders with 25 squares: ○、○、▲、○、○、▲、○、○、▲、▲、○、○、○、▼、○、○、○、○、▼、○、○、▼、○、▼、○、 */

类类型专属协议

可以在协议的继承列表中,通过添加class关键字来限制协议只能被类类型采纳,而结构体或枚举不能采纳该协议class关键字必须第一个出现在协议的继承列表中,在其他继承的协议之前。

当协议定义的要求需要采纳协议的类型必须是引用语义而非值语义时,应该采用类类型专属协议。

protocol A {

}

protocol B: class,A {

}

协议合成

有时候需要同时采纳多个协议,你可以将多个协议采用protocol<SomeProtocol,AnotherProtocol>这样的格式进行组合,称为协议合成(protocol composition)。你可以在<>中罗列任意多个你想要采纳的协议,以逗号分隔。

协议合成并不会生成新的、永久的协议类型,而是将多个协议中的要求合成到一个只在局部作用域有效的临时协议中

protocol Named {
    var name: String { get }
}

protocol Aged {
    var age: Int { get }
}

struct Person: Named,Aged {
    var name: String
    var age: Int
}

func wishHappyBirthday(celebrator: protocol<Named,Aged>) {     // 不关心参数的具体类型,只要参数符合这两个协议即可
    print("Happy birthday \(celebrator.name) -- you are \(celebrator.age)")
}

let p = Person(name: "TIm",age: 23)
wishHappyBirthday(p)    // Happy birthday TIm -- you are 23

检查协议一致性

可以使用类型转换中描述的isas操作符来检查协议一致性,即是否符合某协议,并且可以转换到指定的协议类型。检查转换到某个协议类型在语法上和类型的检查和转换完全相同:

  • is用来检查实例是否符合某个协议,若符合则返回true,否则返回false
  • as?返回一个可选值,当实例符合某个协议时,返回类型为协议类型的可选值,否则返回nil
  • as!将实例强制向下转换到某个协议类型,如果强转失败,会引发运行时错误
protocol HasArea {
    var area: Double { get }
}

class Circle: HasArea {
    let pi = 3.1415927
    var radius: Double
    var area: Double { return radius * radius }     // 计算型属性
    init(radius: Double) { self.radius = radius }
}

class Country: HasArea {
    var area: Double
    init(area: Double) { self.area = area }         // 存储型属性
}

class Animal{
    var legs: Int
    init(legs: Int) { self.legs = legs }
}

let objects: [AnyObject] = [
    Circle(radius: 3.2),Country(area: 123456.0),Animal(legs: 4)
]

for o in objects {
    if let oo = o as? HasArea {
        print(oo.area)
    } else {
        print("area没有值")
    }
}

可选的协议要求

协议可以定义可选要求,采纳协议的类型可以选择是否实现这些要求。在协议中使用optional关键字作为前缀来定义可选要求。使用可选要求时(例如,可选的方法或者属性),它们的类型会自动变成可选的。比如,一个类型为(Int) -> String的方法会变成((Int) -> String)?。需要注意的是整个函数类型是可选的,而不是函数的返回值。

协议中的可选要求可通过可选链式调用来使用,因为采纳协议的类型可能没有实现这些可选要求。类似someOptionalMethod?(someArgument)这样,你可以在可选方法名称后加上?来调用可选方法。

注意,可选的协议要求只能用在标记@objc特性的协议中。该特性表示协议将暴露给Objective-C代码,详情参见Using Swift with Cocoa and Objective-C(Swift 2.1)。即使你不打算和Objective-C有什么交互,如果你想要指定可选的协议要求,那么还是要为协议加上@obj特性。

还需要注意的是,标记@objc特性的协议只能被继承自Objective-C类的类或者@objc类采纳,其他类以及结构体和枚举均不能采纳这种协议。

import Foundation   // @objc需要导入Foundation框架
@objc protocol CounterDataSource {
    optional func incrementForCount(count: Int) -> Int  // 可选的函数类型:(Int -> Int)?
    optional var fixedIncrement: Int { get }            // 可选的Int:Int?
}

class Counter {
    var count = 0
    var dataSource: CounterDataSource?
    func increment() {
        if let amount = dataSource?.incrementForCount?(count) {
            count += amount
        } else if let amount = dataSource?.fixedIncrement {
            count += amount
        }
    }
}

class ThreeSource: NSObject,CounterDataSource {
    let  fixedIncrement = 3
}

let counter = Counter()
counter.dataSource = ThreeSource()
for _ in 0..<3 {
    counter.increment()
    print(counter.count)
}
/* 3 6 9 */

print("")

@objc class TowardsZeroSource: NSObject,CounterDataSource {
    func incrementForCount(count: Int) -> Int {
        if count == 0 {
            return 0
        } else if count < 0 {
            return 1
        } else {
            return -1
        }
    }
}

let a = Counter()
a.count = -4
a.dataSource = TowardsZeroSource()
for _ in 0..<5 {
    a.increment()
    print(a.count)
}
/* -3 -2 -1 0 0 */

严格来讲,CounterDataSource协议中的方法和属性都是可选的,因此采纳协议的类可以不实现这些要求,尽管技术上允许这样做,不过最好不要这样写。

协议扩展

协议可以通过扩展来为采纳协议的类型提供属性、方法以及下标的实现。通过这种方式,你可以基于协议本身来实现这些功能,而无需在每个采纳协议的类型中都重复同样的实现,也无需使用全局函数。

protocol RandomNumberGenerator {
    func random() -> Double
}

/** * 一个实现了“线性同余生成器(linear congruential generator)”的伪随机数算法 */
class LinearCongruentialGenerator: RandomNumberGenerator {
    var lastRandom = 42.0
    let m = 139968.0
    let a = 3877.0
    let c = 29573.0
    func random() -> Double {
        lastRandom = (lastRandom * a + c) % m
        return lastRandom / m
    }
}

extension RandomNumberGenerator {
    func randomBool() -> Bool {
        return random() > 0.5
    }
}



let l = LinearCongruentialGenerator()
print(l.random())       // 0.37464991998171
print(l.randomBool())   // true,即使扩展是在LinearCongruentialGenerator定义之后也可以正常调用

提供默认实现

可以通过协议扩展来为协议要求的属性、方法以及下标提供默认的实现。如果采纳协议的类型为这些要求提供了自己的实现,那么这些自定义实现将会替代扩展中的默认实现被使用。

注意,通过协议扩展为协议要求提供的默认实现和可选的协议要求不同。虽然在这两种情况下,采纳协议的类型都无需自己实现这些要求,但是通过扩展提供的默认实现可以直接调用,而无需使用可选链式调用。

protocol TextRepresentable {
    var textualDescription: String { get }
}

protocol PrettyTextRepresentable: TextRepresentable {
    var prettyTextualDescription: String { get }
}

extension PrettyTextRepresentable {
    var prettyTextualDescription: String {
        return textualDescription   // 默认的prettyTextualDescription属性,只是简单地返回textualDescription属性的值
    }
}

为协议扩展添加限制条件

在扩展协议的时候,可以指定一些限制条件,只有采纳协议的类型满足这些限制条件时,才能获得协议扩展提供的默认实现。这些限制条件写在协议名之后,使用where子句来描述。

如果多个协议扩展都为同一个协议要求提供了默认实现,而采纳协议的类型又同时满足这些协议扩展的限制条件,那么将会使用限制条件最多的那个协议扩展提供的默认实现。

protocol TextRepresentable {
    var textualDescription: String { get }
}

struct Hamster {
    var name: String
    var textualDescription: String {
        return "A hamster named \(name)"
    }
}

extension Hamster: TextRepresentable {}

/** * 扩展CollectionType协议,但是只适用于集合中的元素采纳了TextRepresentable协议的情况 */
extension CollectionType where Generator.Element: TextRepresentable {
    var textualDescription: String {
        let itemAsText = self.map { $0.textualDescription }
        return "[" + itemAsText.joinWithSeparator(",") + "]"
    }
}

let tim = Hamster(name: "Tim")
let kate = Hamster(name: "Kate")
let jack = Hamster(name: "Jack")
let hs = [tim,kate,jack] print(hs.textualDescription) // [A hamster named Tim,A hamster named Kate,A hamster named Jack],Array符合CollectionType协议,而数组中的元素又符合TextRepresentable协议

ACM International Collegiate Programming Contest, Damascus University Collegiate Programming Cont...

ACM International Collegiate Programming Contest, Damascus University Collegiate Programming Cont...

签到题


A

4min 1Y

C

45min 3Y

题意

给两个串,要把第一个串变成第二个串。每次选择一个半径r,然后以第一个串的中心为中心,r为半径,左右翻转。问最少几次操作?

题解

细节有点多。

  • 先是输出-1的情况。这个很好考虑
  • 然后遍历s1串,对于位置i,如果需要翻转(s1[i]=s2[n+1-i]),则打上标记1,不需要翻转(s1[i]=s2[i]).则打上标记0,如果翻不翻转都可以(s1[i]=s1[n+1-i]),打上标记2。
  • 遍历[1,n/2],对于打上标记2的位置,我们要决定是翻还是不翻,根据贪心,我们可以怠惰一点!对于打上标记2的位置,和前一个位置保持一致即可。

F

25min 1Y

题解

统计一下每个数字出现了多少次,出现了x次,对答案的贡献则为x!,相乘即为答案。

J

42min 2Y

题解

按题意模拟。

冷静分析一下开局

  • 开场看了C觉得很基本,然后过了A,开始写C。
  • C细节没考虑清楚,用了10min,WA on test 2!
#include <iostream>
#include <cstring>
using namespace std;
typedef long long LL;
const int N = 100000 + 10;
int T;
char s1[N], s2[N], s3[N];
bool ok[N];
int main() {
    scanf("%d", &T);
    while (T --) {
        scanf("%s %s", s1+1, s2+1);

        int n = strlen(s1+1);
        int mid = (n+1)/2;
        if (s1[mid] != s2[mid]) {
            printf("-1\n"); continue;
        }
        bool gg = 0;
        for (int i=1;i<mid;i++) {
            if (s1[i]==s2[i] && s1[n-i+1]==s2[n-i+1])
                ok[i] = 1;
            else if (s1[i]==s2[n-i+1] && s1[n-i+1]==s2[i])
                ok[i] = 0;
            else 
                gg = 1;
        }
        if (gg) {
            printf("-1\n"); continue;
        }

        int ans = 0;
        ok[0] = 1;
        for(int i=1;i<mid;i++) {
            if (ok[i] != ok[i-1])
                ans ++;
        }
        printf("%d\n", ans);
    }
}

完全没有考虑s1[i]=s1[n+1-i]的情况。

用了6分钟fix了一下。s1[i]=s1[n+1-i]

int ans = 0;
ok[0] = 1;
for(int i=1;i<mid;i++) {
    if (ok[i] != ok[i-1] && ok[i] != 2)
        ans ++;
}
printf("%d\n", ans);

然后喵了个呜,又一次WA2

  • 我逃跑,用了5min时间过了F
  • 用了10min的时间J题WA2
  • 用了5min的时间fix了一下,人调换方向那个地方写得意识有点模糊。
  • 用了2min时间fix了一下C,很沙比的错误,比如说n = 7,前3位,标记为0 2 0的情况就智障掉了。

前期比较容易细节没考虑清楚就上去写代码。C,J这两个模拟题都是这个原因吧。开始写代码之间,大概是抱着“随便搞搞就好”的想法。这种态度很沙比的啊。

  • 我在求什么?
  • 在模拟的过程,变量会如何变化?

这两个问题都考虑得不清楚吧!

中档题


B

Hash的做法很容易看出。然后就unlimited WA TLE MLE

搞Hash的经验严重不足?

边的种数不会超过n种,因此我们放n个桶。每个桶记录这种边出现的次数。

然后就是对一个XXX进制下,n位数的hash了【双hash保平安】

#include <iostream>
#include <algorithm>
#include <vector>
#include <unordered_map>

using namespace std;
typedef long long LL;

const int N = 10000008;
const LL MOD1 = 100000007;
const LL MOD2 = 923439347;

int T;
int n, x, y;
int val[N]; vector<int> v;
LL k1[N], k2[N];

unordered_map< LL, int > mp;

int main() {
    scanf("%d", &T);
    
    k1[0] = k2[0] = 1;
    for(int i=1;i<N;i++) {
        k1[i]=k1[i-1]*10007LL%MOD1;
        k2[i]=k2[i-1]*20007LL%MOD2;
    }
    while (T --) {
        scanf("%d", &n);
        v.clear();
        for (int i = 1; i <= n; i ++) {
            scanf("%d %d", &x, &y);
            if (x > y) swap(x, y);
            val[i] = (2*n+1)*x + y;
            v.push_back(val[i]);
        }
        sort(v.begin(), v.end());
        v.erase(unique(v.begin(), v.end()), v.end());
        for(int i=1;i<=n;i++) {
            val[i] = lower_bound(v.begin(), v.end(), val[i])-v.begin()+1;
        }
        mp.clear();
        LL sum1, sum2;
        LL ans=0;
        for (int i=1;i<=n;i++) {
            sum1 = 0, sum2 = 0;
            for(int j=1;j<=n;j++) {
                sum1 += k1[val[j]]; 
                sum1 %= MOD1;
                sum2 += k2[val[j]];
                sum2 %= MOD2;
            }
            mp[sum1*MOD2 + sum2] ++;
            for(int j=i+1;j<=n;j++) {
                sum1 += k1[val[j]]-k1[val[j-i]]; 
                sum1 = (sum1%MOD1+MOD1)%MOD1;
                sum2 += k2[val[j]]-k2[val[j-i]];
                sum2 = (sum2%MOD2+MOD2)%MOD2;
                mp[sum1*MOD2+sum2] ++;
            }
            for (auto x: mp) {
                LL v = x.second;
                ans += v * (v - 1) / 2;
            }
            mp.clear();
        }


        printf("%lld\n", ans);
    }
}

G

题意

修改最少的元素,使得序列的GCD为x,LCM为y。

题解

先判-1(一番激烈的讨论)

如果a[i]%x!=0 or y%a[i]!=0,那么i就是个卜。

直觉告诉我们把一个数字变成x,另一个数字变成y,只要其它的数字不卜,生活就有保障了。

  • 如果卜的个数>=2,那么答案就是卜的个数了。
  • 否则答案可能是1,可能是2,可能是0
  • 答案是0,很简单。
  • 答案是1,很不简单。我们枚举一下每个数字,看他是否能力挽狂澜,我们把枚举的数字放逐掉,求出其它数字的GCD&LCM(先预处理前缀后缀GCD&LCM),然后看一看世界末日前怎么办?来得及拯救吗?【具体怎么做,留给读者思考。】
  • 答案是2,很简单,加个else

I

题意

n堆石头,两种操作,两人轮流操作。

  • 可以从一堆石头中拿走一个石头。
  • 如果每堆石子都至少有1个,可以从每堆石头中拿走一个石头。

先手胜?后手胜?

题解

冷静分析一下

  • n%2=1. 易证,留给读者思考【读者似乎就我一个人。】
  • n%2=0. 这个得冷静分析一下。
  • min=1, 先手可以把后手气死。类似于chomp Game的模型。
  • min=2, 第一种操作肯定不可以施展的!不然会被气死。既然只能施展第二种操作,胜负显而易见了吧。
  • min=3, 先手可以把后手气死。类似于chomp Game的模型。
  • min=4, .......

博弈在模型的直觉很重要的吖!这题意识到了chomp Game就很简单了吧。

K

题意

n个点,n条边,多组查询,求两点间最短路。

题解

先去掉一条边edge,那么这个图就是一颗树了。

枚举,u到v是否要经过edge即可。

冷静分析一下中期

  • 先用了10min施展G,施展了一大半,然后咕咕咕了。
  • 先用了10min施展B题。没考虑清楚自己在hash什么东西,耽误了一段时间,然后WA11
  • 用了13min过了K。
  • 用了3min过了G的样例,WA2
  • 开始unlimited Fix G,中间用了很长一段时间次饭去了。但调G的时间至少有1小时。
    • Bug1:没读完就输出结果,WA
    • Bug2:复杂度nsqrt(n).【不错,有创意】
    • Bug3:n=1的特判不到位,搞得后面越界了。一大波RE。
  • 用了一个小时Fix B题的Hash,好不容易开始双hash然后MLE。枚举长度后清空,解决了问题。
  • 用了半个小时搞I

emmmm....大部分时间都在Fix辣鸡。。。

考虑清楚再写啊······

羊肉粉丝汤弱鸡啊。

感觉30min 4题,2h30min8题是比较合理的。


ACM International Collegiate Programming Contest, Egyptian Collegiate Programming Contest (ECPC 2...

ACM International Collegiate Programming Contest, Egyptian Collegiate Programming Contest (ECPC 2...

A.Arcade Game(康拓展开)

题意:

  给出一个每个数位都不同的数n,进行一场游戏。每次游戏将n个数的每个数位重组。如果重组后的数比原来的数大则继续游戏,否则算输。如果重组后的数是最大的数则算赢,问赢的概率。

题解:

  用康拓展开求出n是第几大的数,然后递推后面的概率。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t;
char s[15];
double ans;
int fac[10] = {1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880};
double cal(char *s) {
    int res = 0;
    int k = strlen(s);
    for(int i = 0; i < k; i++) {
        int cnt = 0;
        for(int j = i+1; j < k; j++) if(s[j]<s[i]) cnt++;
        res += fac[k-i-1]*cnt;
    } 
    if(res==fac[k]-1) return 0;
    double ans = 1.0/fac[k];
    for(int i = res; i < fac[k]-2; i++) ans += ans/fac[k];
    return ans;
}
int main() {
    scanf("%d", &t);
    while(t--) {
        scanf("%s", s);
        printf("%.9lf\n", cal(s));
    }
}
View Code

 

B.Unlucky Teacher(模拟)

题意:

  Q个题目和M个学生的判卷,求出每道题的答案。如果求不出则输出?。

题解:

  模拟即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t;
int q, m;
int num[105], state[105];
char ans[105];
char s1[2], s2[2];
int main() {
    scanf("%d", &t);
    while(t--) {
        memset(state, 0, sizeof(state));
        memset(num, 0, sizeof(num));
        scanf("%d%d", &q, &m);
        while(m--) {
            for(int i = 1; i <= q; i++) {
                scanf("%s%s", s1, s2);
                if(s2[0]==''T'') num[i] = -1, ans[i] = s1[0];
                else {
                    if((num[i]==-1)||((state[i]&(1<<s1[0]-''A''))>0)) continue;
                    state[i] |= 1<<s1[0]-''A'';
                    num[i]++;
                    if(num[i]==3) {
                        for(int j = 0; j < 4; j++) 
                        if(!(state[i]&(1<<j))) {
                            ans[i] = ''A''+j;
                            break;
                        }
                        num[i] = -1;
                    }        
                }
            }
        }
        for(int i = 1; i <= q; i++) {
            if(num[i]>-1) printf("?");
            else printf("%c", ans[i]);
            if(i < q) printf(" ");
        }
        puts("");
    }
}
View Code

 

C.Connecting Graph(并查集+二分)

题意:

  初始有n个点,m次操作。每次操作加一条边或者询问两个点第一次连通的时刻(若不连通输出-1)。

题解:

  GYM - 100814 C.Connecting Graph

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5+10;
int t;
int n, m;
int u, v, k;
int f[N], num[N];
vector<pair<int, int> > g[N];
vector<int> c[N];
bool check(int x) {
    int l = 0, r = g[u].size()-1;
    while(l <= r) {
        int mid = l+r>>1;
        if(g[u][mid].first <= x) l = mid+1;
        else r = mid-1;
    }
    int p1 = g[u][r].second;
    l = 0, r = g[v].size()-1;
    while(l <= r) {
        int mid = l+r>>1;
        if(g[v][mid].first <= x) l = mid+1;
        else r = mid-1;
    }
    int p2 = g[v][r].second;
    if(p1==p2) return 1;
    return 0;
}
int main() {
    scanf("%d", &t);
    while(t--) {
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++) {
            num[i] = 1;
            f[i] = i;
            c[i].clear();
            g[i].clear();
            c[i].push_back(i);
            g[i].push_back(make_pair(0, i));
        }
        for(int i = 1; i <= m; i++) {
            scanf("%d%d%d", &k, &u, &v);
            if(k&1) {
                u = f[u]; v = f[v];
                if(u!=v) {
                    if(num[u]>num[v]) swap(u, v);
                    for(int j = 0; j < num[u]; j++) {
                        c[v].push_back(c[u][j]);
                        f[c[u][j]] = v;
                        g[c[u][j]].push_back(make_pair(i, v));
                    }
                    num[v] += num[u];
                    num[u] = 0;
                    c[u].clear();
                }
            }
            else {
                int l = 0, r = i-1;
                while(l<=r) {
                    int mid = l+r>>1;
                    if(check(mid)) r = mid-1;
                    else l = mid+1;
                }
                if(check(r+1)) printf("%d\n", r+1);
                else puts("-1");
            }
        }
    }    
}
View Code

 

D.Frozen Rivers

题意:

  一棵n个节点的树,每条边代表一条河。从点1开始边以每秒1个单位开始融化。每个点连的边(不包括连向父亲的)有一条融化完时剩下的该点连的边融化速度降为0.5。q次询问,每次询问某个时刻融化到叶子节点的数量。

题解:

  设minn[u]代表节点u的边中权值最小的那个,将点u所有边的权值加上他们与minn[u]的差值。即每条边的权值翻倍再减去minn[u]。

  这样处理完之后就省去了0.5的限制。问题变成了求叶子节点到根节点的距离。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5+10;
const int inf = 0x3f3f3f3f;
int t;
int n, q, p, c;
int tot;
int head[N], to[N], nxt[N], w[N], minn[N];
int cnt;
ll tim, num[N];
void dfs(int u, ll val) {
    if(minn[u]==inf) {
        num[++cnt] = val;
        return ;
    }
    for(int i = head[u]; ~i; i = nxt[i]) {
        w[i] = 2*w[i]-minn[u];
        dfs(to[i], val+w[i]);
    }
}
int main() {
    scanf("%d", &t);
    while(t--) {
        tot = cnt = 0;
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) head[i] = -1, minn[i] = inf;
        for(int i = 2; i <= n; i++) {
            scanf("%d%d", &p, &c);
            to[++tot] = i; nxt[tot] = head[p]; head[p] = tot, w[tot] = c;
            minn[p] = min(minn[p], c);
        }
        dfs(1, 0);
        sort(num+1, num+cnt+1);
        scanf("%d", &q);
        while(q--) {
            scanf("%lld", &tim);
            int ans = upper_bound(num+1, num+cnt+1, tim)-num-1;
            printf("%d\n", ans);
        }
    }
}
View Code

 

E.Palmyra(dp)

题意:

  给出n*m的矩阵。从点(1,1)出发,可以向右或者向下移动,最后走到(n,m)。将路途上的点值乘起来,问最后的值拿6进制表示末尾最多有几个0。

题解:

  题意可以理解为,使最后2的因子数和3的因子数中的最小值最大。

  dp[i][j][k]表示走到(i,j),3的因子数为k时2的因子数最多是多少。

#include <bits/stdc++.h>
using namespace std;
int t;
int n, m;
int q[105][105][3];
int dp[105][105][1505];
int main() {
    scanf("%d", &t);
    while(t--) {
        memset(q, 0, sizeof(q));
        memset(dp, -1, sizeof(dp)); 
        scanf("%d%d", &n, &m);
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= m; j++) {
                scanf("%d", &q[i][j][0]);
                int t = q[i][j][0];
                while(t%2 == 0) {
                    q[i][j][1]++;
                    t /= 2;
                }
                while(t%3 == 0) {
                    q[i][j][2]++;
                    t /= 3;
                }
            }
        }
        dp[1][1][q[1][1][2]] = q[1][1][1];
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= m; j++) {
                int n2 = q[i][j][1]; 
                int n3 = q[i][j][2]; 
                for(int k = 0; k + n3 <= 1500; k++) {
                    if(dp[i][j-1][k] != -1)
                        dp[i][j][k+n3] = max(dp[i][j][k+n3], dp[i][j-1][k]+n2);
                    if(dp[i-1][j][k] != -1)
                        dp[i][j][k+n3] = max(dp[i][j][k+n3], dp[i-1][j][k]+n2);
                }
            }
        }
        int ans = 0;
        for(int i = 1; i <= 1500; i++) {
            int nn = min(dp[n][m][i], i);
            ans = max(ans, nn);
        }
        printf("%d\n", ans);
    }
    return 0;
}
View Code

 

F.Geometry

题意:

  给出长和宽,判断时正方形还是矩形。

题解:

  判断w是否等于h。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t;
int w, h;
int main() {
    scanf("%d", &t);
    while(t--) {
        scanf("%d%d", &w, &h);
        if(w==h) puts("Square");
        else puts("Rectangle");
    }
}
View Code

 

G.It is all about wisdom(最短路+二分)

题意:

  给出一个图,图中的每条边有使用的最低限制值和花费。问从1走到n在总花费小于k的前提下的最小限制值是多少。

题解:

  标准的二分套最短路的题型。二分最小的限制值即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5+10;
const int inf = 0x3f3f3f3f;
int t;
int n, m, k;
int u, v, c, l, r;
int vis[N], dis[N];
struct node {
    int to, v, lim;
    node(int a, int b, int c) {
        to = a; v = b; lim = c;
    }
};
vector<node> g[N];
bool check(int x) {
    queue<int> q;
    for(int i = 1; i <= n; i++) vis[i] = 0, dis[i] = inf;
    q.push(1);
    vis[1] = 1;
    dis[1] = 0;
    while(!q.empty()) {
        int v = q.front();
        q.pop();
        vis[v] = 0;
        int len = g[v].size();
        for(int i = 0; i < len; i++) {
            if(g[v][i].lim > x) continue;
            if(g[v][i].v+dis[v] < dis[g[v][i].to]) {
                dis[g[v][i].to] = g[v][i].v+dis[v];
                if(!vis[g[v][i].to]) {
                    q.push(g[v][i].to);
                    vis[g[v][i].to] = 1;
                }
            }
        }
    }
    if(dis[n] < k) return 1;
    return 0;
}
int main() {
    scanf("%d", &t);
    while(t--) {
        scanf("%d%d%d", &n, &m, &k);
        r = 0;
        for(int i = 1; i <= n; i++) g[i].clear();
        while(m--) {
            scanf("%d%d%d%d", &u, &v, &c, &l);
            g[u].push_back(node(v, c, l));
            g[v].push_back(node(u, c, l));
            r = max(r, l);
        }
        l = 1;
        while(l<=r) {
            int mid = l+r>>1;
            if(check(mid)) r = mid-1;
            else l = mid+1;
        }
        if(check(r+1)) printf("%d\n", r+1);
        else puts("-1");
        
    }
}
View Code

 

I.Salem

题意:

  给出n个数,求数对中最大的hamming距离。

题解:

  每两个数求一下异或之后二进制下1个数量即可,输出最大值。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t, n;
int a[105];
int main() {
    scanf("%d", &t);
    while(t--) {
        int ans = 0;
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) {
            scanf("%d", &a[i]);
            for(int j = 1; j < i; j++) {
                int p = a[i]^a[j], cnt = 0;
                for(int k = 20; k >= 0; k--) {
                    if(p&(1<<k)) cnt++;
                }
                ans = max(ans, cnt);
            }
        }
        printf("%d\n", ans);
    }
}
View Code

 

J.Game

题意:

  给出合并规则表。两个人轮流进行操作,每次选择从最左面或者最右面开始每两个合并成一个。如果最后剩的是元音字符,就是Salah获胜。否则Marzo获胜。

题解:

  暴力维护每一种情况。用1表示S获胜,0表示M获胜。

  当S操作时,若两种情况存在1,则当前为1。

  当M操作时,若两种情况存在0,则当前为0。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e4+10;
int t;
int tot;
int len[N];
char s[N][N];
char g[30][30];
char cmp[5] = {''a'', ''e'', ''i'', ''o'', ''u''};
bool dfs(int num, int k) {
    if(len[num] < 3) {
        char c;
        if(len[num]==1) c = s[num][0];
        else c = g[s[num][0]-''a''][s[num][1]-''a''];
        for(int i = 0; i < 5; i++) if(c==cmp[i]) return true;
        return false;
    }
    ++tot;
    for(int i = 0; i < len[num]; i+=2) {
        if(i==len[num]-1) s[tot][i/2] = s[num][i];
        else s[tot][i/2] =  g[s[num][i]-''a''][s[num][i+1]-''a''];
    }
    len[tot] = (len[num]+1)/2;
    bool res = dfs(tot, k^1);
    if(len[num]&1) {
        ++tot;
        s[tot][0] = s[num][0];
        for(int i = 1; i < len[num]; i+=2) {
            s[tot][i/2+1] =  g[s[num][i]-''a''][s[num][i+1]-''a''];
        }
        len[tot] = (len[num]+1)/2;
        if(k) res &= dfs(tot, k^1);
        else res |= dfs(tot, k^1);
    }
    return res;
}
int main() {
    scanf("%d", &t);
    while(t--) {
        tot = 0;
        for(int i = 0; i < 26; i++) scanf("%s", g[i]);
        scanf("%s", s[0]);
        len[0] = strlen(s[0]);
        if(dfs(0, 0)) puts("Salah");
        else puts("Marzo");
    }
}
View Code

 

K.PhD math

题意:

  给出a,b,n,p(a<b)。求a/b的前n位数中有多少字串整除p。

题解:

  从1扫到n。维护每一位新增的余数。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6+10;
int t;
ll a, b;
int n, p;
int bit[N];
int v1[205], v2[205];
ll ans;
int main() {
    scanf("%d", &t);
    while(t--) {
        ans = 0;
        memset(v1, 0, sizeof(v1));
        memset(v2, 0, sizeof(v2));
        scanf("%lld%lld%d%d", &a, &b, &n, &p);
        for(int i = 1; i <= n; i++) {
            a *= 10;
            bit[i] = a/b;
            a = a%b;
        }
        for(int i = 1; i <= n; i++) {
            for(int j = 0; j < p; j++) {
                if(i&1) v1[j] = 0;
                else v2[j] = 0;
            }
            for(int j = 0; j < p; j++) {
                if(i&1) v1[(j*10+bit[i])%p] += v2[j];
                else v2[(j*10+bit[i])%p] += v1[j];
            }
            if(i&1) v1[bit[i]%p]++, ans += v1[0];
            else v2[bit[i]%p]++, ans += v2[0];
        }
        printf("%lld\n", ans);
    }
}
View Code

 

L.Candy Jars(博弈)

题意:

  N个罐子,每个罐子有一定数量的糖。两个人轮流操作,每次选定一罐,把其他罐中的糖都扔掉。然后把选定罐中的糖任意分配给每个罐,但要保证每个罐中都有糖。不能操作者判输。

题解:

  只要有一个罐子糖数必胜则操作者必胜。

  当所有罐子糖数小于N时无法给所有罐子分配糖,必输。

  当存在罐子糖数在[N,N(N-1)]时,可以把糖分成必输态,即分成所有罐子糖数小于N的状态,这时必胜。

  然后举例发现N(N-1)是一个循环节,取模就可以了。

#include <bits/stdc++.h>
using namespace std;
int t, n;
int k;
int main() {
    scanf("%d", &t);
    while(t--) {
        scanf("%d", &n);
        int ans = 0;
        for(int i = 1; i <= n; i++) {
            scanf("%d", &k);
            k %= n*(n-1);
            if(k==0 || k > n-1) ans = 1;
        }
        if(ans) puts("Alice");
        else puts("Bob");
    }
}
View Code

 

M.Building Force Fields(dp)

题意:

  按x升序给出n个点的二维坐标,并保证没有两个点x坐标相同。可以在任意两个点之间连边,最后要保证每个点都在连边之下(或在连边上)。问最小的连边总长。

题解:

  dp[i]表示第i个点结尾的最小总连边长。

  转移是枚举i向第j(1<=j<i)个点连边,要保证连边上方无点。即第i和第j个点的斜率比第i个点和(j,i)范围内的点的斜率都小。最后取最小值。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int t, n;
double dp[1005];
struct node {
    ll x, y;
}a[1005];
double dis(int n1, int n2) {
    return sqrt((a[n1].x-a[n2].x)*(a[n1].x-a[n2].x)+(a[n1].y-a[n2].y)*(a[n1].y-a[n2].y));
}
int main() {
    scanf("%d", &t);
    while(t--) {
        scanf("%d", &n);
        for(int i = 1; i <= n; i++) scanf("%lld%lld", &a[i].x, &a[i].y);
        dp[1] = dis(1, 2);
        for(int i = 2; i <= n; i++) {
            int pos = i-1;
            dp[i] = min(dp[i-2], dp[i-1])+dis(i, i-1);
            for(int j = i-2; j >= 1; j--) {
                if((a[i].y-a[pos].y)*(a[i].x-a[j].x) >= (a[i].y-a[j].y)*(a[i].x-a[pos].x)) {
                    dp[i] = min(dp[i], min(dp[j-1], dp[j])+dis(i, j));
                    pos = j;
                }
            }
        }
        printf("%.6lf\n", dp[n]);
    }
}
View Code

 

ACM International Collegiate Programming Contest, JUST Collegiate Programming Contest (2018)

ACM International Collegiate Programming Contest, JUST Collegiate Programming Contest (2018)

ACM International Collegiate Programming Contest, JUST Collegiate Programming Contest (2018)


B. New Assignment

  • 有 n 个人 (1 ≤ n ≤ 104),有男有女,每个人都有一个 id,现在这 n 个人分成学习互助小组,有三种组队模式,一个男人一组,一个女人一组,一男一女一组,如果要一男一女一组,那么这两人 id 的 gcd 要 > 1。保证任意三个人的 gcd=1。求小组的组数最少是多少?
  • 看起来是一个很裸的二分匹配,当两个人性别为男女,并且 gcd>1 时,连边。可是这里的连边的时候复杂度很高。直接暴力连边为 5000 × 5000×log 级别的。所以,需要换一个角度考虑。
  • 可以发现,由于任意三个数的 gcd=1 的性质,可以保证任何一个质因子最多有两个被除数。当且仅当这两个被除数的性别不同连边。
#include"stdio.h"
#include"string.h"
#include"queue"
#include"vector"
#include"algorithm"
#include"iostream"
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=100010;//点数 
const int maxm=400010;//边数
struct node{
	int v,next,cap,flow;
}edge[maxm];
int cnt;
int head[maxn];
int cur[maxn],d[maxn];// 当前弧下标   结点到汇点弧长 
int p[maxn],gap[maxn];////可增广路上的上一条弧   gap优化
int ss[maxn];//保存路径
void init(){
	cnt=-1;
	memset(head,-1,sizeof(head));
} 
void debug(int k){
	int i,j;
	printf("\n");
	for (i=0;i<=k;i++) 
	for (j=head[i];j!=-1;j=edge[j].next) 
        
        printf("%d %d %d\n",i,edge[j].v,edge[j].flow);
}
void add(int u,int v,int w,int rw=0){
	cnt++;
	edge[cnt].v=v;
	edge[cnt].next=head[u];
	edge[cnt].cap=w;
	edge[cnt].flow=0;
	head[u]=cnt;
	cnt++;
	edge[cnt].v=u;
	edge[cnt].next=head[v];
	edge[cnt].cap=rw;
	edge[cnt].flow=0;
	head[v]=cnt;
}
void bfs(int k){
	int v,i;
	int u;
	memset(gap,0,sizeof(gap));
	memset(d,-1,sizeof(d));
	d[k]=0;
	gap[0]++;
	queue<int> q;
	q.push(k);
	while(q.empty()==false){
		u=q.front();q.pop();
		for (i=head[u];i!=-1;i=edge[i].next){
			v=edge[i].v;
			if (d[v]==-1) {
				d[v]=d[u]+1;
				gap[d[v]]++;
				q.push(v);
			}
		}
	}
}
int sap(int s,int t,int N){
	int i;
	bfs(t);
	memcpy(cur,head,sizeof(head));
	int top=0;
	int u=s;
	int ans=0;
	while(d[s]<N){
		if (u==t){
			int min=inf;
			int inser;
			for (i=0;i<top;i++){
				if (min>=edge[ss[i]].cap-edge[ss[i]].flow){
					min=edge[ss[i]].cap-edge[ss[i]].flow;
					inser=i;//这跟管子是满流了 
				}
			}
			for (i=0;i<top;i++){
				edge[ss[i]].flow+=min;
				edge[ss[i]^1].flow-=min;
			} 
			ans+=min;
			top=inser;
			u=edge[ss[top]^1].v;//u为满流的那个结点 也就是那根管子的倒管子的终点
			continue;
		}
		bool ok=false;
		int v;
		for (i=cur[u];i!=-1;i=edge[i].next){ 
			v=edge[i].v;
			if (edge[i].cap-edge[i].flow>0&&d[v]+1==d[u]){
				ok=true;
				cur[u]=i;
				break;
			}//u后 添加了一条下标为i的弧 
		}
		if(ok==true){
			ss[top]=cur[u];
			top++;
			u=v;
			continue;
		}
		//如果这条路已经走不通了,那么撤退
		int min=N;
		for (i=head[u];i!=-1;i=edge[i].next)
		  if (edge[i].cap-edge[i].flow>0&&d[edge[i].v]<min){
		  	min=d[edge[i].v];
		  	cur[u]=i;
		  }
		gap[d[u]]--;
		if (gap[d[u]]==0) return ans;
		d[u]=min+1;
		gap[d[u]]++;
		if (u!=s) {
			top--;
			u=edge[ss[top]^1].v;
			
	}
	}
	return ans;
}
int gcd(int a,int b){
	if (a%b==0) return b;
	return gcd(b,a%b);
}
int prime[600000]={0},numprime=0;
bool isNotPrime[1000010]={1,1};
void sushu(int N){
  long long int i;
   for (i=2;i<=N;i++) {
   	     if(! isNotPrime[i])                 
            prime[numprime ++]=i;    
        
        for(long j = 0 ; j < numprime && i * prime[j] <  N ; j ++)  
            {                 
                isNotPrime[i * prime[j]] = 1;    
            if( !(i % prime[j] ) )                   
                break;             
        }          
    }          
}
int a[10500];
char s[10];
vector<int > v[1000005];
int vis[1000005];
int main(){
	int e,t,i,j,x,y,cap,now;
	int n,m;
	sushu(1000000);
	scanf("%d",&t);
	for (e=1;e<=t;e++){
		memset(vis,0,sizeof(vis));
		for (i=0;i<numprime;i++) v[prime[i]].clear();
		init();
		scanf("%d",&n);
		for (i=1;i<=n;i++) scanf("%d",&a[i]);
		for (j=1;j<=n;j++) {
			scanf("%s",s);
		
			if (s[0]==''M'') {
				now=a[j];
				for (i=0;i<numprime&&prime[i]<=1000;i++) {
				//	printf("%d\n",now);
					if (now%prime[i]==0) v[prime[i]].push_back(j);
					while (now%prime[i]==0) now=now/prime[i];
					if (now==1||isNotPrime[now]==0) break;	
				}	
				if (isNotPrime[now]==0) v[now].push_back(j);
			} else {
				now=a[j];
				for (i=0;i<numprime&&prime[i]<=1000;i++) {
					if (now%prime[i]==0) v[prime[i]].push_back(-j);
					while (now%prime[i]==0) now=now/prime[i];
					if (now==1||isNotPrime[now]==0) break;	
				}
				if (isNotPrime[now]==0) v[now].push_back(-j);
			}
		}
		//	printf("%d %d %d\n",prime[0],v[prime[0]][0],v[prime[0]][1]);
		for (i=0;i<numprime;i++) 
		if (v[prime[i]].size()==2&&v[prime[i]][0]*v[prime[i]][1]<0) {
			
			if (v[prime[i]][0]>0) {
				
				if (vis[v[prime[i]][0]]==0) {add(0,v[prime[i]][0],1);vis[v[prime[i]][0]]=1;}
				if (vis[-v[prime[i]][1]]==0) {add(-v[prime[i]][1],n+1,1);vis[-v[prime[i]][1]]=1;}
				add(v[prime[i]][0],-v[prime[i]][1],inf);
			//	printf("%d %d\n",v[prime[i]][0],-v[prime[i]][1]);
			}
			else {
				if (vis[v[prime[i]][1]]==0) {add(0,v[prime[i]][1],1);vis[v[prime[i]][1]]=1;}
				if (vis[-v[prime[i]][0]]==0) {add(-v[prime[i]][0],n+1,1);vis[-v[prime[i]][0]]=1;}
				add(v[prime[i]][1],-v[prime[i]][0],inf);
			//	printf("%d %d\n",v[prime[i]][1],-v[prime[i]][0]);
			}
		}
		int ans=sap(0,n+1,n+2);
		printf("%d\n",n-ans);
	}
}

E. Maximum Sum

  • 取数问题。16*16 的矩阵,如果你取了这个数,那么周围 8 个格子的数都不能取。求取的数和最大。
  • dp 瞎搞。事先筛选出行内任意两个相邻位置不同的状态,大约有 3000 种不到。
  • dp [i][j] 代表第 i 行,这一行的取数方案为 j 时,前 i 行的最大和。由于第 i-1 行无法去影响 i+1 行,故可以这样设置状态。
  • 考虑转移, 设上一行的状态为 k, 当前行的状态为 j,如果 j and k==0 并且 j<<1 and k ==0 并且 j and k<<1 ==0 那么表示可以转移。
  • 尽管算下来是超高的复杂度,不过千万不要低估银河评测机的实力。
#include"stdio.h"
#include"string.h"
#include"vector"
#include"algorithm"
using namespace std;
vector<int> v;
int d[20];
int a[50][50];
int dp[17][2600];
int main(){
	int i,j,k,l;
	int e,t,n;
	int ans,x;
	int tmp;
	d[0]=1;
	v.clear();
	for (i=1;i<=17;i++) d[i]=d[i-1]*2;
	for (i=0;i<=d[16]-1;i++) {
		int sign=0;
		for (j=0;j<=14;j++) 
			if ((i&d[j])>0&&(i&d[j+1])>0) {sign=1;break;}
		if (sign==0) v.push_back(i);
	}
	
	l=v.size();
	scanf("%d",&t);
	for (e=1;e<=t;e++) {
		scanf("%d",&n);
		for (i=1;i<=n;i++)
		for (j=1;j<=n;j++) scanf("%d",&a[i][j]);
		memset(dp,0,sizeof(dp));
		for (i=0;i<l&&v[i]<=d[n]-1;i++) {
			for (j=1;j<=n;j++) if ((v[i]&d[j-1])>0) dp[1][i]+=a[1][j];
		//	printf("%d :%d\n",i,dp[1][v[i]]);
		}
		for (k=2;k<=n;k++) {
			for (i=0;i<l&&v[i]<=d[n]-1;i++) {
			int tmp=0;
			for (x=1;x<=n;x++) if ((v[i]&d[x-1])>0) tmp+=a[k][x];
			for (j=0;j<l&&v[j]<=d[n]-1;j++) 
			if ((v[i]&v[j])==0&&((v[i]<<1)&v[j])==0&&(v[i]&(v[j]<<1))==0) {

		    	dp[k][i]=max(dp[k][i],tmp+dp[k-1][j]);
		//    	printf("%d :%d :%d\n",k,v[i],dp[k][v[i]]);
		    }
		}
		}
		ans=0;
		for (i=0;i<l&&v[i]<=d[n]-1;i++) ans=max(dp[n][i],ans);
		printf("%d\n",ans);
	}
}

Aspect-Oriented Programming : Aspect-Oriented Programming with the RealProxy Class

Aspect-Oriented Programming : Aspect-Oriented Programming with the RealProxy Class

Aspect-Oriented Programming : Aspect-Oriented Programming with the RealProxy Class

A well-architected application has separate layers so different concerns don’t interact more than needed. Imagine you’re designing a loosely coupled and maintainable application,but in the middle of the development,you see some requirements that might not fit well in the architecture,such as:

  • The application must have an authentication system,used before any query or update.
  • The data must be validated before it’s written to the database.
  • The application must have auditing and logging for sensible operations.
  • The application must maintain a debugging log to check if operations are OK.
  • Some operations must have their performance measured to see if they’re in the desired range.

Any of these requirements need a lot of work and,more than that,code duplication. You have to add the same code in many parts of the system,which goes against the “don’t repeat yourself” (DRY) principle and makes maintenance more difficult. Any requirement change causes a massive change in the program. When I have to add something like that in my applications,I think,“Why can’t the compiler add this repeated code in multiple places for me?” or,“I wish I had some option to ‘Add logging to this method.’”

 

The good news is that something like that does exist: aspect-oriented programming (AOP). It separates general code from aspects that cross the boundaries of an object or a layer. For example,the application log isn’t tied to any application layer. It applies to the whole program and should be present everywhere. That’s called a crosscutting concern.

AOP is,according to Wikipedia,“a programming paradigm that aims to increase modularity by allowing the separation of crosscutting concerns.” It deals with functionality that occurs in multiple parts of the system and separates it from the core of the application,thus improving separation of concerns while avoiding duplication of code and coupling.

In this article,I’ll explain the basics of AOP and then detail how to make it easier by using a dynamic proxy via the Microsoft .NET Framework class RealProxy.

关于The Swift Programming Language学习笔记二十三——协议的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于ACM International Collegiate Programming Contest, Damascus University Collegiate Programming Cont...、ACM International Collegiate Programming Contest, Egyptian Collegiate Programming Contest (ECPC 2...、ACM International Collegiate Programming Contest, JUST Collegiate Programming Contest (2018)、Aspect-Oriented Programming : Aspect-Oriented Programming with the RealProxy Class等相关知识的信息别忘了在本站进行查找喔。

本文标签: