前言:本人也是初次接触组件化开发,感觉现有的资料太繁杂,就简单整理了一下,在此跟大家分享一些入手的经验,主要就是描述cocoapods的私有库封装和提交。组件化开发是个大的议题,涉及到架构思路、设计模式应用、项目经验、工具的使用,所以在此只是做一个开始,后面还会做进一步的拓展和深入,尽量做到干货,欢迎探讨和纠正。

目录:

  • 什么是组件化开发
  • 组件化的核心内容
  • 模块间通信的简单Demo
  • Cocoapods 原理
  • 使用cocoapods制作私有库的一般流程详述
  • 小结

一、 什么是组件化开发

1、 概述

  将项目按照功能、业务等进行拆分,在开发过程中针对不同的场景,将“组件”进行“组装”。

2、 为什么使用组件化

  (1)界面、逻辑和功能拆分,实现解耦

  (2)通过pod管理,很方便的实现安装和卸载

  (3)对于较大项目而言,加强了项目的扩展性、组件复用性和设计统一性

  (4) 可以方便按照组件进行测试

  (5) 多人开发时可以更清晰的管理开发任务

  (6) 解决项目臃肿,编译时间过长

3、 场景及常用的实现方式

  (1) 曾经的常用方式:

    a.  目录结构管理:这是最原始的方式,仅仅通过目录结构实现代码层次的清晰化。但本质上并没有解决代码之间的依赖混乱的情况,模块化划分也非常不清晰。

    b. 子工程:通过子工程可以实现代码依赖管理和模块化,但是需要引入复杂的设置,不利于管理。

    c. 静态库:将依赖代码打包成为静态库.a或者Framework,不过由于不能看到源码,调试非常不方便。

(2)现在较多的使用:cocoapods,使用它来管理私有库,从而实现了代码模块化管理

二、 组件化的核心内容

1、模块拆分

  (1)一个广泛受用的模块化方式:

    a. 基础模块:项目中最基础的代码抽取,不牵扯具体功能的封装,比如一些必要的分类,常量定义,宏命令等,这个模块是要被其他模块所依赖的。

    b. 功能模块:功能封装的抽取,比如网络请求、正则匹配、定位功能等。

    c. 业务模块:面向业务的整体模块,比如订单、个人中心、首页等。可能会依赖基础和功能模块。

  (2)图示

    

2、模块间通信

  (1)组件化最大的特点之一是“模块化”,解耦是核心话题。模块间尽量不要直接引用形成依赖关系(但是业务模块和基础模块之间的引用不可避免)。

  (2)采用router 的方式进行模块间的通信。router可以理解为一个为了解耦而实现的中间键。

  (3)这种通信方式常常采用一种设计模式:命令模式。 使用target + action 的方式响应命令。下面会附上一个简单demo。

3、CocoaPods远程私有库:将我们的组件做成私有库,通过cocoaPods进行管理。

4、宿主工程:就是我们总体的工程项目,各个模块的“组装地”,控制模块间的通信,配置一般的环境变量等等任务。

5、组件化的准备工作:基础与工具

  (1) 了解远程代码仓库的管理工具,本文不会描述如何创建远程仓库。

  (2) 了解cocoapods使用。

  (3) 了解封装、解耦的概念和作用。

  (4) 熟悉Git命令行(习惯使用SourceTree的同学尽量还是熟悉一下命令行)。

三、模块间通信的简单Demo --- 命令模式:router+target+action

1、demo的文件结构,两个模块,一个路由,一个target

  

2、代码

(1)两个业务模块

@implementation HomePageVC

- (void)viewDidLoad {
[super viewDidLoad];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
/*
跳转到详情页面并实现跳转后的相应逻辑
使用url传递“命令”:http://detail/showLog:?vc=HomePageVC
target 是 detail (添加了前缀Target)
action 是 showLog(添加了前缀action)
*/
Router * router = [[Router alloc]init];
[router openURL:@"http://detail/showLog?currentVC=HomePageVC"];
}
@end @implementation DetailVC - (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor orangeColor];
}
-(void)showView:(NSString*)str{
NSLog(@"处理detailVC的逻辑");
} @end

homePage

@implementation DetailVC

- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor orangeColor];
}
-(void)showView:(NSString*)str{
NSLog(@"处理detailVC的逻辑");
} @end

detail

(2)router

@interface Router : NSObject

/*
通过传递urlStr 将target、action、参数传进来
*/
- (id)openURL:(NSString*)urlStr; @end @implementation Router - (id)openURL:(NSString*)urlStr{ NSURL *url = [NSURL URLWithString:urlStr];
NSMutableDictionary *params = [[NSMutableDictionary alloc]init];
//查询http携带的参数
NSString *parameterString = [url query];
//切割字符串,转换为键值对
NSArray * parameterArr = [parameterString componentsSeparatedByString:@"&"];
for (NSString *param in parameterArr) {
NSArray *elts = [param componentsSeparatedByString:@"="];
if (elts.count<) continue;
id firstElt = [elts firstObject];
id lastElt = [elts lastObject];
if (firstElt && lastElt) {
[params setObject:lastElt forKey:firstElt];
}
}
NSString *actionName = [url.path stringByReplacingOccurrencesOfString:@"/" withString:@""];
if ([actionName hasPrefix:@"native"]) {
return @(NO);
}
//将 target 、action 和 参数传递过去
id result = [self performTarget:url.host action:actionName param:params];
return result;
} - (id)performTarget:(NSString *)targetName action:(NSString *)actionName param:(NSDictionary *)para { //OC反射机制,获取class 和 方法名(方法名要注意是否有冒号)
NSString *targetClassString = [NSString stringWithFormat:@"Target_%@",targetName];
NSString *actionMethondString = [NSString stringWithFormat:@"action_%@:",actionName];
Class targetClass = NSClassFromString(targetClassString);
//确定target 和 action
NSObject *target = [[targetClass alloc] init];
SEL action = NSSelectorFromString(actionMethondString); // 排除不响应的情况
if ([target respondsToSelector:action]) {
return [self safePerformAction:action target:target param:para];
} else {
SEL action = NSSelectorFromString(@"notFound:");
if ([target respondsToSelector:action]) {
return [self safePerformAction:action target:target param:para];
} else {
return nil;
}
}
return nil;
}
/**
调用指定对象的指定方法完成命令的响应
@param action 方法
@param target 目标对象
@param para 参数
@return 返回值
*/
- (id)safePerformAction:(SEL)action target:(NSObject *)target param:(NSDictionary *)para {
//方法签名
NSMethodSignature *methodSig = [target methodSignatureForSelector:action];
if (methodSig == nil) {
return nil;
}
// 获取这个方法返回值
const char *retType = [methodSig methodReturnType];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setArgument:&para atIndex:];
[invocation setTarget:target];
[invocation setSelector:action];
[invocation invoke];
// id是可以返回任意对象,所以单独处理基本变量: NSInteger Bool ...
if (strcmp(retType, @encode(NSInteger)) == ||
strcmp(retType, @encode(BOOL)) == ||
strcmp(retType, @encode(CGFloat)) == ) {
NSInteger result = ;
[invocation getReturnValue:&result];
return @(result);
}
else if (strcmp(retType, @encode(void)) == ){
return nil;
}
else{
id result;
[invocation getReturnValue:&result];
return result;
}
} @end

router

(3) target

@interface Target_detail : NSObject

-(void)action_showLog:(NSDictionary*)parameter;
-(void)notFound:(NSDictionary*)parameter; @end @implementation Target_detail -(void)action_showLog:(NSDictionary*)parameter{
DetailVC * detailVC = [[DetailVC alloc]init];
[detailVC showView:parameter[@"currentVC"]];
}
-(void)notFound:(NSDictionary*)parameter{
NSLog(@"Target_detail 中%@方法未找到",parameter);
} @end

target

3、补充:OC的反射机制和内省方法

  • Foundation框架为我们提供了一些方法反射的API,我们可以通过这些API执行将字符串转为Class、SEL等操作。由于OC语言的动态性,这些操作都是发生在运行时的。
  • 反射机制可以灵活的从后台获得创建信息,并在运行时动态选择创建那个对象和调用什么方法。
  • 内省方法:在NSObject类中为我们提供了一些基础方法,用来做一些判断操作,这些方法都是发生在运行时动态判断的。
// 当前对象是否这个类或其子类的实例
- (BOOL)isKindOfClass:(Class)aClass;
// 当前对象是否是这个类的实例
- (BOOL)isMemberOfClass:(Class)aClass;
// 当前对象是否遵守这个协议
- (BOOL)conformsToProtocol:(Protocol *)aProtocol;
// 当前对象是否实现这个方法
- (BOOL)respondsToSelector:(SEL)aSelector;

4、补充:NSInvocation消息传递

(1)两种方式

  • performSelector:withObject:调用比较简单,但是对于>2个的参数或者有返回值的处理就会麻烦。
  • NSInvocation:进行相对复杂的操作。

(2)NSInvocation原理:它将调用者,函数名(方法名),参数封装到一个对象,然后通过一个invoke函数来执行被调用的函数,其思想就是命令模式,将请求封装成对象。

四、Cocoapods 原理

1、通过几个名词了解原理:

  (1)框架描述文件 —— spec ,  最重要的文件

     json文件,包含框架名称、版本、框架的源码地址等信息,每一个框架都要有对应的spec,我们创建自己的私有库也要创建spec文件,比如:

  

  (2)远程索引库: 远端存放spec 文件的库,cocoapods在github上的索引库如下:

  

  (3)本地索引库: pod setup 之后,将远程索引库拷贝到本地。可以在本地查看:

  

  (4)本地索引库的检索文件:

    a.本地索引库会生成一个“检索时用来索引的文件”

    b.命令 pod search <NAME>  检索的是这个文件

    c.文件位置如图:

    

  (5)本地缓存:用pod安装一个库之后,会在cache文件夹里生成对应的缓存,如下:

    

2、cocoapods原理综述:总结上面的几个名词的关系和作用

  (1)图示

  

  (2)如果远端有更新,本地索引库需要和远程索引库保持一致,这个过程需要手动更新,不在上面的图示之中。

3、git 和pod的一些常用命令

  (1)git命令:

git init                   :初始化git仓库
git add . :将当前文件夹下的所有文件提交的git缓冲区
git status :显示状态
git commit -m ‘注释’ :提交代码到本地仓库
git log :查看记录
git push :推送远端仓库
git push <主机名> <分支名> :推送远端分支
git push <主机名> ‘版本号’ :按照版本号推送到远程
git remote :查看远程主机名
git remote add <NAME> <URL> :关联远程仓库
git tag -a ‘版本’ -m ‘描述’ :打标签
git push --tags :提交标签到远程仓库
git repo add master :查看仓库地址,添加自己的索引库
git branch :查看分支
git clean -f :删除没有被add的文件

  (2)pod命令:

pod update    :不会参照podfile.lock,直接检测新版本,并更新pod库。
pod install   :会参照podfile.lock (里面记录了各个pod库的版本),进行下载(不更新)。如果podfile.lock里面没有,则参照podfile。
pod spec create <名字> :手动创建spec文件
pod trunk register <邮箱> '名字' --description='描述' :注册trunk,用于上传spec到远程索引库
pod lib lint  :本地验证pod库能否通过验证
pod repo    :获得pod本地/远程索引库
pod spec lint :本地/远程验证pod库能否通过验证

4、其他

  (1)cocoapods打包的私有库,默认两层文件夹(所有文件放到一个目录里),下面会介绍私有库文件夹结构分层的方式。

  (2)cocoapods打包私有库时,可以添加对其他第三方框架的依赖,具体方式看下文的spec文件字段举例。

    (3) 使用git管理项目的时候,是否需要将pod文件上传? 这个根据具体情况来判断,gitignore文件中可以设置是否忽略cocoapods,但是无论是否忽略,都建议podfile和podfile.lock这两个文件要上传到远端,来保证多人开发中pod的正确使用。

五、 使用cocoapods制作私有库的一般流程详述

 <1>  在github上(cocoapods远端)创建自己的框架,也就是将库发布到cocoapods上

  1、 创建框架项目,完成框架的代码。

  2、在github上创建代码仓库,导入框架项目到远程仓库。

  3、需要给代码仓库打上tag,并且将tag提交到远端(否则后面提交spec会报错)

  4、在框架目录下创建spec文件(框架描述文件)

    (1)手动创建spec:pod spec create <名字>

    (2)字段详细描述:https://guides.cocoapods.org/syntax/podspec.html

    (3)举例:

Pod::Spec.new do |s|

  # ――― 基本信息 ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
# homepage仓库主页(注意,是主页地址,不是完整的仓库地址,否则有可能报错)
# license 开源协议,一般是'MIT',还有 'BSD' and 'Apache License, Version 2.0'等
# platform 框架支持的最低平台版本
s.name = "TestSpec"
s.version = "0.0.1"
s.summary = "简短描述"
s.description = "详细描述(要比summary长)"
s.homepage = "http://EXAMPLE/TestSpec"
s.license = "MIT"
# s.license = { :type => "MIT", :file => "FILE_LICENSE" }
s.author = { "Zhang Qi" => "zhangqi@163.com" }
# s.author = "Zhang Qi"
# s.platform = :ios
#s.platform = :ios, "9.0" # ――― 资源路径 ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
# 框架的资源路径:路径可以指向远端代码库,也可以指向本地项目,例如:
# .指向远端代码库: { :git => "git@git.oschina.net:xxx/xxx.git", :tag => "1.0.0" }
# .指向本地项目: { :path => 'TestSpec', }
# .版本号和上面的version对应
s.source = { :git => "http://EXAMPLE/TestSpec.git", :tag => "#{s.version}" } # ――― 被引入的文件 ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
# 框架被其他工程引入时,会导入对应目录下的.h和.m文件
# .描述在s.source路径下,文件下的对应文件夹
# .可以起到“过滤”的作用,* 为通配符,比如:Classes/{ZQ,UI}*.{h.m} ,这个是表示ZQ和UI开头的h和m文件
s.source_files = "Classes", "Classes/**/*.{h,m}"
# s.resource = "icon.png"
# s.resources = "Resources/*.{png,jpg,xib,storyboard,xcassets}" # ――― 依赖的库和框架 ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
# s.framework = "SomeFramework"
# s.frameworks = "SomeFramework", "AnotherFramework"
# s.library = "iconv"
# s.libraries = "iconv", "xml2" # ――― 其他设置 && 目录结构 ――――――――――――――――――――――――――――――――――――――――――――――――――――――――― #
# .dependency 表示依赖的第三方库,会在导入本库的同时,导入依赖库
# .subspec 通过设置子库实现文件夹的层级结构,同时可以单独引入子库
# s.requires_arc = true
# s.dependency "JSONKit", "~> 1.4"
#s.subspec 'Category' do |c|
#c.source_files = 'MyLib/Classes/Category/**/*'
#end end

spec字段举例

  5、注册trunk 账号(如果是第一次)

    (1)命令行:pod trunk register <邮箱> '名字'

    (2)进入邮箱进行验证:复制并打开里面提供的链接

  6、 提交spec文件到github上(cocoapods):

    (1)确保本地仓库已经commit

    (2)pod trunk push xxxx.podsepc

  7、搜索刚刚提交的框架

    (1)搜索不到:search_index 文件里面没有新框架的信息

    (2)可以将search_index删除,然后重新pod search 或者pod setup

 <2>  创建本地私有库,有时我们的私有库并不想让使用者从远端加载

  1、创建本地工程项目,完成项目的代码

  2、创建本地框架,完成框架的代码

  3、在框架目录下创建spec文件(框架描述文件)

    (1)手动创建spec:pod spec create <名字>

    (2)修改spec ,因为是本地私有库,所以,字段和上面的远程库有所不同,主要是s.source 里面的git路径可以删掉了

  4、在本地的工程项目中,引用本地的私有库

    (1)pod init 本地的工程项目

    (2)更改podfile文件,添加本地的私有库,比如: pod  ‘TestLib’

    (3)配置podfile 中引用的私有库路径:pod ‘TestLib’ ,:path => ‘../TestLib’  ,这里需要根据TestLib的真实位置,填写path路径。这个路径是spec文件相对于podfile文件的路径,其中  ../ 表示向上一级。

(4)(可选)如果需要提交本地私有库,可以按照远端创建私有库的方式,将本地的私有库上传。但是,本地的工程项目,还是使用的本地引用的方式,导入的私有库。

    (5) 回到本地工程项目的目录,pod install

    (6)安装完毕后,在本地工程项目中,观察pods文件夹:

    

    (7)如果本地私有库里面发生了更新,直接在本地工程项目里面pod install即可安装。

 <3> 使用其他远程仓库创建自己的私有库(比如码云)

  1、创建本地工程项目的代码。

  2、在码云上创建项目仓库(整体工程项目的远端仓库)。

  3、在码云上创建spec仓库(私有远程索引库),这个仓库不是用来存放代码的,是放置spec的,类似cocoapods在github上的远程索引库(上文有介绍)。

  4、处理本地索引库:需要添加私有的索引库

    (1) pod repo 可以查看当前的索引库

    

    (2)创建私有本地仓库:pod repo add <NAME> <远程仓库url>

  5、 创建私有库框架的代码,这里有两种方式:

    (1)正常创建框架的文件,并且为了测试方便,添加一个测试用的工程项目。下面两张图,就是整个私有库的内容,包含一个Lib,一个TestProject,这个私有库通过git管理源码:

    

     上图为Lib文件夹

    

     上图为测试项目结构

    (2)使用cocoapods提供的模板: 进入Lib文件夹,使用命令 pod lib create <NAME>,直接创建一个带有1、框架源码  2、测试工程 3、spec文件的模板。结构如下图:

  

    当使用pod lib create <NAME> 时,会有提示选项出现,如下图:

  

       创建好的模板,用xcode打开(example里的工程),如下图:

  

 6、 在码云上创建框架仓库(私有框架的源代码仓库),并且导入上一步创建好的Lib

    (1)  在Lib目录git init

    (2)  git add 和git commit

    (3)  git remote add <主机名><URL>  添加仓库源

    (4)  git push –u <主机名><分支名>   提交到远程

 7、修改私有库的spec文件(文件位置在上面第5步中已经标注),如下图:

  

 8、给私有库打tag,并且提交到远端。这一步是必须的,而且tag和上面的spec要一致,不然下面会报错。

 9、提交前的准备:检测spec文件,需要两步

    (1) pod lib lint  检测本地spec文件是否有问题,此时不检测source字段是否正确。

    (2) pod spec lint  检测远端spec是否有问题。后面我们会将spec提交到本地,然后会推到远端,这是很重要的同步远端的机制。

 10、提交spec文件

    (1) pod repo push <新建的本地索引库> <spec文件>  , 新建的索引库在上面第四步中已经创建了。

    (2) 如果出现问题,需要到索引库里面git clean –f 清除一下,或者删除并重建一下本地索引库。

 11、使用pod search 检索私有库,如果搜不到,效仿上面介绍的,删掉search_index文件,重新检索。

 12、在项目中引入远程私有库

    (1) pod repo 查看索引库并且记录下新建的私有库的url。这个url是远程索引库的地址(spec的仓库地址),并不是私有库源码的地址。

    (2)由于默认情况,podfile里面指向的远程索引库是在github上的,所以,我们需要添加新的“源”。在podfile顶部,增加添加上一步记录的url  : source '远程索引库的url' ,如果需要同时引用github上面的其他库,还要再添加github上的索引库地址,方法同第一步(这个地址默认是不用添加的)。

    (3) 在podfile中间,加入要引入的私有库,不用再使用path => ‘本地路径’了。

    (4) pod install 加载私有库,观察此时项目中的pods文件夹结构:

      

六、小结

  本文比较详细的描述了用cocoapods封装私有库的方式,组件化相关的知识还有很多没有涉及到,包括资源包的封装,Framework,block在组件间通信时的使用等等,还有很多有关设计模式和架构的思路没有深入探讨。个人认为,关于组件化,还是根据业务需求和产品体量来运用,网上也有很多可以借鉴的成型的解决方案,不要为了组件化而组件化,这时有一定设计模式的知识积累还是很有帮助的。

iOS组件化开发入门 —— 提交自己的私有库的更多相关文章

  1. iOS组件化开发一使用source管理远端库升级(四)

    一.克隆远端库代码到本地选择master分支 1.克隆 2.代码会显示出你所有版本的tag 二.可以在Example目录下验证代码的正确行: cd 到库的文件夹然后 pod install comma ...

  2. iOS 组件化开发之使用CocoaPod制作自己的远程私有库

    随着应用需求逐步迭代,应用的代码体积将会越来越大,为了更好的管理应用工程,我们开始借助CocoaPods版本管理工具对原有应用工程进行拆分.但是仅仅完成代码拆分还不足以解决业务之间的代码耦合,为了更好 ...

  3. iOS组件化开发-CocoaPods简介

    CocoaPods简介 任何一门开发语言到达一定阶段就会出现第三方的类库管理工具,比如Java的Maven.WEB的Webpack等.在iOS中类库的管理工具-CocoaPods. 利用CocoaPo ...

  4. iOS组件化开发· 什么是组件化

    越来越多公司,开始了组件化,你还要等到什么时候...... 说到开发模式,我们最熟知的开发模式 MVC 或者最近比较热门的MVVM.但是我今天说的组件化的开发,其实MVC不是一类的.它其实是····· ...

  5. iOS组件化开发一本地环境配置(一)

    首先我们要使用pod支持组件化开发 解决CocoaPods慢的方案(gem和pod repo换源) gem换源 $ gem sources --remove https://rubygems.org/ ...

  6. iOS组件化开发-发布私有库

    远程索引库 将远程索引库添关联到本地 pod repo 查看本地已关联仓库源 pod repo add 本地索引库名称 远程索引库仓库地址 pod repo update 索引库名称 pod repo ...

  7. iOS组件化开发一远程私有库的升级(三)

    一.远程私有库的升级 1. 把新增的类 拖入到 classes 文件夹中 : 2. 修改 pod.spec 文件的 s.verson = ‘0.2.0': 二.更新远程仓库 1.cd 到本地仓库的位置 ...

  8. iOS组件化开发一远端私有库建立(二)

    公共库业务,基础层划分! 一.构建私有云,本文推荐为码云 1.构建名称为LuckTimeSpec,选择私有建立: 2.Copy地址的https 链接: 二.打开终端 输入: cd /Users/zha ...

  9. iOS组件化开发一pod库包含MRC的文件处理(五)

    在做项目的过程中,建立了一个私有pod库,在这个库中存在mrc类文件这个时候如果在使用了arc的工程中引用这个pod的工程中手动设置当然也可以就是费时费力.现在我们来看看如何在私有库配置文件里配置自动 ...

随机推荐

  1. Django之Ajax提交

    Ajax 提交数据,页面不刷新 Ajax要引入jQuery Django之Ajax提交 Js实现页面的跳转: location.href = "/url/" $ajax({ url ...

  2. scrapy快速入门

    1. 什么是scrapy? 其官网是这样简述的,“A Fast & Powerful Scraping &Crawling Framework ”,  并且其底层以twisted作为网 ...

  3. Python之Pycharm安装及介绍

    在学习Python之前,先安装好编程所需的编译环境也就是IDE,在安装PycharPm之前先安装最新版本的anaconda根据不同的系统选择不同的版本,安装好anaconda以后再安装Pycharm, ...

  4. 确定协议-通过分析系统阶段需要知道该系统能不能进行性能测试-Omnipeek

  5. HDU 3157 Crazy Circuits

    Crazy Circuits Time Limit: 2000ms Memory Limit: 32768KB This problem will be judged on HDU. Original ...

  6. Android BGABadgeView:显示提示数字(2)

     Android BGABadgeView:显示提示数字(2) 在附录文章3的基础上,对代码进行稍微改造,显示在红色小圆球内部显示数字,同时给红色小圆球通过可编程调控红色小圆球的整体外观,布局文件 ...

  7. GitHub & puppeteer & Chinese character & bug

    GitHub & puppeteer & Chinese character & bug https://github.com/GoogleChrome/puppeteer/b ...

  8. 【HDOJ4812】D Tree(点分治)

    题意: 给定一棵 n 个点的树,每个点有权值 Vi 问是否存在一条路径使得路径上所有点的权值乘积 mod(10^6 + 3) 为 K 输出路径的首尾标号,若有多解,输出字典序最小的解 对于100%的数 ...

  9. csu1365 Play with Chain

    很显然的splay,第一次用splay操作区间...我实在佩服这个targan大佬,居然搞出这么牛逼的平衡树,调了大概5个小时终于搞定了.. #include<cstdio> #inclu ...

  10. 2017-10-01-afternoon

    T1 一道图论好题(graph) Time Limit:1000ms   Memory Limit:128MB 题目描述 LYK有一张无向图G={V,E},这张无向图有n个点m条边组成.并且这是一张带 ...