-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
861 lines (415 loc) · 391 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>Pass_02 指令的相关操作</title>
<link href="/2022/10/05/LLVM/CS6120/Pass_02%20%E6%8C%87%E4%BB%A4%E7%9A%84%E7%9B%B8%E5%85%B3%E6%93%8D%E4%BD%9C/"/>
<url>/2022/10/05/LLVM/CS6120/Pass_02%20%E6%8C%87%E4%BB%A4%E7%9A%84%E7%9B%B8%E5%85%B3%E6%93%8D%E4%BD%9C/</url>
<content type="html"><![CDATA[<p>在 [上一节](./Pass_01 写一个pass) 的基础上,本节展示一些指令的相关操作</p><h2 id="遍历操作"><a href="#遍历操作" class="headerlink" title="遍历操作"></a>遍历操作</h2><p>这一节主要是讲如何进行一些简单的转换和遍历的操作</p><p>首先回顾LLVM IR中的组件结构:</p><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20221004235336.png" alt="IR中的组件结构"></p><p>对于一个可迭代的序列,<code>xxxbegin()</code> 返回一个指向序列开始的迭代器,<code>xxxend()</code> 返回一个指向序列结尾的迭代器,两者之间有一些<code>xxxiterator()</code>。</p><p>在各个组件结构中,有一些常见的遍历方法:</p><h3 id="遍历Function中的BasicBlock"><a href="#遍历Function中的BasicBlock" class="headerlink" title="遍历Function中的BasicBlock"></a><strong>遍历Function中的BasicBlock</strong></h3><pre><code>Function &Func = ...for (BasicBlock &BB : Func) // 遍历Function中的所有BasicBlock,并输出每个BB中有几个Instruction errs() << "Basic block (name=" << BB.getName() << ") has " << BB.size() << " instructions.\n";</code></pre><h3 id="遍历BasicBlock中的Instruction"><a href="#遍历BasicBlock中的Instruction" class="headerlink" title="遍历BasicBlock中的Instruction"></a><strong>遍历BasicBlock中的Instruction</strong></h3><pre><code>BasicBlock& BB = ...for (Instruction &I : BB) // The next statement works since operator<<(ostream&,...) // is overloaded for Instruction& errs() << I << "\n";</code></pre><p>然而这其实不是输出BB中的Ins的最好方法,因为ostream对几乎所有内容都是重载的。</p><p>可以在基本块本身调用<code>errs() << BB << "\n";</code></p><h3 id="遍历Function中的Instruction"><a href="#遍历Function中的Instruction" class="headerlink" title="遍历Function中的Instruction"></a><strong>遍历Function中的Instruction</strong></h3><pre><code>#include "llvm/IR/InstIterator.h"// F is a pointer to a Function instancefor (inst_iterator I = inst_begin(F), E = inst_end(F); I != E; ++I) errs() << *I << "\n";</code></pre><p>或者用一个<code>worklist</code>把所有的Ins都保存下来:</p><pre><code>std::set<Instruction*> worklist;for (inst_iterator I = inst_begin(F), E = inst_end(F); I != E; ++I) worklist.insert(&*I);</code></pre><h3 id="把迭代器转换成类指针"><a href="#把迭代器转换成类指针" class="headerlink" title="把迭代器转换成类指针"></a><strong>把迭代器转换成类指针</strong></h3><p>为什么要这么转换呢?因为有时,手边只有一个迭代器,这时候获取实例的指针就很有用。</p><p>假设<code>i</code> 是一个 <code>BasicBlock::iterator</code> ,<code>j</code> 是 <code>BasicBlock::const_iterator</code>:</p><pre><code>Instruction& inst = *i;// 下面两行等价Instruction* pinst = &*i; Instruction *pinst = i;const Instruction& inst = *j;</code></pre><p>也可以把类指针转换成迭代器,这个操作很高效。</p><p>获取某个对象的迭代器:</p><pre><code>void printNextInstruction(Instruction* inst) { BasicBlock::iterator it(inst); ++it; // After this line, it refers to the instruction after *inst if (it != inst->getParent()->end()) errs() << *it << "\n";}</code></pre><h3 id="一个稍微复杂的例子"><a href="#一个稍微复杂的例子" class="headerlink" title="一个稍微复杂的例子"></a><strong>一个稍微复杂的例子</strong></h3><p>假如你正在写一个<code>FunctionPass</code>,想要数一数整个模块中的某个函数在域中的出现了多少次(不用<code>InstVisitor</code>)。</p><p>由于正在写<code>FunctionPass</code>所以你要重写<code>runOnFunction</code>函数</p><pre><code class="cpp">Function* targetFunc = ...;class OurFunctionPass : public FunctionPass { public: OurFunctionPass(): callCounter(0) {} virtual runOnFunction(Function& F) { for (BasicBlock &B : F) { for (Instruction &I: B) { // dyn_cast<CallBase>(&I)来检查指令I是否符合CallBase的模式匹配 if (auto *CB = dyn_cast<CallBase>(&I)) { // 检查是否是目标函数 if (CB->getCalledFunction() == targetFunc) ++callCounter; } } } } private: unsigned callCounter;};</code></pre><h3 id="在def-use-和-use-def-链中遍历"><a href="#在def-use-和-use-def-链中遍历" class="headerlink" title="在def-use 和 use-def 链中遍历"></a><strong>在def-use 和 use-def 链中遍历</strong></h3><p>我们经常拿到一个<a href="https://llvm.org/doxygen/classllvm_1_1Value.html">Value类</a>的实例,想要决定哪个<code>User</code>正在用这个<code>Value</code>,这个Value的所有的<code>users</code>叫做<code>def-use</code>链,找到所有的用<code>Function *F</code>的指令:</p><pre><code class="cpp">Function *F = ...;for (User *U : F->users()) { if (Instruction *Inst = dyn_cast<Instruction>(U)) { errs() << "F is used in instruction:\n"; errs() << *Inst << "\n"; }</code></pre><p>同理,我们拿到一个<a href="https://llvm.org/doxygen/classllvm_1_1User.html">User类</a>的实例时,想要知道它用了哪些<code>Value</code>,所有的这个<code>User</code>用的<code>Value</code>叫做<code>use-def</code>链.</p><p><code>Instruction</code>类的实例是最普通的<code>User</code>,所以我们想要遍历某个Ins用的所有的<code>values</code></p><pre><code class="cpp">Instruction *pi = ...;for (Use &U : pi->operands()) { Value *v = U.get();}</code></pre><h3 id="遍历某一个BB的前面的BB和后面的BB"><a href="#遍历某一个BB的前面的BB和后面的BB" class="headerlink" title="遍历某一个BB的前面的BB和后面的BB"></a><strong>遍历某一个BB的前面的BB和后面的BB</strong></h3><pre><code class="cpp">#include "llvm/IR/CFG.h"BasicBlock *BB = ...;for (BasicBlock *Pred : predecessors(BB)) { // ...}</code></pre><p>同理<code>successors</code></p><h2 id="创建指令"><a href="#创建指令" class="headerlink" title="创建指令"></a>创建指令</h2><h3 id="创建和插入一些简单的Instructions"><a href="#创建和插入一些简单的Instructions" class="headerlink" title="创建和插入一些简单的Instructions"></a><strong>创建和插入一些简单的Instructions</strong></h3><pre><code class="cpp">auto *ai = new AllocaInst(Type::Int32Ty);</code></pre><p>上面这行创建了一个<code>AllocaInst</code>Ins,它代表一个整数在当前栈的位置,每一个Ins的子类都有不同的默认参数,这些参数会改变Ins的语意,可以看这个链接: <a href="https://llvm.org/doxygen/classllvm_1_1Instruction.html">doxygen documentation for the subclass of Instruction</a></p><h3 id="给指令的值命名"><a href="#给指令的值命名" class="headerlink" title="给指令的值命名"></a><strong>给指令的值命名</strong></h3><p>如果你查看机器码,你肯定想把指令的结果和逻辑名关联起来。可以通过给指令的构造函数的<code>Name</code>参数提供一个值。</p><p>例如,我正在写一个transform,动态的给堆栈上的一个整数分配空间,这个证书将被其他的一些代码用作某个索引:</p><pre><code class="cpp">auto *pa = new AllocaInst(Type::Int32Ty, 0, "indexLoc");</code></pre><h3 id="插入指令"><a href="#插入指令" class="headerlink" title="插入指令"></a><strong>插入指令</strong></h3><p>有3种方法可以将指令插入到构成BB的现有指令序列中:</p><ul><li><p>插入到显式指令列表中</p><p>想把<code>NewInst</code>插入到<code>pi</code>的前面:</p><pre><code class="cpp">BasicBlock *pb = ...;Instruction *pi = ...;auto *newInst = new Instruction(...);pb->getInstList().insert(pi, newInst); </code></pre><p>插到BB的结尾:</p><pre><code class="cpp">BasicBlock *pb = ...;auto *newInst = new Instruction(...);pb->getInstList().push_back(newInst); </code></pre><p>或者更简单的:</p><pre><code class="cpp">BasicBlock *pb = ...;auto *newInst = new Instruction(..., pb);</code></pre></li><li><p>插到隐式指令列表里</p><p>BB中的指令实例隐式的与一个指令列表相关联,因此,我们可以在没有BB的情况下完成上面代码相同的事情:</p><pre><code class="cpp">Instruction *pi = ...;auto *newInst = new Instruction(...);pi->getParent()->getInstList().insert(pi, newInst);</code></pre><p>可以直接用构造函数,把新指令插到某一个指令的前面:</p><pre><code class="cpp">Instruction* pi = ...;auto *newInst = new Instruction(..., pi);</code></pre></li></ul><p> 当你要构造很多Ins并把它们加到BB中,可以用上面这个方法。</p><ul><li><p>用<code>IRBuilder</code>的实例插</p><p><code>IRBuilder</code>可以很方便的在某个BB的结尾或是某个指令的前面插入多条指令。</p><p>下面这个例子展示了<code>IRBuilder</code>的一个简单用法,在<code>pi</code>之前插入3条指令。</p><pre><code class="cpp">Instruction *pi = ...;IRBuilder<> Builder(pi);CallInst* callOne = Builder.CreateCall(...);CallInst* callTwo = Builder.CreateCall(...);Value* result = Builder.CreateMul(callOne, callTwo);</code></pre><p>在BB的后面插入指令:</p><pre><code class="cpp">BasicBlock *pb = ...;IRBuilder<> Builder(pb);CallInst* callOne = Builder.CreateCall(...);CallInst* callTwo = Builder.CreateCall(...);Value* result = Builder.CreateMul(callOne, callTwo);</code></pre></li></ul><h2 id="删除指令"><a href="#删除指令" class="headerlink" title="删除指令"></a>删除指令</h2><h3 id="删除指令:"><a href="#删除指令:" class="headerlink" title="删除指令:"></a><strong>删除指令</strong>:</h3><pre><code class="cpp">Instruction *I = .. ;I->eraseFromParent();</code></pre><p>如果你只想把这个指令和BB取消链接而不删它,可以用<code>removeFromParent()</code>方法</p><h3 id="删除全局变量:"><a href="#删除全局变量:" class="headerlink" title="删除全局变量:"></a><strong>删除全局变量</strong>:</h3><pre><code class="cpp">GlobalVariable *GV = .. ;GV->eraseFromParent();</code></pre><h2 id="替换指令"><a href="#替换指令" class="headerlink" title="替换指令"></a>替换指令</h2><p>先<code>#include "llvm/Transforms/Utils/BasicBlockUtils.h"</code></p><p>然后就能用2个很好用的替换函数: <code>ReplaceInstWithValue</code> 和 <code>ReplaceInstWithInst</code>.</p><h3 id="ReplaceInstWithValue"><a href="#ReplaceInstWithValue" class="headerlink" title="ReplaceInstWithValue"></a><strong>ReplaceInstWithValue</strong></h3><p>把某一个指令的所有use都用一个value替换,然后删除原指令:</p><pre><code class="cpp">AllocaInst* instToReplace = ...;BasicBlock::iterator ii(instToReplace);ReplaceInstWithValue(instToReplace->getParent()->getInstList(), ii, Constant::getNullValue(PointerType::getUnqual(Type::Int32Ty)));</code></pre><h3 id="ReplaceInstWithInst"><a href="#ReplaceInstWithInst" class="headerlink" title="ReplaceInstWithInst"></a><strong>ReplaceInstWithInst</strong></h3><p>用新指令替换旧指令,同时替换所有的<code>use</code></p><pre><code class="cpp">AllocaInst* instToReplace = ...;BasicBlock::iterator ii(instToReplace);ReplaceInstWithInst(instToReplace->getParent()->getInstList(), ii, new AllocaInst(Type::Int32Ty, 0, "ptrToReplacedInt"));</code></pre><h2 id="参考链接:"><a href="#参考链接:" class="headerlink" title="参考链接:"></a>参考链接:</h2><p><a href="https://link.zhihu.com/?target=https://llvm.org/docs/ProgrammersManual.html%23helpful-hints-for-common-operations">helpful-hints-for-common-operations</a></p>]]></content>
<categories>
<category> LLVM </category>
</categories>
<tags>
<tag> LLVM </tag>
<tag> Pass </tag>
</tags>
</entry>
<entry>
<title>Pass_01 写一个pass</title>
<link href="/2022/10/04/LLVM/CS6120/Pass_01%20%E5%86%99%E4%B8%80%E4%B8%AApass/"/>
<url>/2022/10/04/LLVM/CS6120/Pass_01%20%E5%86%99%E4%B8%80%E4%B8%AApass/</url>
<content type="html"><![CDATA[<p>本篇文章介绍如何实现一个简单的Pass, 并在此基础上进行改动。</p><h2 id="Pass简介"><a href="#Pass简介" class="headerlink" title="Pass简介"></a>Pass简介</h2><p>LLVM Pass 框架是LLVM系统的重要组成部分,它的主要作用是对IR进行分析、转换、优化。编译器开发者将IR分解为不同的处理对象,并将其处理过程实现为单独的Pass类型。在编译器初始化的时候,pass被实例化,并被添加到pass管理器中,pass管理器以流水线的方式将各个独立的pass衔接起来,然后以预定义的顺序遍历每个pass,根据pass实例返回值启动、停止、重复运行不同的pass。因此LLVM Pass的模块主要包括:pass、pass管理器、pass注册以及相关模块。</p><p>所有的LLVM Pass都是Pass类的子类,它里面含有很多虚函数供子类继承和重写。我们在编写pass的时候,要根据需求来继承Module Pass、CallGraphSCCPass、FunctionPass、LoopPass、RegionPass、BasicBlockPass等。LLVM Pass框架的一个主要特性就是它根据传递遇到的约束来调度传递以高效的方式运行。</p><h2 id="模板代码讲解"><a href="#模板代码讲解" class="headerlink" title="模板代码讲解"></a>模板代码讲解</h2><p>我们写的Pass的模板来自 <a href="https://github.com/sampsyo/llvm-pass-skeleton/blob/master/skeleton/Skeleton.cpp">llvm-pass-skeleton</a> ,可以直接使用以下命令clone到合适的位置</p><pre><code>$ git clone https://github.com/sampsyo/llvm-pass-skeleton.git</code></pre><p>这个模板十分简单,仅仅实现了一些打印的功能,并没有对IR代码进行修改,下面对模板中的代码进行详细讲解:</p><p>首先引入头文件:</p><pre><code class="cpp">#include "llvm/Pass.h"#include "llvm/IR/Function.h"#include "llvm/Support/raw_ostream.h"#include "llvm/IR/LegacyPassManager.h"#include "llvm/Transforms/IPO/PassManagerBuilder.h"</code></pre><p>在头文件中的函数存在于llvm的命名空间中,所以我们必须使用 llvm 的命名空间</p><pre><code class="cpp">using namespace llvm;</code></pre><p>接下来开始一个新的匿名命名空间,为了引入静态全局作用域,就像C语言的“static”关键字,它使在匿名命名空间中声明的内容仅对当前文件可见:</p><pre><code class="cpp">namespace{</code></pre><p>声明我们的Pass,继承自FunctionPass,这是一个对函数进行操作的类,它对初学者来说比较友好:</p><pre><code class="cpp"> struct SkeletonPass : public FunctionPass {</code></pre><p>我们需要一个标识符,这允许LLVM避免使用C++运行时信息:</p><pre><code class="cpp"> static char ID; SkeletonPass() : FunctionPass(ID) {}</code></pre><p>声明一个<code>runOnFunction</code>方法,覆盖了从FunctionPass继承的抽象虚方法,在这个方法中,我们仅仅打印出一些基本信息:</p><pre><code class="cpp"> virtual bool runOnFunction(Function &F) { errs() << "I saw a function called " << F.getName() << "!\n"; return false; }</code></pre><ul><li><p><code>errs()</code>是 LLVM 提供的 C++ 输出流,我们可以使用它来打印到控制台。</p></li><li><p>函数返回<code>false</code>表明它没有修改函数<code>F</code>. 稍后,当我们真正转换程序时,我们需要 return <code>true</code>。</p></li></ul><p>初始化PassID,LLVM使用ID的地址来标识ID,因此ID的初始化值并不重要</p><pre><code class="cpp">char SkeletonPass::ID = 0;</code></pre><p>注册我们的pass</p><pre><code class="cpp">static void registerSkeletonPass(const PassManagerBuilder &, legacy::PassManagerBase &PM) { PM.add(new SkeletonPass());}static RegisterStandardPasses RegisterMyPass(PassManagerBuilder::EP_EarlyAsPossible, registerSkeletonPass);</code></pre><p>总的代码如下:</p><pre><code class="cpp">#include "llvm/Pass.h"#include "llvm/IR/Function.h"#include "llvm/Support/raw_ostream.h"#include "llvm/IR/LegacyPassManager.h"#include "llvm/Transforms/IPO/PassManagerBuilder.h"using namespace llvm;namespace { struct SkeletonPass : public FunctionPass { static char ID; SkeletonPass() : FunctionPass(ID) {} virtual bool runOnFunction(Function &F) { errs() << "I saw a function called " << F.getName() << "!\n"; return false; } };}char SkeletonPass::ID = 0;// Automatically enable the pass.// http://adriansampson.net/blog/clangpass.htmlstatic void registerSkeletonPass(const PassManagerBuilder &, legacy::PassManagerBase &PM) { PM.add(new SkeletonPass());}static RegisterStandardPasses RegisterMyPass(PassManagerBuilder::EP_EarlyAsPossible, registerSkeletonPass);</code></pre><h2 id="编译、运行"><a href="#编译、运行" class="headerlink" title="编译、运行"></a>编译、运行</h2><p>构建我们的Pass</p><pre><code>$ git clone https://github.com/sampsyo/llvm-pass-skeleton.git$ cd llvm-pass-skeleton$ mkdir build$ cd build$ cmake ..$ make</code></pre><p>告诉Cmake你的LLVM安装到哪了:</p><pre><code>LLVM_DIR = ~/桌面/tools/llvm-12.0.1/llvm/llvm-build/lib/cmake/llvm cmake ..</code></pre><p>这会生成一个共享库,位置在<code>build/skeleton/libSkeletonPass.so</code></p><p>创建一个C语言文件</p><pre><code>$ vim a.cint main(){ return 42;}</code></pre><p>调用<code>clang</code>编译执行 C 程序并使用一些flag来指向刚刚编译的共享库:</p><pre><code>bupt@ubuntu-virtual-machine:~/桌面/tools/test_pass/llvm-pass-skeleton$ clang -Xclang -load -Xclang build/skeleton/libSkeletonPass.so a.c</code></pre><p>输出:</p><pre><code>I saw a function called main!</code></pre><p>正常运行!</p><p>最终文件树如下:</p><pre><code>bupt@ubuntu-virtual-machine:~/桌面/tools/test_pass/llvm-pass-skeleton$ tree -L 2.├── a.c├── a.out├── build│ ├── CMakeCache.txt│ ├── CMakeFiles│ ├── cmake_install.cmake│ ├── Makefile│ └── skeleton├── CMakeLists.txt├── LICENSE├── README.md└── skeleton ├── CMakeLists.txt └── Skeleton.cpp</code></pre><h2 id="查看IR的组件结构"><a href="#查看IR的组件结构" class="headerlink" title="查看IR的组件结构"></a>查看IR的组件结构</h2><p>IR中的组件结构:</p><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20221004235336.png" alt="IR中的组件结构"></p><ul><li><p>一个<a href="https://link.zhihu.com/?target=http://llvm.org/docs/doxygen/html/classllvm_1_1Module.html">模块</a>(<a href="https://link.zhihu.com/?target=http://llvm.org/docs/doxygen/html/classllvm_1_1Module.html">Module</a>)代表一个源文件(粗略地)或一个<em>翻译单元</em>。Module是其他LLVM IR的对象的顶级容器,每个Module都直接包含了一系列全局变量、函数、该Module依赖的库(或其他Module)以及有关目标特性的各种数据。</p></li><li><p>Modules包含<a href="https://link.zhihu.com/?target=http://llvm.org/docs/doxygen/html/classllvm_1_1Function.html">Function</a>命名的可执行代码块。(在 C++ 中,函数和方法都对应于 LLVM 函数。)</p></li><li><p>除了声明其名称和参数外,Function 主要是<a href="https://link.zhihu.com/?target=http://llvm.org/docs/doxygen/html/classllvm_1_1BasicBlock.html">BasicBlock</a>(BB)的容器。<a href="https://link.zhihu.com/?target=https://en.wikipedia.org/wiki/Basic_block">基本块</a>(BB)可以理解为一段连续的指令。</p></li><li><p>指令是单个代码操作。例如,指令可能是整数加法、浮点除法或存储到内存。</p></li><li><p>LLVM 中的大多数东西(包括 Function、BasicBlock 和 Instruction)都是从名为Value的基类继承而来。<a href="https://www.llvm.org/doxygen/classllvm_1_1Value.html#details:~:text=Detailed%20Description">Value</a>是可以在计算中使用的任何数据——例如,数字或某些代码的地址。全局变量和常量(又名literals或immediates)也是Value。</p></li></ul><p>可以在刚才的Skeleton.cpp文件里修改代码,从而查看每一条IR指令</p><pre><code>$ vim skeleton/Skeleton.cpp</code></pre><p>输出整个函数的IR,遍历函数中的所有basic block,同时遍历basic block中的instruction,将其输出:</p><pre><code class="cpp">errs() << "Function body:\n" << F << "\n";for (auto& B : F) { errs() << "Basic block:\n" << B << "\n"; for (auto& I : B) { errs() << "Instruction: " << I << "\n"; }}</code></pre><p>编译运行:</p><pre><code>$ cd ~/桌面/tools/test_pass/llvm-pass-skeleton/build$ make$ cd ..$ clang -Xclang -load -Xclang build/skeleton/libSkeletonPass.so a.c</code></pre><p>输出结果如下:</p><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20221004093323.png" alt="image-20221004093323543"></p><p>可以看到,其中有1个basic block,3个Instruction</p><h2 id="修改功能"><a href="#修改功能" class="headerlink" title="修改功能"></a>修改功能</h2><p>a.c 代码如下:</p><pre><code class="c">int main(int argc, char **argv){ return argc + 5;}// argc是参数个数,argv是参数列表。// 注意,程序本身会有一个参数</code></pre><p>我们想保持代码中的<code>+</code>不变,但实际实现的是<code>x</code>(乘法)的功能</p><p>需要用到IR builder,这是LLVM的一个库</p><pre><code class="cpp">#include "llvm/IR/IRBuilder.h"</code></pre><p>先把原函数的输出删去,然后在循环里如下改动:</p><pre><code class="cpp">for (auto& I : B) { // 获取二元操作符+,检查是否获取到了 if(auto* op = dyn_cast<BinaryOperator>(&I)){ // 指定 op 出现的位置进行代码改写 IRBuilder<> builder(op); // 从操作数中获取lhs和rhs Value *lhs = op->getOperand(0); Value *rhs = op->getOperand(1); // 用乘法来运算lhs和rhs,保存运算结果 Value *mul = builder.CreateMul(lhs,rhs); //因为我们要用新的操作符来替换旧的操作符 //所以我们要找到所有使用旧的操作符的地方,并换成新的操作符 for(auto& U :op->uses()){ User* user = U.getUser(); user->setOperand(U.getOperandNo(),mul); } // return true来告诉剩余的pipline我们已经改过这个函数了 return true; }}</code></pre><p>说明:</p><ul><li><a href="https://llvm.org/docs/ProgrammersManual.html#isa:~:text=the%20dyn_cast%3C%3E%20operator.-,dyn_cast%3C%3E%3A,-The%20dyn_cast%3C%3E">dyn_cast<BinaryOperator>(&I)</a>是检查强制转换的操作,它检查操作数是否属于指定的类型,如果是则返回指向他的指针,否则返回空,在我们这个例子中,它用来检验<code>I</code> 是不是二元操作符。</li><li><a href="https://llvm.org/doxygen/classllvm_1_1IRBuilder.html">IRBuilder</a>帮助我们创建可能想要的任何类型的指令。</li><li>将新指令拼接到代码中,我们必须找到它原来的所有位置并将新指令作为参数进行交换。</li></ul><p><strong>完整的代码如下:</strong></p><p>Skeleton.cpp:</p><pre><code class="cpp">#include "llvm/Pass.h"#include "llvm/IR/Function.h"#include "llvm/Support/raw_ostream.h"#include "llvm/IR/LegacyPassManager.h"#include "llvm/Transforms/IPO/PassManagerBuilder.h"#include "llvm/IR/InstrTypes.h"#include "llvm/IR/IRBuilder.h"using namespace llvm;namespace { struct SkeletonPass : public FunctionPass { static char ID; SkeletonPass() : FunctionPass(ID) {} virtual bool runOnFunction(Function &F) { errs() << "I saw a function called " << F.getName() << "!\n"; for (auto& B : F) { for (auto& I : B) { if(auto* op = dyn_cast<BinaryOperator>(&I)){ IRBuilder<> builder(op); Value *lhs = op->getOperand(0); Value *rhs = op->getOperand(1); Value *mul = builder.CreateMul(lhs,rhs); for(auto& U :op->uses()){ User* user = U.getUser(); user->setOperand(U.getOperandNo(),mul); } return true; } } } return false; } };}char SkeletonPass::ID = 0;// Automatically enable the pass.// http://adriansampson.net/blog/clangpass.htmlstatic void registerSkeletonPass(const PassManagerBuilder &,</code></pre><p>编译运行:</p><pre><code>$ cd ..$ cd build$ make$ cd ..$ clang -Xclang -load -Xclang build/skeleton/libSkeletonPass.so a.c</code></pre><p>看不到什么输出</p><p>可以这样检验效果</p><p>首先编译</p><pre><code>$ clang -Xclang -load -Xclang build/skeleton/libSkeletonPass.so a.c$ clang a.c</code></pre><p>然后运行,并传入参数:</p><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20221004110653.png" alt="image-20221004110653818"></p><p><strong>结果说明:</strong></p><p><code>echo $?</code>是询问返回码,即return的值,源文件没有变,输出:argc+5</p><p>可以看到当不给a.out传入参数的时候,默认参数个数是1,输出5,即1*5</p><p>当给a.out传入3个参数的时候,参数个数是4,输出20,即4*5</p><p>说明实现了用<code>x</code>(乘法)替换<code>+</code>的功能:)</p><h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><p><a href="https://vod.video.cornell.edu/media/1_4nrtmvc9">CS 6120</a></p><p><a href="https://llvm.org/docs/WritingAnLLVMPass.html#writing-an-llvm-pass-registration">LLVM 官网</a></p><p><a href="https://llvm.liuxfe.com/tutorial/writepass/quickstart">LLVM 中文网</a></p><p><a href="https://www.cs.cornell.edu/~asampson/blog/llvm.html">LLVM for Grad Students</a></p><p><a href="https://zhuanlan.zhihu.com/p/290946850">LLVM中的pass及其管理机制</a></p>]]></content>
<categories>
<category> LLVM </category>
</categories>
<tags>
<tag> LLVM </tag>
<tag> Pass </tag>
</tags>
</entry>
<entry>
<title>02_MLIR A Compiler Infrastructure for the End of Moore’s Law</title>
<link href="/2022/09/18/%E8%AE%BA%E6%96%87%E7%AC%94%E8%AE%B0/02_MLIR%20A%20Compiler%20Infrastructure%20for%20the%20End%20of%20Moore%E2%80%99s%20Law/"/>
<url>/2022/09/18/%E8%AE%BA%E6%96%87%E7%AC%94%E8%AE%B0/02_MLIR%20A%20Compiler%20Infrastructure%20for%20the%20End%20of%20Moore%E2%80%99s%20Law/</url>
<content type="html"><![CDATA[<h2 id="挑战"><a href="#挑战" class="headerlink" title="挑战"></a>挑战</h2><p>MLIR 工作的开始是由于认识到现代机器学习框架由许多不同的编译器、图处理技术和运行时系统组成(如图一),但是他们不共享一个共同的基础设施,也没有都遵循编译器设计的最佳方式。这表现在很多用户可见的方面上。</p><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220916164009.png" alt="image-20220916164002451"></p><p>人们认识到编译器行业有一个相似的问题:现有的系统,像LLVM很成功的把不同语言统一和集成起来,但是高级语言总是发明自己的IR,并为更高层次的抽象重新发明了很多相同的技术,与此同时,LLVM社区经常纠结于如何最好地表示并行结构,如何共享公共前端,但没有令人满意的答案。</p><p>我们负担不起改进N个编译器,所以想要构建一个更加通用的解决方案,</p><p>待续</p>]]></content>
<categories>
<category> 论文 </category>
</categories>
<tags>
<tag> 论文 </tag>
<tag> 论文笔记 </tag>
<tag> MLIR </tag>
</tags>
</entry>
<entry>
<title>01_ScaleHLS</title>
<link href="/2022/09/15/%E8%AE%BA%E6%96%87%E7%AC%94%E8%AE%B0/01_ScaleHLS/"/>
<url>/2022/09/15/%E8%AE%BA%E6%96%87%E7%AC%94%E8%AE%B0/01_ScaleHLS/</url>
<content type="html"><![CDATA[<h2 id="论文信息"><a href="#论文信息" class="headerlink" title="论文信息"></a>论文信息</h2><p><strong>ScaleHLS: A New Scalable High-Level Synthesis Framework on Multi-Level Intermediate Representation</strong></p><p><em>[Submitted on 24 Jul 2021 (*<a href="https://arxiv.org/abs/2107.11673v1">v1</a></em>), last revised 22 Dec 2021 (this version, v4)]*</p><p>HPCA(High Performance Computer Architecture),既高性能计算架构会议是体系结构/高性能计算领域最重要的学术会议之一,基本上都是最顶尖的研究小组在上面发文章。HPCA为CCF A类,Core Conference Ranking A*类会议,H-5指数53,Impace Score 6.93,录取率常年在20%左右。该会议历史悠久,几十年的举办历史中,国内只13年在深圳举办一次,绝大多数举办地在美国。</p><h2 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h2><h3 id="HLS-High-level-synthesis-高级综合"><a href="#HLS-High-level-synthesis-高级综合" class="headerlink" title="HLS: High-level synthesis 高级综合"></a>HLS: High-level synthesis 高级综合</h3><p>在传统的FPGA开发过程中,一般的流程是:编写verilog代码,功能仿真,生成可执行文件,上板调试,采集数据,进行分析,释放版本。但是这个流程中,功能仿真会占用大部分的时间,尤其是现在的算法越来越复杂,如果按照上述流程操作,开发周期会越来越长,需要经过更长时间的仿真和调试,那么是否有一种方法来解决这个问题?</p><p>于是提出了HLS,借助他,我们可以使用高级编程语言,实现对FPGA的功能模块的开发,这帮助解决算法的实现,利用算法工程师实现硬件难以描述的算法,从而节约了开发时间。它聚焦算法的快速迭代和功能的实现,在软件环境中直接进行功能仿真,并与硬件仿真无缝集成。</p><p><a href="https://blog.csdn.net/weixin_49617016/article/details/108005982">高级综合(high-level-synthesis,HLS):软件算法在FPGA上实现硬件加速的综合工具</a></p><p><a href="https://www.elecfans.com/d/1474291.html">高层次综合技术(High-level synthesis)的概念</a></p><p><a href="https://www.bilibili.com/video/BV1TU4y1u7oW?spm_id_from=333.337.search-card.all.click&vd_source=3945e3962fa714687a283b476b79aa64">基于Intel FPGA的HLS开发流程</a></p><p><a href="https://www.cnblogs.com/sea-wind/p/4024665.html">高级综合HLS简介</a></p><h3 id="Design-space-exploration"><a href="#Design-space-exploration" class="headerlink" title="Design space exploration"></a>Design space exploration</h3><p><strong>Design Space Exploration</strong> (DSE) refers to systematic analysis and pruning of unwanted design points based on parameters of interest. While the term DSE can apply to any kind of system, we refer to electronic and embedded system design in this article.</p><p><a href="https://en.wikipedia.org/wiki/Design_space_exploration">Design space exploration</a></p><h2 id="问题提出"><a href="#问题提出" class="headerlink" title="问题提出"></a>问题提出</h2><p>HLS自动把高级语言转换成指定硬件语言的加速器,从而消除了对使用专用硬件描述语言的繁琐和可能容易出错的硬件设计实践的依赖。</p><p>现有的研究工作和解决方案在处理包含大量的子模块和复杂的相互依赖关系的大规模HLS设计方面面临重大困难,主要体现在3个方面:</p><ol><li>Representation: 现有的用来表示和分析HLS的工作设计之初考虑的都是单层抽象的软件编译。但是HLS包含多层次的抽象,直接把多层次的representation合起来容易导致严重的碎片化和繁琐和不一致的跨级别优化。我们认为应该有一个系统的方法来在多个抽象层次上表示HLS设计,以体现HLS设计的内在层次结构。</li><li>Optinization: 现有的工作把很多重要的HLS优化留给了人工设计员来进行代码重写,这种方法不够高效和可扩展,不足以处理大型HLS设计,并可能阻碍综合DSE。我们认为HLS优化应该完全自动化而不是依赖人工重写代码,这个优化应该在多个不同的抽象级别上自动执行。</li><li>Exploration: 传统的编译器领域,每项优化技术的参数通常由一个成本模型决定,表名参数组合的收益。但是HLS设计中,由于不同的HLS优化的效果相互关联(或冲突),我们不能把一个优化之间的收益隔离来看。为了解决这个问题,一个全局的DSE引擎需要考虑跨不同抽象层次的所有HLS优化。</li></ol><h2 id="目标"><a href="#目标" class="headerlink" title="目标"></a>目标</h2><h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><h2 id="性能评估"><a href="#性能评估" class="headerlink" title="性能评估"></a>性能评估</h2><h2 id="评价"><a href="#评价" class="headerlink" title="评价"></a>评价</h2>]]></content>
<categories>
<category> 论文 </category>
</categories>
<tags>
<tag> 论文 </tag>
<tag> 论文笔记 </tag>
<tag> MLIR </tag>
</tags>
</entry>
<entry>
<title>00_MLIR简介</title>
<link href="/2022/09/08/MLIR/MLIR%E7%AE%80%E4%BB%8B/"/>
<url>/2022/09/08/MLIR/MLIR%E7%AE%80%E4%BB%8B/</url>
<content type="html"><![CDATA[<h2 id="一、MLIR简介"><a href="#一、MLIR简介" class="headerlink" title="一、MLIR简介"></a>一、MLIR简介</h2><p>下面这张图分为高级编程语言和机器学习框架两个部分。</p><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220920162058.png" alt="常见的IR表示系统"></p><p>以C语言为例,在编译阶段,会由前端Clang编译生成抽象语法树(AST),AST之后会被转换成LLVM IR,再转换成机器码,在指定的机器上运行。值得注意的是,C语言在转换成LLVM IR之前,不会进行特定语言的优化,程序的优化,主要集中在LLVM IR阶段,它会丢失很多信息,也会带来优化不充分的问题。</p><p>Java语言不会生成AST,而是将Java代码转换成Java字节码,这是一种内部IR,他可以进行自己语言的优化,在此之后Java字节码会转换成LLVM IR,进一步的优化,根据LLVM后端进一步的转换成字节码。</p><p>Swift语言转换成AST之后,也会创建自己语言的IR,这种方式也被Rust等语言采用。</p><p>不仅在高级语言中,在深度学习框架中也有类似的情况,如TensorFlow框架,会先转化成图IR,进一步转换成针对某个硬件的IR表示,如针对TPU,手机设备等,面对这种情况,开发人员需要进行不同的IR设计以及针对这种IR的优化。</p><p>但是随着IR种类的增多,会出现一些问题:</p><ol><li>针对不同种类的IR开发的Pass优化可能重复,也就是不同的IR的同类Pass不兼容,针对新的IR编写同类的Pass需要重新学习IR语法,门槛过高</li><li>不同类型的IR所做的Pass优化在下一层中不可见,各种IR都想在当前层中优化做的更好,这就会导致优化的重复,导致效率偏低。</li><li>不同类型IR间转换开销大,从图IR到LLVM IR直接转换存在较大的开销,比如从图IR转换到LLVM IR 。</li></ol><p>因此, TensorFlow团队提出了MLIR,它希望作为一种编译器的基础设施,将编译流程中的各个层次的IR进行统一的表示,降低新加入的前端语言或是后端硬件构建编译器的成本,帮助现在已有的编译器连接在一起,进行复用。</p><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220920160838.png" alt="常见的IR种类"></p><hr><center>以下的内容还待完善</center><hr><p>MLIR中最重要的概念是它的Dialect方言系统。</p><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220920160754.png" alt="使用Dialect构建IR表示系统"></p><p>Dialect其实是表示一种层次的IR,将各种IR转换成对应的MLIR Dialect。 </p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220920152804.png" alt="MLIR表达式" style="zoom: 67%;" /><p>MLIR Dialect</p><p>一个 MLIR dialect 包括 :</p><ul><li>命名空间</li><li>一组自定义类型</li><li><strong>一组 operations,这是核心元素</strong></li><li>解析器、打印器</li><li><strong>优化(pass): analysis, transformations, dialect conversions.</strong></li></ul><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220920153435.png" alt="translation,conversion,transformation之间的区别"></p><p>conversion : Dialect之间的转换</p><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220920153650.png" alt="conversion : Dialect之间的转换"></p><p>可以利用mlirGen模块来遍历AST,生成对应的Toy Dialect的MLIR表达式,那么builder创建的operation是从哪来的?以及Toy Dialect是从哪来的?针对这两个问题,我们需要了解MLIR系统是如何引用自定义的Dialect和其中的一些Operation</p><p>后面ODS框架有点没听懂 </p><p><a href="https://www.bilibili.com/video/BV1Hd4y1U7mb?spm_id_from=333.999.0.0&vd_source=3945e3962fa714687a283b476b79aa64">https://www.bilibili.com/video/BV1Hd4y1U7mb?spm_id_from=333.999.0.0&vd_source=3945e3962fa714687a283b476b79aa64</a></p>]]></content>
<categories>
<category> MLIR </category>
</categories>
<tags>
<tag> MLIR </tag>
</tags>
</entry>
<entry>
<title>STL</title>
<link href="/2022/07/02/%E7%AE%97%E6%B3%95/sjtu%E6%9C%BA%E8%AF%95/STL/"/>
<url>/2022/07/02/%E7%AE%97%E6%B3%95/sjtu%E6%9C%BA%E8%AF%95/STL/</url>
<content type="html"><![CDATA[<p>[TOC]</p><p> STL set</p><pre><code>3.1.2 使⽤用set定义⼀一个set对象:set<int> s;set<double> ss;</code></pre><p>set的基本操作:</p><pre><code class="cpp">s.begin() // 返回指向第⼀一个元素的迭代器器s.clear() // 清除所有元素s.count() // 返回某个值元素的个数s.empty() // 如果集合为空,返回true(真)s.end() // 返回指向最后⼀一个元素之后的迭代器器,不不是最后⼀一个元素s.equal_range() // 返回集合中与给定值相等的上下限的两个迭代器器s.erase() // 删除集合中的元素s.find() // 返回⼀一个指向被查找到元素的迭代器器s.get_allocator() // 返回集合的分配器器s.insert() // 在集合中插⼊入元素s.lower_bound() // 返回指向⼤大于(或等于)某值的第⼀一个元素的迭代器器s.key_comp() // 返回⼀一个⽤用于元素间值⽐比较的函数s.max_size() // 返回集合能容纳的元素的最⼤大限值s.rbegin() // 返回指向集合中最后⼀一个元素的反向迭代器器s.rend() // 返回指向集合中第⼀一个元素的反向迭代器器s.size() // 集合中元素的数⽬目s.swap() // 交换两个集合变量量s.upper_bound() // 返回⼤大于某个值元素的迭代器器s.value_comp() // 返回⼀一个⽤用于⽐比较元素间的值的函数</code></pre><p>STL vector</p><pre><code class="cpp">定义vector向量量对象:vector<int> s;// 定义⼀一个空的vector对象,存储的是int类型的元素vector<int> s(n);// 定义⼀一个含有n个int元素的vector对象vector<int> s(first, last);// 定义⼀一个vector对象,并从由迭代器器first和last定义的序列列[first, last)中复制初值</code></pre><p>vector的基本操作:</p><pre><code class="cpp">s[i] // 直接以下标⽅方式访问容器器中的元素 s.front() // 返回⾸首元素 s.back() // 返回尾元素 s.push_back(x) // 向表尾插⼊入元素x s.size() // 返回表⻓长 s.empty() // 表为空时,返回真,否则返回假 s.pop_back() // 删除表尾元素 s.begin() // 返回指向⾸首元素的随机存取迭代器器 s.end() // 返回指向尾元素的下⼀一个位置的随机存取迭代器器 s.insert(it, val) // 向迭代器器it指向的元素前插⼊入新元素val s.insert(it, n, val) // 向迭代器器it指向的元素前插⼊入n个新元素val s.insert(it, first, last) // 将由迭代器器first和last所指定的序列列[first, last)插⼊入到迭代器器it指向的元素前⾯面 s.erase(it) // 删除由迭代器器it所指向的元素 s.erase(first, last) // 删除由迭代器器first和last所指定的序列列[first, last) s.reserve(n) // 预分配缓冲空间,使存储空间⾄至少可容纳n个元素 s.resize(n) // 改变序列列⻓长度,超出的元素将会全部被删除,如果序列列需要扩展(原空间 ⼩小于n),元素默认值将填满扩展出的空间 s.resize(n, val) // 改变序列列⻓长度,超出的元素将会全部被删除,如果序列列需要扩展(原空间 ⼩小于n),val将填满扩展出的空间 s.clear() // 删除容器器中的所有元素 s.swap(v) // 将s与另⼀一个vector对象进⾏行行交换 s.assign(first, last) // 将序列列替换成由迭代器器first和last所指定的序列列[first, last),[first, last)不不能是原序列列中的⼀一部分 // 要注意的是,resize操作和clear操作都是对表的有效元素进⾏行行的操作,但并不不⼀一定会改变缓冲 空间的⼤大⼩小 // 另外,vector还有其他的⼀一些操作,如反转、取反等,不不再⼀一⼀一列列举 // vector上还定义了了序列列之间的⽐比较操作运算符(>、<、>=、<=、==、!=),可以按照字典序⽐比 较两个序列列。 // 还是来看⼀一些示例例代码吧…… /** 输⼊入个数不不定的⼀一组整数,再将这组整数按倒序输出*/ int main(){ vector<int> L; int x; while(cin >> x) { L.push_back(x); } for (int i = L.size() - 1; i >= 0; i--) { cout << L[i] << " "; } cout << endl; return 0;}</code></pre><p>string</p><pre><code class="cpp">int main(){ string s = "Hello!", name; cin >> name; s += name; s += '!'; cout << s << endl; return 0;}</code></pre><pre><code class="cpp">/** 1064--Parencoding(吉林林⼤大学OJ) 题解⽚片段* ⽤用string作为容器器,实现由P代码还原括号字符串串*/int main(){ int m; cin >> m; // p编码的⻓长度 string str; // ⽤用来存放还原出来的括号字符串串 int leftpa = 0; // 记录已经出现的左括号的总数 for (int i = 0; i < m; i++) { int p; cin >> p; for (int j = 0; j < p - leftpa; j++) { str += '('; } str += ')'; leftpa = p; } return 0;}</code></pre><p>定义stack对象:</p><pre><code class="cpp">stack<int> s;stack<string> ss;</code></pre><pre><code class="cpp">s.push(x); // ⼊入栈s.pop(); // 出栈s.top(); // 访问栈顶s.empty(); // 当栈空时,返回trues.size(); // 访问栈中元素个数</code></pre><pre><code class="cpp">/** 1064--Parencoding(吉林林⼤大学OJ)* string和stack实现*/int main(){ int n; cin >> n; for (int i = 0; i < n; i++) { int m; cin >> m; string str; int leftpa = 0; for (int j = 0; j < m; j++) { int p; cin >> p; for (int k = 0; k < p - leftpa; k++) { str += '('; } str += ')'; leftpa = p; } stack<int> s; for (string::iterator it = str.begin(); it != str.end(); it++) { if (*it == '(') { s.push(1); } else { int p = s.top(); s.pop(); cout << p << " "; if (!s.empty()) { s.top() += p; } } cout << '\n'; } } return 0;}</code></pre><p>STL queue</p><pre><code class="cpp">queue<int> q;queue<double> qq;</code></pre><pre><code class="cpp">q.push(x); // ⼊入队列列q.pop(); // 出队列列q.front(); // 访问队⾸首元素q.back(); // 访问队尾元素q.empty(); // 判断队列列是否为空q.size(); // 访问队列列中的元素个数</code></pre><pre><code class="cpp">priority_queue<int> q;priority_queue<pair<int, int> > qq; // 注意在两个尖括号之间⼀一定要留留空格,防⽌止误判priority_queue<int, vector<int>, greater<int> > qqq; // 定义⼩小的先出队列列</code></pre><p>priority_queue的基本操作:</p><pre><code class="cpp">q.empty() // 如果队列列为空,则返回true,否则返回falseq.size() // 返回队列列中元素的个数q.pop() // 删除队⾸首元素,但不不返回其值q.top() // 返回具有最⾼高优先级的元素值,但不不删除该元素q.push(item) // 在基于优先级的适当位置插⼊入新元素</code></pre><pre><code class="cpp">class T{ public: int x, y, z; T(int a, int b, int c) : x(a), y(b), z(c) {}};bool operator < (const T &tOne, const T &tTwo){ return tOne.z < tTwo.z; // 按照z的顺序来决定tOne和tTwo的顺序}int main(){ priority_queue<T> q; q.push(T(4, 4, 3)); q.push(T(2, 2, 5)); q.push(T(1, 5, 4)); q.push(T(3, 3, 6)); while (!q.empty()) { T t = q.top(); q.pop(); cout << t.x << " " << t.y << " " << t.z << '\n'; } return 0;}/** 输出结果为:* 4 4 3* 1 5 4* 2 2 5* 3 3 6*/如果我们将第⼀一个例例⼦子中的⽐比较运算符重载为: bool operator < (const T &tOne, const T &tTwo){ return tOne.z > tTwo.z; // 按照z的顺序来决定tOne和tTwo的顺序}</code></pre><p> STL map</p><p>定义map对象:</p><pre><code class="cpp">map<string, int> m;</code></pre><p>map的基本操作:</p><pre><code class="cpp">/* 向map中插⼊入元素 */m[key] = value; // [key]操作是map很有特⾊色的操作,如果在map中存在键值为key的元素对, 则返回该元素对的值域部分,否则将会创建⼀一个键值为key的元素对,值域为默认值。 所以可以⽤用该操作向map中插⼊入元素对或修改已经存在的元素对的值域部分。 m.insert(make_pair(key, value)); // 也可以直接调⽤用insert⽅方法插⼊入元素对,insert操作会返回⼀一个pair,当map中没有与key相匹配的键值时,其first是指向插⼊入元素对的迭代器器,其second为true;若 map中已经存在与key相等的键值时,其first是指向该元素对的迭代器器,second为false。 /* 查找元素 */ int i = m[key]; // 要注意的是,当与该键值相匹配的元素对不不存在时,会创建键值为key(当另⼀一个元素是整形时,m[key]=0)的元素对。 map<string, int>::iterator it = m.find(key); // 如果map中存在与key相匹配的键值时,find操作将返回指向该元素对的迭代器器,否则,返回的迭代器器等于map的end()(参⻅见vector中提到的begin()和end() 操作)。 /* 删除元素 */ m.erase(key); // 删除与指定key键值相匹配的元素对,并返回被删除的元素的个数。m.erase(it); // 删除由迭代器器it所指定的元素对,并返回指向下⼀一个元素对的迭代器器。/* 其他操作 */m.size(); // 返回元素个数m.empty(); // 判断是否为空m.clear(); // 清空所有元素Ex_One:typedef map<int, string, less<int> > M_TYPE typedef M_TYPE::iterator M_IT typedef M_TYPR::const_iterator M_CIT int main(){ /? 17 358 v 2.1 2017.09.21 M_TYPR myTestMap; myTestMap[3] = "No.3"; myTestMap[5] = "No.5"; myTestMap[1] = "No.1"; myTestMap[2] = "No.2"; myTestMap[4] = "No.4"; M_IT itStop = myTestMap.find(2); cout << "myTestMap[2] = " << itStop->second << endl; itStop->second = "No.2 After modification"; cout << "myTestMap[2] = " << itStop->second << endl; cout << "Map contents:" << endl; for (M_CIT it = myTestMap.begin(); it != myTestMap.end(); it++) { cout << it->second << endl; } return 0;}/** 程序的输出结果为:* MyTestMap[2] = No.2* MyTestMap[2] = No.2 After modification Map contents :* No.1* No.2 After modification* No.3* No.4* No.5*/</code></pre><pre><code class="cpp">Ex_Two:int main(){ map<string, int> m; m["one"] = 1; m["two"] = 2; // ⼏几种不不同的 insert 调⽤用⽅方法 m.insert(make_pair("three", 3)); m.insert(map<string, int>::value_type("four", 4)); m.insert(pair<string, int>("five", 5)); string key; while (cin >> key) { map<string, int>::iterator it = m.find(key); if (it == m.end()) { cout << "No such key!" << endl; } else { cout << key << " is " << it->second << endl; cout << "Erased " << m.erase(key) << endl; } } return 0;}</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> sjtu机试 </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> 模拟 </tag>
</tags>
</entry>
<entry>
<title>acwing</title>
<link href="/2022/07/02/%E7%AE%97%E6%B3%95/sjtu%E6%9C%BA%E8%AF%95/acwing/"/>
<url>/2022/07/02/%E7%AE%97%E6%B3%95/sjtu%E6%9C%BA%E8%AF%95/acwing/</url>
<content type="html"><![CDATA[<p>[TOC]</p><h3 id="AcWing-3581-单词识别"><a href="#AcWing-3581-单词识别" class="headerlink" title="AcWing 3581. 单词识别"></a>AcWing 3581. 单词识别</h3><p>统计每个单词的出现次数</p><hr><ul><li><p>把一个字母转换成小写字母:tolower(str[j ++ ])</p></li><li><p>判断某个字符是不是字母:isalpha(str[j])</p></li><li><p>遍历map:auto& [k, v]: hash</p></li></ul><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#include <map>using namespace std;int main(){ string str; getline(cin, str); map<string, int> hash; for (int i = 0; i < str.size(); i ++ ) { if (isalpha(str[i])) { int j = i; string word; while (j < str.size() && isalpha(str[j])) word += tolower(str[j ++ ]); hash[word] ++ ; i = j; } } for (auto& [k, v]: hash) cout << k << ':' << v << endl; return 0;}</code></pre><h3 id="n-后面的0"><a href="#n-后面的0" class="headerlink" title="n!后面的0"></a>n!后面的0</h3><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;int main(){ int n; cin >> n; int res = 0; while (n / 5) res += n / 5, n /= 5; cout << res << endl; return 0;}</code></pre><h3 id="AcWing-3484-整除问题"><a href="#AcWing-3484-整除问题" class="headerlink" title="AcWing 3484. 整除问题"></a>AcWing 3484. 整除问题</h3><p>给定 n,a 求最大的 k,使 n! 可以被 a^k 整除但不能被 a(k+1) 整除。</p><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#include <vector>using namespace std;vector<vector<int>> get_ds(int n)//得到n的所有质因子及其次数{ vector<vector<int>> res; for (int i = 2; i * i <= n; i ++ ) if (n % i == 0) { int s = 0; while (n % i == 0) n /= i, s ++ ; res.push_back({i, s}); } if (n > 1) res.push_back({n, 1}); return res;}int get_p(int n, int p)// n!里面有几个p相乘{ int res = 0; while (n / p) res += n / p, n /= p; return res;}int main(){ int n, m; cin >> n >> m; auto ds = get_ds(m); int res = 1e8; for (int i = 0; i < ds.size(); i ++ ) { int p = ds[i][0], s = ds[i][1]; res = min(res, (get_p(n, p) / s)); } cout << res << endl; return 0;}</code></pre><p>重复者</p><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#include <vector>using namespace std;int n;vector<string> p;vector<string> g(int k){ if (k == 1) return p; auto s = g(k - 1); int m = s.size(); vector<string> res(n * m); for (int i = 0; i < n * m; i ++ ) res[i] = string(n * m, ' '); for (int i = 0; i < n; i ++ ) for (int j = 0; j < n; j ++ ) if (p[i][j] != ' ') for (int x = 0; x < m; x ++ ) for (int y = 0; y < m; y ++ ) res[i * m + x][j * m + y] = s[x][y]; return res;}int main(){ while (cin >> n, n) { p.clear(); getchar(); // 读掉n后的回车 for (int i = 0; i < n; i ++ ) { string line; getline(cin, line); p.push_back(line); } int k; cin >> k; auto res = g(k); for (auto& s: res) cout << s << endl; } return 0;}</code></pre><h3 id="AcWing-3874-三元组的最小距离"><a href="#AcWing-3874-三元组的最小距离" class="headerlink" title="AcWing 3874. 三元组的最小距离"></a>AcWing 3874. 三元组的最小距离</h3><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;typedef long long LL;const int N = 100010;int l, m, n;int a[N], b[N], c[N];int main(){ scanf("%d%d%d", &l, &m, &n); for (int i = 0; i < l; i ++ ) scanf("%d", &a[i]); for (int i = 0; i < m; i ++ ) scanf("%d", &b[i]); for (int i = 0; i < n; i ++ ) scanf("%d", &c[i]); LL res = 1e18; for (int i = 0, j = 0, k = 0; i < l && j < m && k < n;) { int x = a[i], y = b[j], z = c[k]; res = min(res, (LL)max(max(x, y), z) - min(min(x, y), z)); if (x <= y && x <= z) i ++ ; else if (y <= x && y <= z) j ++ ; else k ++ ; } printf("%lld\n", res * 2); return 0;}</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> sjtu机试 </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> 模拟 </tag>
</tags>
</entry>
<entry>
<title>写法</title>
<link href="/2022/07/02/%E7%AE%97%E6%B3%95/sjtu%E6%9C%BA%E8%AF%95/%E5%86%99%E6%B3%95/"/>
<url>/2022/07/02/%E7%AE%97%E6%B3%95/sjtu%E6%9C%BA%E8%AF%95/%E5%86%99%E6%B3%95/</url>
<content type="html"><![CDATA[<p>[TOC]</p><p>定义二维vector</p><pre><code class="cpp">vector<vector <int> > vec(n ,vector<int>(m)); </code></pre><p>生成组合数</p><pre><code class="cpp">//求组合数的模板要背会for (int i = 0; i <= n; i ++ ) for (int j = 0; j <= i; j ++ ) if (!j) C[i][j] = 1; else C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD;</code></pre><h3 id="lower-bound"><a href="#lower-bound" class="headerlink" title="lower_bound()"></a>lower_bound()</h3><p>返回值是一个迭代器,返回指向<strong>大于等于</strong>key的第一个值的位置</p><p>对象:有序数组或容器</p><p>数组:</p><pre><code class="cpp">#include <algorithm>#include <iostream>using namespace std;int main(){ int a[]={1,2,3,4,5,7,8,9}; printf("%d",lower_bound(a,a+8,6)-a); return 0; } </code></pre><p>输出:5</p><p>将key换成10,所有val都小于key,返回last的位置</p><pre><code class="cpp">#include <algorithm>#include <iostream>using namespace std;int main(){ int a[]={1,2,3,4,5,7,8,9}; printf("%d",lower_bound(a,a+8,10)-a); return 0; } </code></pre><p>输出: 8</p><p>vector: </p><pre><code class="cpp">#include<iostream>#include<algorithm>#include<vector>using namespace std;int main(){ vector<int> A; A.push_back(1); A.push_back(2); A.push_back(3); A.push_back(4); A.push_back(5); A.push_back(7); A.push_back(8); A.push_back(9); int pos = lower_bound(A.begin() , A.end() , 6)-A.begin(); cout << pos << endl; return 0; }</code></pre><p> 输出还是5</p><p>对应lower_bound()函数是upper_bound()函数,它返回大于等于key的最后一个元素</p><p>也同样是要求有序数组,若数组中无重复元素,则两者返回值相同</p><p>判断a数组中有没有x:(a数组有序)</p><pre><code class="cpp">int t = lower_bound(a,a+n,x)-a;if(a[t] != x)cout << "NO" << endl;else cout << "YES" << endl;</code></pre><h3 id="字符串toupper"><a href="#字符串toupper" class="headerlink" title="字符串toupper"></a>字符串toupper</h3><p>string s;</p><p>cout << (char)toupper(s[i]);</p><p>cout<<char(st[i]-32)</p><h3 id="to-string"><a href="#to-string" class="headerlink" title="to_string()"></a>to_string()</h3><p>看某个数字的某一位是否有7:</p><p>to_string(i).find(“7”) != -1</p><h3 id="gcd"><a href="#gcd" class="headerlink" title="gcd()"></a>gcd()</h3><p>int gcd(int a,int b){</p><p> return b ? gcd(b, a % b) :a;</p><p>}</p>]]></content>
<categories>
<category> 算法 </category>
<category> sjtu机试 </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> 模拟 </tag>
</tags>
</entry>
<entry>
<title>字符串</title>
<link href="/2022/07/02/%E7%AE%97%E6%B3%95/sjtu%E6%9C%BA%E8%AF%95/%E5%AD%97%E7%AC%A6%E4%B8%B2/"/>
<url>/2022/07/02/%E7%AE%97%E6%B3%95/sjtu%E6%9C%BA%E8%AF%95/%E5%AD%97%E7%AC%A6%E4%B8%B2/</url>
<content type="html"><![CDATA[<p>字符串,就是由字符连接而成的序列。</p><p>常见的字符串问题包括字符串匹配问题、子串相关问题、前缀/后缀相关问题、回文串相关问题、子序列相关问题等。</p><span id="more"></span><p> [TOC]</p><h2 id="字符串各种输入处理"><a href="#字符串各种输入处理" class="headerlink" title="字符串各种输入处理"></a>字符串各种输入处理</h2><p>学C++的时候,这几个输入函数弄的有点迷糊;这里做个小结,为了自己复习,也希望对后来者能有所帮助,如果有差错的地方还请各位多多指教(本文所有程序均通过VC 6.0运行)</p><p>1、cin<br>2、cin.get()<br>3、cin.getline()<br>4、getline()<br>5、gets()<br>6、getchar()</p><p>附:cin.ignore();cin.get()//跳过一个字符,例如不想要的回车,空格等字符</p><h3 id="1、cin-gt-gt"><a href="#1、cin-gt-gt" class="headerlink" title="1、cin>>"></a><em><strong>1、cin>></strong></em></h3><p><em><strong>用法 1:</strong></em> 最基本,也是最常用的用法,输入一个数字:</p><pre><code class="c">#include <iostream>using namespace std;int main (){ int a,b; cin>>a>>b; cout<<a+b<<endl;}1234567输入:2[回车]3[回车]输出:512</code></pre><p><strong>注意:</strong>>> 是会过滤掉不可见字符(如 空格 回车,TAB 等)<br>cin>>noskipws>>input[j];//不想略过空白字符,那就使用 noskipws 流控制</p><p><em><strong>用法 2:</strong></em> 接受一个字符串,遇“空格”、“TAB”、“回车”都结束</p><pre><code class="c">#include <iostream>using namespace std;int main (){ char a[20]; cin>>a; cout<<a<<endl;}输入:jkljkljkl输出:jkljkljkl输入:jkljkl jkljkl //遇空格结束输出:jkljkl</code></pre><h3 id="2、cin-get"><a href="#2、cin-get" class="headerlink" title="2、cin.get()"></a><em><strong>2、cin.get()</strong></em></h3><p><em><strong>用法 1:</strong></em> cin.get(字符变量名)可以用来接收字符</p><pre><code class="c++">#include <iostream>using namespace std;int main (){ char ch; ch=cin.get(); //或者cin.get(ch); cout<<ch<<endl;}输入:jljkljkl输出:j</code></pre><p><em><strong>用法 2:</strong></em> cin.get(字符数组名,接收字符数目)用来接收一行字符串,可以接收空格</p><pre><code class="c">#include <iostream>using namespace std;int main (){ char a[20]; cin.get(a,20); cout<<a<<endl;}12345678输入:jkl jkl jkl输出:jkl jkl jkl12输入:abcdeabcdeabcdeabcdeabcde (输入25个字符)输出:abcdeabcdeabcdeabcd (接收19个字符+1个'\0')1</code></pre><p><em><strong>用法 3:</strong></em> cin.get(无参数)没有参数主要是用于舍弃输入流中的不需要的字符,或者舍弃回车,弥补cin.get(字符数组名,接收字符数目)的不足。</p><h3 id="3、cin-getline"><a href="#3、cin-getline" class="headerlink" title="3、cin.getline()"></a><em><strong>3、cin.getline()</strong></em></h3><p><strong>// 接受一个字符串,可以接收空格并输出*</strong></p><pre><code class="c">#include <iostream>using namespace std; int main (){ char m[20]; cin.getline(m,5); cout<<m<<endl;}1234567输入:jkljkljkl输出:jklj12</code></pre><p>接受5个字符到m中,其中最后一个为’\0’,所以只看到4个字符输出;</p><p>如果把5改成20:</p><pre><code>输入:jkljkljkl输出:jkljkljkl12输入:jklf fjlsjf fjsdklf输出:jklf fjlsjf fjsdklf12</code></pre><h5 id="延伸"><a href="#延伸" class="headerlink" title="延伸"></a>延伸</h5><ul><li>cin.getline()实际上有三个参数,cin.getline(接受字符串m,接受个数5,结束字符)</li><li>当第三个参数省略时,系统默认为’\0’</li><li>如果将例子中cin.getline()改为cin.getline(m,5,‘a’);当输入jlkjkljkl时输出jklj,输入jkaljkljkl时,输出jk</li></ul><p>当用在多维数组中的时候,也可以用cin.getline(m[i],20)之类的用法:</p><pre><code class="c">#include<iostream>#include<string>using namespace std;int main (){ char m[3][20]; for(int i=0;i<3;i++){ cout<<"\n请输入第"<<i+1<<"个字符串:"<<endl; cin.getline(m[i],20); } cout<<endl; for(int j=0;j<3;j++) cout<<"输出m["<<j<<"]的值:"<<m[j]<<endl;}12345678910111213请输入第1个字符串:kskr11请输入第2个字符串:kskr21请输入第3个字符串:kskr31输出m[0]的值:kskr1输出m[1]的值:kskr2输出m[2]的值:kskr31</code></pre><h3 id="4、getline"><a href="#4、getline" class="headerlink" title="4、getline()"></a><em><strong>4、getline()</strong></em></h3><p><em><strong>// 接受一个字符串,可以接收空格并输出,需包含“#include”</strong></em></p><pre><code class="c">#include<iostream>#include<string>using namespace std;int main (){ string str; getline(cin,str); cout<<str<<endl;}123456789输入:jkljkljkl输出:jkljkljkl12输入:jkl jfksldfj jklsjfl输出:jkl jfksldfj jklsjfl12</code></pre><p>和cin.getline()类似,但是cin.getline()属于istream流,而getline()属于string流,是不一样的两个函数</p><h3 id="5、gets"><a href="#5、gets" class="headerlink" title="5、gets()"></a><em><strong>5、gets()</strong></em></h3><p><em><strong>// 接受一个字符串,可以接收空格并输出,需包含“#include”</strong></em></p><pre><code class="c">#include<iostream>#include<string>using namespace std;int main (){ char m[20]; gets(m); //不能写成m=gets(); cout<<m<<endl;}12345678910输入:jkljkljkl输出:jkljkljkl12输入:jkl jkl jkl输出:jkl jkl jkl12</code></pre><p>类似cin.getline()里面的一个例子,gets()同样可以用在多维数组里面:</p><pre><code class="c">#include<iostream>#include<string>using namespace std;int main (){ char m[3][20]; for(int i=0;i<3;i++){ cout<<"\n请输入第"<<i+1<<"个字符串:"<<endl; gets(m[i]); } cout<<endl; for(int j=0;j<3;j++) cout<<"输出m["<<j<<"]的值:"<<m[j]<<endl;}12345678910111213141516请输入第1个字符串:kskr11请输入第2个字符串:kskr21请输入第3个字符串:kskr31输出m[0]的值:kskr1输出m[1]的值:kskr2输出m[2]的值:kskr3123</code></pre><p>自我感觉gets()和cin.getline()的用法很类似,只不过cin.getline()多一个参数罢了;</p><p>这里顺带说明一下,对于本文中的这个kskr1,kskr2,kskr3的例子,对于cin>>也可以适用,原因是这里输入的没有空格,如果输入了空格,比如“ks kr jkl[回车]”那么cin就会已经接收到3个字符串,“ks,kr,jkl”;再如“kskr 1[回车]kskr 2[回车]”,那么则接收“kskr,1,kskr”;这不是我们所要的结果!而cin.getline()和gets()因为可以接收空格,所以不会产生这个错误;</p><h3 id="6、getchar"><a href="#6、getchar" class="headerlink" title="6、getchar()"></a><em><strong>6、getchar()</strong></em></h3><p><em><strong>//接受一个字符,需包含“#include”</strong></em></p><pre><code class="c">#include<iostream>#include<string>using namespace std;int main (){ char ch; ch=getchar(); //不能写成getchar(ch); cout<<ch<<endl;}12345678输入:jkljkljkl输出:j12</code></pre><p>//getchar()是C语言的函数,C++也可以兼容,但是尽量不用或少用;</p><h2 id="常用函数"><a href="#常用函数" class="headerlink" title="常用函数"></a>常用函数</h2><p>char a[100]:</p><pre><code>1.定义,输入,长度,输出char str[100] = "abcdef";char s2[100] = {0};scanf("%s", s1);//每次读到空格,会自动停止printf("%s\n", s1);gets(s1);//可以读一整行带空格的字符puts(s1);int len = strlen(str);2.把字符串转换成整数string s = "123";int a = atoi(s.c_str());char s1[10] = "123";int a = atoi(s1);3.判断字母c是不是数字isdigit(c);islower(c);c = toupper(c);4.字符串拼接char s1[10] = "123";char s2[5] = "afs";strcat(s1, s2);5.字符串比较:相同输出0,s1>s2输出1,s1<s2输出-1char s1[10] = "abcdefgh";char s2[10] = "aaa";cout << strcmp(s1, s2);6.复制:把s2的内容复制到s1中char s1[10] = "abcdefgh";char s2[10] = "bbbbbb";strcpy(s1, s2);7.查找子串pos = str.find("abc")8.删除字符串str.erase(pos, 3);</code></pre><p>string a:</p><pre><code>1.输入输出string str1;cin >> str1;cout << str1;getline(cin, str1);puts(str1.c_str());不能用:scanf("%s",&str1);printf("%s",str1);2.str1.strcmp(str2)两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇'\0'为止。若str1 = str2返回0;若str1 > str2 返回正数;若str1<str2 返回负数string str1 = "abc";string str2 = "abc";cout << str1.compare(str2) << endl;//03.int lena = str.size();4.str2.find(str1)返回str2第一次和str1匹配的下标,否则返回-1string str1 = "abc";string str2 = "aaaabc";cout << str2.find(str1) << endl;5.可以进行修改string str1 = "abc";str1[0] = 'b';cout << str1 << endl;//bbc6.转小写transform(str.begin(), str.end(), str.begin(), ::tolower); </code></pre><h2 id="char数组-常用函数"><a href="#char数组-常用函数" class="headerlink" title="char数组 常用函数"></a>char数组 常用函数</h2><h3 id="strlwr-s1"><a href="#strlwr-s1" class="headerlink" title="strlwr(s1)"></a>strlwr(s1)</h3><p>将字符串 s1 中的所有的大写字母转换为小写字母</p><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;int main(int argc, char* argv[]) { char s1[20] = "HeLLo WorlD"; char* s2 = strlwr(s1); cout << s2 << endl; //输出结果为hello world return 0;}</code></pre><p>strupr(s1)和strlwr(s1)相反</p><h3 id="strstr-s1-s2"><a href="#strstr-s1-s2" class="headerlink" title="strstr(s1,s2)"></a>strstr(s1,s2)</h3><p>功能:用来定义判断 s2 是否为 s1 的字串<br>返回值:如果是,则返回 s2 首次在 s1 中首次出现的地址,如果否,则返回NULL</p><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;int main(int argc, char* argv[]) { char s1[20] = "qwertyuiop"; char s2[20] = "yuiop"; char s3[20] = "iods"; char *s4 = strstr(s1, s2) cout << s4 << endl; //返回 s2 在 s1首次出现的地址 输出yuiop cout << strstr(s1, s3) << endl; //返回NULL return 0;}</code></pre><h3 id="strrev-s1"><a href="#strrev-s1" class="headerlink" title="strrev(s1)"></a>strrev(s1)</h3><p>功能:实现字符反转<br>说明:只对字符数组有效,对 string 类型是无效的</p><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;int main(int argc, char* argv[]) { char s1[20] = "hello"; cout << "反转前: " << s1 << endl; //输出hello strrev(s1); cout << "反转后:" << s1 << endl; //输出olleh return 0;}</code></pre><h3 id="strlen"><a href="#strlen" class="headerlink" title="strlen"></a>strlen</h3><pre><code class="cpp">char name[100] = "hello world";cout << strlen(name) << endl;</code></pre><h3 id="strcat"><a href="#strcat" class="headerlink" title="strcat"></a>strcat</h3><p><strong>下面两种方式输出结果得到的值是一样的。</strong></p><pre><code class="cpp">char name[100] = "hello world";char level[100] = "concat test";char *ret = strcat(name, level);cout << ret << endl; // 方式1cout << name << endl; // 方式2</code></pre><h3 id="strcpy"><a href="#strcpy" class="headerlink" title="strcpy"></a>strcpy</h3><pre><code class="cpp">char name[10] = "";char level[100] = "concat test";strcpy(name, level);cout << name << endl;</code></pre><h3 id="memset"><a href="#memset" class="headerlink" title="memset"></a>memset</h3><pre><code class="cpp">char name[15] = "abc";char level[100] = "concat test";memset(name, 'c', 10);cout << name << endl;</code></pre><h3 id="strcmp"><a href="#strcmp" class="headerlink" title="strcmp"></a>strcmp</h3><p>比较字符串,相等返回0,s1大返回正数,s2大返回负数</p><pre><code class="cpp">char s1[15] = "abc";char s2[15] = "abcd";cout << strcmp(s1, s2) << endl;//-100</code></pre><h3 id="stricmp"><a href="#stricmp" class="headerlink" title="stricmp"></a>stricmp</h3><p>功 能: 比较字符串大小,忽略大小写<br>用 法: int stricmp(char *str1, char *str2);</p><pre><code class="cpp">char s1[15] = "abcD";char s2[15] = "abcd";cout << stricmp(s1, s2) << endl;//0</code></pre><h2 id="string-常用函数"><a href="#string-常用函数" class="headerlink" title="string 常用函数"></a>string 常用函数</h2><h3 id="运算符重载"><a href="#运算符重载" class="headerlink" title="运算符重载"></a>运算符重载</h3><ol><li>+ 和 +=:连接字符串</li><li>=:字符串赋值</li><li>>、>=、< 和 <=:字符串比较(例如a < b, aa < ab)</li><li>==、!=:比较字符串</li><li><<、>>:输出、输入字符串</li></ol><p>注意:使用重载的运算符 + 时,必须保证前两个操作数至少有一个为 string 类型。例如,下面的写法是不合法的:</p><pre><code class="cpp">#include <iostream>#include <string>int main(){ string str = "cat"; cout << "apple" + "boy" + str; // illegal! return 0;}</code></pre><h3 id="查找"><a href="#查找" class="headerlink" title="查找"></a>查找</h3><pre><code class="cpp">string str;cin >> str;str.find("ab");//返回字符串 ab 在 str 的位置str.find("ab", 2);//在 str[2]~str[n-1] 范围内查找并返回字符串 ab 在 str 的位置str.rfind("ab", 2);//在 str[0]~str[2] 范围内查找并返回字符串 ab 在 str 的位置//first 系列函数str.find_first_of("apple");//返回 apple 中任何一个字符首次在 str 中出现的位置str.find_first_of("apple", 2);//返回 apple 中任何一个字符首次在 str[2]~str[n-1] 范围中出现的位置str.find_first_not_of("apple");//返回除 apple 以外的任何一个字符在 str 中首次出现的位置str.find_first_not_of("apple", 2);//返回除 apple 以外的任何一个字符在 str[2]~str[n-1] 范围中首次出现的位置//last 系列函数str.find_last_of("apple");//返回 apple 中任何一个字符最后一次在 str 中出现的位置str.find_last_of("apple", 2);//返回 apple 中任何一个字符最后一次在 str[0]~str[2] 范围中出现的位置str.find_last_not_of("apple");//返回除 apple 以外的任何一个字符在 str 中最后一次出现的位置str.find_last_not_of("apple", 2);//返回除 apple 以外的任何一个字符在 str[0]~str[2] 范围中最后一次出现的位置//以上函数如果没有找到,均返回string::nposcout << string::npos;</code></pre><h3 id="子串"><a href="#子串" class="headerlink" title="子串"></a>子串</h3><pre><code class="cpp">str.substr(3); //返回 [3] 及以后的子串str.substr(2, 4); //返回 str[2]~str[2+(4-1)] 子串(即从[2]开始4个字符组成的字符串)</code></pre><h3 id="替换"><a href="#替换" class="headerlink" title="替换"></a>替换</h3><pre><code class="cpp">str.replace(2, 4, "sz");//返回把 [2]~[2+(4-1)] 的内容替换为 "sz" 后的新字符串str.replace(2, 4, "abcd", 3);//返回把 [2]~[2+(4-1)] 的内容替换为 "abcd" 的前3个字符后的新字符串</code></pre><h3 id="插入"><a href="#插入" class="headerlink" title="插入"></a>插入</h3><pre><code class="cpp">str.insert(2, "sz");//从 [2] 位置开始添加字符串 "sz",并返回形成的新字符串str.insert(2, "abcd", 3);//从 [2] 位置开始添加字符串 "abcd" 的前 3 个字符,并返回形成的新字符串str.insert(2, "abcd", 1, 3);//从 [2] 位置开始添加字符串 "abcd" 的前 [1]~[1+(3-1)] 个字符,并返回形成的新字符串</code></pre><h3 id="追加"><a href="#追加" class="headerlink" title="追加"></a>追加</h3><p>除了用重载的 <code>+</code> 操作符,还可以使用函数来完成。</p><pre><code class="cpp">str.push_back('a');//在 str 末尾添加字符'a'str.append("abc");//在 str 末尾添加字符串"abc"</code></pre><h3 id="删除"><a href="#删除" class="headerlink" title="删除"></a>删除</h3><pre><code class="cpp">str.erase(3);//删除 [3] 及以后的字符,并返回新字符串str.erase(3, 5);//删除从 [3] 开始的 5 个字符,并返回新字符串</code></pre><h3 id="交换"><a href="#交换" class="headerlink" title="交换"></a>交换</h3><pre><code class="cpp">str1.swap(str2);//把 str1 与 str2 交换</code></pre><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><pre><code class="cpp">str.size();//返回字符串长度str.length();//返回字符串长度str.empty();//检查 str 是否为空,为空返回 1,否则返回 0str[n];//存取 str 第 n + 1 个字符str.at(n);//存取 str 第 n + 1 个字符(如果溢出会抛出异常)</code></pre><h3 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h3><h4 id="查找给定字符串并把相应子串替换为另一给定字符串"><a href="#查找给定字符串并把相应子串替换为另一给定字符串" class="headerlink" title="查找给定字符串并把相应子串替换为另一给定字符串"></a>查找给定字符串并把相应子串替换为另一给定字符串</h4><p>string 并没有提供这样的函数,所以我们自己来实现。由于给定字符串可能出现多次,所以需要用到 <code>find()</code> 成员函数的第二个参数,每次查找之后,从找到位置往后继续搜索。直接看代码(这个函数返回替换的次数,如果返回值是 0 说明没有替换):</p><pre><code class="cpp">int str_replace(string &str, const string &src, const string &dest){ int counter = 0; string::size_type pos = 0; while ((pos = str.find(src, pos)) != string::npos) { str.replace(pos, src.size(), dest); ++counter; pos += dest.size(); } return counter;}</code></pre><h4 id="从给定字符串中删除一给定字串"><a href="#从给定字符串中删除一给定字串" class="headerlink" title="从给定字符串中删除一给定字串"></a>从给定字符串中删除一给定字串</h4><p>方法和上面相似,内部使用 <code>erase()</code> 完成。代码:</p><pre><code class="cpp">int str_erase(string &str, const string src){ int counter = 0; string::size_type pos = 0; while ((pos = str.find(src, pos)) != string::npos) { str.erase(pos, src.size()); ++counter; } return counter;}</code></pre><h4 id="给定一字符串和一字符集,从字符串剔除字符集中的任意字符"><a href="#给定一字符串和一字符集,从字符串剔除字符集中的任意字符" class="headerlink" title="给定一字符串和一字符集,从字符串剔除字符集中的任意字符"></a>给定一字符串和一字符集,从字符串剔除字符集中的任意字符</h4><pre><code class="cpp">int str_wash(string &str, const string src){ int counter = 0; string::size_type pos = 0; while ((pos = str.find_first_of(src, pos)) != string::npos) { str.erase(pos, 1); ++counter; } return counter;}</code></pre><h2 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h2><h3 id="446-统计单词数"><a href="#446-统计单词数" class="headerlink" title="446. 统计单词数"></a><a href="https://www.acwing.com/problem/content/description/448/">446. 统计单词数</a></h3><p>一般的文本编辑器都有查找单词的功能,该功能可以快速定位特定单词在文章中的位置,有的还能统计出特定单词在文章中出现的次数。</p><p>现在,请你编程实现这一功能,具体要求是:给定一个单词,请你输出它在给定的文章中出现的次数和第一次出现的位置。</p><p>注意:匹配单词时,不区分大小写,但要求完全匹配, 即给定单词必须与文章中的某一独立单词在不区分大小写的情况下完全相同(参见样例 1), 如果给定单词仅是文章中某一单词的一部分则不算匹配(参见样例 2)。</p><h4 id="输入格式"><a href="#输入格式" class="headerlink" title="输入格式"></a>输入格式</h4><p>输入共 2 行。</p><p>第 1 行为一个字符串,其中只含字母,表示给定单词。</p><p>第 2 行为一个字符串,其中只可能包含字母和空格,表示给定的文章。</p><h4 id="输出格式"><a href="#输出格式" class="headerlink" title="输出格式"></a>输出格式</h4><p>输出只有一行,如果在文章中找到给定单词则输出两个整数,两个整数之间用一个空格隔开, 分别是单词在文章中出现的次数和第一次出现的位置。(即在文章中第一次出现时,单词首字母在文章中的位置,位置从 0 开始)</p><p>如果单词在文章中没有出现,则直接输出一个整数 −1。</p><h4 id="数据范围"><a href="#数据范围" class="headerlink" title="数据范围"></a>数据范围</h4><p>1≤单词长度≤10,<br>1≤文章长度≤106</p><h4 id="输入样例-1:"><a href="#输入样例-1:" class="headerlink" title="输入样例 1:"></a>输入样例 1:</h4><pre><code>Toto be or not to be is a question</code></pre><h4 id="输出样例-1:"><a href="#输出样例-1:" class="headerlink" title="输出样例 1:"></a>输出样例 1:</h4><pre><code>2 0</code></pre><h4 id="输入样例-2:"><a href="#输入样例-2:" class="headerlink" title="输入样例 2:"></a>输入样例 2:</h4><pre><code>toDid the Ottoman Empire lose its power at that time</code></pre><h4 id="输出样例-2:"><a href="#输出样例-2:" class="headerlink" title="输出样例 2:"></a>输出样例 2:</h4><pre><code>-1</code></pre><h4 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h4><p>关键在于:防止识别在单词内部。</p><h4 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h4><pre><code class="cpp">#include <iostream>#include <algorithm>using namespace std;int main(){ string a,b; getline(cin,a); // 读入一行 getline(cin,b); transform(a.begin(), a.end(), a.begin(), ::tolower); //转小写 transform(b.begin(), b.end(), b.begin(), ::tolower); a=' ' + a + ' '; // 防止识别在单词内 b=' ' + b + ' '; int pos=b.find(a); //find()函数找到返回第一次下标,否则返回-1 int idx=0,res=0; if(pos != -1){ idx=pos; while(pos != -1){ res ++ ; pos=b.find(a,pos+1); } }else{ cout<<-1<<endl; return 0; } cout<<res<<" "<<idx <<endl; return 0;}</code></pre><h3 id="1461-反序相等"><a href="#1461-反序相等" class="headerlink" title="1461 - 反序相等 "></a><a href="https://www.noobdream.com/DreamJudge/Issue/page/1461/#">1461 - 反序相等 </a></h3><p>设N是一个四位数,它的9倍恰好是其反序数(例如:1234 的反序数是4321),求N的值</p><pre><code class="cpp">#include<cstdio>#include<iostream>#include<stdlib.h>#include<string>#include<algorithm>using namespace std;int main(){ for (int i = 1000; i <= 1111; i++) { string s1 = to_string(i); string s2 = to_string(i * 9); reverse(s2.begin(), s2.end()); if (s1.compare(s2)==0) { cout << i << endl; } } return 0;}</code></pre><h3 id="1014-加密算法"><a href="#1014-加密算法" class="headerlink" title="1014 - 加密算法"></a><a href="https://www.noobdream.com/DreamJudge/Issue/page/1014/">1014 - 加密算法</a></h3><p>编写加密程序,加密规则为:将所有字母转化为该字母后的第三个字母,即A->D、B->E、C->F、……、Y->B、Z->C。小写字母同上,其他字符不做转化。输入任意字符串,输出加密后的结果。</p><p>例如:输入”I love 007”,输出”L oryh 007”</p><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;int main() { char s[105]; gets(s); int len = strlen(s); for (int i = 0; i < len; i++) { if (s[i] >= 'A' && s[i] <= 'Z') { s[i] += 3; if (s[i] > 'Z') s[i] -= 26; } else if (s[i] >= 'a' && s[i] <= 'z') { s[i] += 3; if (s[i] > 'z') s[i] -= 26; } else continue; } puts(s); return 0;}</code></pre><h3 id="1012-字符移动"><a href="#1012-字符移动" class="headerlink" title="1012 - 字符移动"></a><a href="https://www.noobdream.com/DreamJudge/Issue/page/1012/">1012 - 字符移动</a></h3><p>输入一个字符串,将其中的数字字符移动到非数字字符之后,并保持数字字符和非数字字符输入时的顺序。例如:输入字符串“ab4f35gr#a6”,输出为“abfgr#a4356”。</p><h4 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h4><h5 id="法一:sort函数"><a href="#法一:sort函数" class="headerlink" title="法一:sort函数"></a>法一:sort函数</h5><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;bool cmp(char a, char b) { if (! isdigit(a) && isdigit(b))//如果a不是数字,而b是数字,则返回true return 1; else return 0;}int main() { string s; cin >> s; sort(s.begin(), s.end(), cmp); cout << s; return 0;}</code></pre><h5 id="法二:分别存储"><a href="#法二:分别存储" class="headerlink" title="法二:分别存储"></a>法二:分别存储</h5><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;int main() { char s[105] = {0}; //输入的字符串数组 char num[105] = {0}; //数字数组 char other[105] = {0}; //其他符号数组 scanf("%s", s); //字符串输入用%s 注意字符数组名字就相当于首地址 int l_id = 0, n_id = 0, o_id = 0; //定义几个字符数组的下标 for (int i = 0; i < strlen(s); i++) // strlen函数是判断字符串长度 { if (s[i] >= '0' && s[i] <= '9') { num[n_id++] = s[i]; } else other[o_id++] = s[i]; } printf("%s", other); printf("%s\n", num); return 0;}</code></pre><h5 id="法三:2次遍历"><a href="#法三:2次遍历" class="headerlink" title="法三:2次遍历"></a>法三:2次遍历</h5><p>先输出非字母,再输出字母</p><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;int main() { char s[105] = {0}; scanf("%s", s); int len = strlen(s); for (int i = 0; i < len; i++) { if (!isdigit(s[i])) printf("%c", s[i]); } for (int i = 0; i < len; i++) { if (isdigit(s[i])) printf("%c", s[i]); } return 0;}</code></pre><h3 id="首字母大写"><a href="#首字母大写" class="headerlink" title="首字母大写"></a>首字母大写</h3><p>对一个字符串中的所有单词,如果单词的首字母不是大写字母,则把单词的首字母变成大写字母。 在字符串中,单词之间通过空白符分隔,空白符包括:空格(‘ ‘)、制表符(‘\t’)、回车符(‘\r’)、换行符(‘\n’)。</p><p>多组输入</p><p>法一</p><p>一次读入一整行,包括空格</p><p>string, getline(cin, s)</p><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;int main() { string s; while (getline(cin, s)) { for (int i = 0; i < s.size(); i++) { if ((!i || s[i - 1] == ' ' || s[i - 1] == '\t') && s[i] >= 'a' && s[i] <= 'z') s[i] -= 32; } cout << s << endl; } return 0;}</code></pre><p>法二:</p><p>char s[100],while( gets(s)!=NULL ),cout << s <<endl;</p><p>法三:</p><p>逐个单词的读,将第一个字母大写</p><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;int main() { char s[100]; while (scanf("%s", s) != EOF) { if (islower(s[0])) s[0] = toupper(s[0]); printf("%s ", s); } return 0;}</code></pre><h3 id="1027-删除字符串2"><a href="#1027-删除字符串2" class="headerlink" title="1027 - 删除字符串2"></a><a href="https://www.noobdream.com/DreamJudge/Issue/page/1027/#">1027 - 删除字符串2</a></h3><p>给你一个字符串S,要求你将字符串中出现的所有”gzu”(不区分大小写)子串删除,输出删除之后的S。</p><p>就是说出现“Gzu”、“GZU”、“GZu”、”gzU”都可以删除。</p><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;int main() { string str, str1; cin >> str; str1 = str; int pos = 0; transform(str1.begin(), str1.end(), str1.begin(), ::tolower); while ((pos = str1.find("gzu")) != -1) { str.erase(pos, 3); str1.erase(pos, 3); } cout << str << endl; return 0;}</code></pre><h3 id="日志排序"><a href="#日志排序" class="headerlink" title="日志排序***"></a>日志排序***</h3><p>有一个网络日志,记录了网络中计算任务的执行情况,每个计算任务对应一条如下形式的日志记录:</p><pre><code>hs_10000_p 2007-01-17 19:22:53,315 253.035(s)</code></pre><p>其中 <code>hs_10000_p</code> 是计算任务的名称,<code>2007-01-17 19:22:53,315</code> 是计算任务开始执行的时间“年-月-日 时:分:秒,毫秒”,<code>253.035(s)</code> 是计算任务消耗的时间(以秒计)</p><p>请你写一个程序,对日志中记录计算任务进行排序。</p><p>时间消耗少的计算任务排在前面,时间消耗多的计算任务排在后面。</p><p>如果两个计算任务消耗的时间相同,则将开始执行时间早的计算任务排在前面。</p><h4 id="输入格式-1"><a href="#输入格式-1" class="headerlink" title="输入格式"></a>输入格式</h4><p>日志中每个记录是一个字符串,每个字符串占一行。最后一行为空行,表示日志结束。</p><p>计算任务名称的长度不超过 1010,开始执行时间的格式是 <code>YYYY-MM-DD HH:MM:SS,MMM</code>,消耗时间小数点后有三位数字。</p><p>计算任务名称与任务开始时间、消耗时间之间以一个或多个空格隔开。</p><h4 id="输出格式-1"><a href="#输出格式-1" class="headerlink" title="输出格式"></a>输出格式</h4><p>排序好的日志记录。每个记录的字符串各占一行。</p><p>输入的格式与输入保持一致,输入包括几个空格,你的输出中也应该包含同样多的空格。</p><h4 id="数据范围-1"><a href="#数据范围-1" class="headerlink" title="数据范围"></a>数据范围</h4><p>日志中最多可能有 1000010000 条记录。<br>保证不存在开始执行时间和计算任务消耗时间都相同的任务。<br>开始执行时间保证合法,任务消耗时间不超过 1000010000。</p><h4 id="输入样例:"><a href="#输入样例:" class="headerlink" title="输入样例:"></a>输入样例:</h4><pre><code>hs_10000_p 2007-01-17 19:22:53,315 253.035(s)hs_10001_p 2007-01-17 19:22:53,315 253.846(s)hs_10002_m 2007-01-17 19:22:53,315 129.574(s)hs_10002_p 2007-01-17 19:22:53,315 262.531(s)hs_10003_m 2007-01-17 19:22:53,318 126.622(s)hs_10003_p 2007-01-17 19:22:53,318 136.962(s)hs_10005_m 2007-01-17 19:22:53,318 130.487(s)hs_10005_p 2007-01-17 19:22:53,318 253.035(s)hs_10006_m 2007-01-17 19:22:53,318 248.548(s)hs_10006_p 2007-01-17 19:25:23,367 3146.827(s)</code></pre><h4 id="输出样例:"><a href="#输出样例:" class="headerlink" title="输出样例:"></a>输出样例:</h4><pre><code>hs_10003_m 2007-01-17 19:22:53,318 126.622(s)hs_10002_m 2007-01-17 19:22:53,315 129.574(s)hs_10005_m 2007-01-17 19:22:53,318 130.487(s)hs_10003_p 2007-01-17 19:22:53,318 136.962(s)hs_10006_m 2007-01-17 19:22:53,318 248.548(s)hs_10000_p 2007-01-17 19:22:53,315 253.035(s)hs_10005_p 2007-01-17 19:22:53,318 253.035(s)hs_10001_p 2007-01-17 19:22:53,315 253.846(s)hs_10002_p 2007-01-17 19:22:53,315 262.531(s)hs_10006_p 2007-01-17 19:25:23,367 3146.827(s)</code></pre><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#include <sstream>using namespace std;const int N = 10010;int n;string logs[N];int main(){ while (getline(cin, logs[n])) if (logs[n].size()) n ++ ; else break; sort(logs, logs + n, [](string& a, string &b) { stringstream ssina(a), ssinb(b); string sa[4], sb[4]; for (int i = 0; i < 4; i ++ ) { ssina >> sa[i]; ssinb >> sb[i]; } if (sa[3] == sb[3]) return sa[1] + sa[2] < sb[1] + sb[2]; double ta, tb; sscanf(sa[3].c_str(), "%lf(s)", &ta); sscanf(sb[3].c_str(), "%lf(s)", &tb); return ta < tb; }); for (int i = 0; i < n; i ++ ) cout << logs[i] << endl; return 0;}作者:yxc链接:https://www.acwing.com/activity/content/code/content/1706985/来源:AcWing著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。</code></pre><h3 id="循环移位"><a href="#循环移位" class="headerlink" title="循环移位"></a>循环移位</h3><p>判断a的二进制表示s1是否能由b的二进制表示s2循环移位得到</p><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;int main(){ int a, b; while(cin >> a >> b ){ string s1, s2; for(int i = 15; i >= 0; i --){ s1 += to_string(a >> i & 1); s2 += to_string(b >> i & 1); } s2 += s2; if(s2.find(s1) != -1)cout<< "YES" << endl; else cout << "NO" << endl; } return 0;}</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> sjtu机试 </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> 模拟 </tag>
</tags>
</entry>
<entry>
<title>sjtu往年题</title>
<link href="/2022/07/02/%E7%AE%97%E6%B3%95/sjtu%E6%9C%BA%E8%AF%95/%E5%BE%80%E5%B9%B4%E9%A2%98/"/>
<url>/2022/07/02/%E7%AE%97%E6%B3%95/sjtu%E6%9C%BA%E8%AF%95/%E5%BE%80%E5%B9%B4%E9%A2%98/</url>
<content type="html"><![CDATA[<p>[TOC]</p><h3 id="日期差值"><a href="#日期差值" class="headerlink" title="日期差值"></a>日期差值</h3><h4 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h4><p>有两个日期,求两个日期之间的天数,如果两个日期是连续的我们规定他们之间的天数为两天</p><h4 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h4><p>先上日期三板斧</p><p>int保存日期,然后从后往前摩,逐次取出来年月日,然后先加年,再加月,再加日</p><h4 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h4><pre><code class="cpp">#include <algorithm>#include <cstring>#include <iostream>using namespace std;const int N = 10010;const int months[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};int is_leap_year(int year) { if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) return 1; return 0;}int days_of_month(int year, int month) { return months[month] + is_leap_year(year);}int date1, date2;int main() { while (cin >> date1 >> date2) { int y1, d1, m1, d2, m2, y2; d1 = date1 % 100; date1 /= 100; m1 = date1 % 12; date1 /= 100; y1 = date1; d2 = date2 % 100; date2 /= 100; m2 = date2 % 12; date2 /= 100; y2 = date2; int cha = 0; for (int i = y1; i < y2; i++) { cha += 365 + is_leap_year(i); } for (int i = m1; i < m2; i++) { cha += months[i]; } cha += d2 - d1; cout << cha + 1 << endl; } return 0;}</code></pre><h3 id="字母统计"><a href="#字母统计" class="headerlink" title="字母统计"></a>字母统计</h3><h4 id="题目-1"><a href="#题目-1" class="headerlink" title="题目"></a>题目</h4><p>输入一行字符串,计算其中A-Z大写字母出现的次数</p><h4 id="思路-1"><a href="#思路-1" class="headerlink" title="思路"></a>思路</h4><p>char[1000] s;</p><p>int n = strlen(s);</p><p>for(int i = 0; i < n;i ++)…</p><p>定义map:</p><p>map<char,int> m;</p><h4 id="代码-1"><a href="#代码-1" class="headerlink" title="代码"></a>代码</h4><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;int main() { char s[1000]; while (~scanf("%s", s)) { map<char, int> m; int n = strlen(s); for (int i = 0; i < n; i++) { if (s[i] >= 'A' && s[i] <= 'Z') { m[s[i]]++; } } for (int i = 'A'; i <= 'Z'; i++) { printf("%c:%d\n", i, m[i]); } } return 0;}</code></pre><h3 id="后缀子串排序"><a href="#后缀子串排序" class="headerlink" title="后缀子串排序"></a>后缀子串排序</h3><h4 id="题目-2"><a href="#题目-2" class="headerlink" title="题目"></a>题目</h4><p>对于一个字符串,将其后缀子串进行排序,例如grain 其子串有: grain rain ain in n 然后对各子串按字典顺序排序,即: ain,grain,in,n,rain</p><h4 id="思路-2"><a href="#思路-2" class="headerlink" title="思路"></a>思路</h4><p>逐个把后缀压入vector,注意不要压入空字符串!!</p><h4 id="代码-2"><a href="#代码-2" class="headerlink" title="代码"></a>代码</h4><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;bool cmp(string a, string b) { return a < b;}int main() { string s; while (cin >> s) { vector<string> vec; int n = s.size(); for (int i = 1; i <= n; i++) { vec.push_back(s.substr(n - i)); } sort(vec.begin(), vec.end(), cmp); int m = vec.size(); for (int j = 0; j < m; j++) { cout << vec[j] << endl; } } return 0;}</code></pre><h3 id="筛质数:"><a href="#筛质数:" class="headerlink" title="筛质数:"></a>筛质数:</h3><p>从2到n遍历,如果当前数是质数,就把它的所有倍数都置为合数</p><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;const int N = 1e6 + 10;int st[N], primes[N], cnt;void get_primes(int n) { for (int i = 2; i <= n; i++) { if (!st[i]) { //如果i是质数 primes[cnt++] = i; for (int j = 2 * i; j <= n; j += i) st[j] = true; } }}int main() { int n; cin >> n; get_primes(n); cout << cnt << endl; for (int i = 0; i < cnt; i++) { cout << primes[i] << endl; } return 0;}</code></pre><h3 id="快速幂"><a href="#快速幂" class="headerlink" title="快速幂"></a>快速幂</h3><p>把k分解成二进制的形式,从后往前逐位的乘$a^{2^i}$</p><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;const int N = 1e6 + 10;int st[N], primes[N], cnt;// a ^ k % pint qmi(int a, int k, int p) { int res = 1; while (k) { if (k & 1) res = (long long)res * a % p; k >>= 1; a = (long long)a * a % p; } return res;}int main() { int n; cin >> n; while (n--) { int a, k, p; cin >> a >> k >> p; cout << qmi(a, k, p) << endl; } return 0;}</code></pre><h3 id="整除问题"><a href="#整除问题" class="headerlink" title="整除问题"></a>整除问题</h3><h4 id="题目-3"><a href="#题目-3" class="headerlink" title="题目"></a>题目</h4><p>给定n,a求最大的k,使n!可以被a^k整除但不能被a^(k+1)整除。</p><h4 id="思路-3"><a href="#思路-3" class="headerlink" title="思路"></a>思路</h4><p>把n!和a^k都分解成多项式的乘积的形式,然后看每个质因子的指数的倍数关系,最小的倍数就是k</p><h4 id="代码-3"><a href="#代码-3" class="headerlink" title="代码"></a>代码</h4><pre><code class="cpp">#include<bits/stdc++.h>using namespace std;//得到数n的质因子及其个数void getPrime(vector<int>& factors, int n){ for(int i=2; i*i<=n; i++){ while(n % i == 0){ factors[i]++; n /= i; if(n <= 1) return; } } if(n > 1) factors[n]++;}int main(){ int n, a; while(cin >> n >> a){ vector<int> factor_a(1000), factor_n(1000); getPrime(factor_a, a); //计算阶乘的每一个数的质因子及其个数;并进行个数的累加 for(int i=2; i<=n; i++) getPrime(factor_n, i); int k = 1000; //看2~n包含多少个对应的质因子 for(int i=2; i<=a; i++) if(factor_a[i]) k = min(k, factor_n[i]/factor_a[i]); cout << k << endl; }}</code></pre><h3 id="高精度"><a href="#高精度" class="headerlink" title="高精度"></a>高精度</h3><pre><code class="java">import java.util.Scanner;import java.math.BigInteger;public class Main{ public static void main(String[] args){ Scanner sr = new Scanner(System.in); while(sr.hasNext()){ BigInteger a, b; a = sr.nextBigInteger(); b = sr.nextBigInteger(); System.out.println(a.add(b)); System.out.println(a.substract(b)); System.out.println(a.multiply(b)); System.out.println(a.divided(b)); } }}</code></pre><h3 id="运算表达式"><a href="#运算表达式" class="headerlink" title="运算表达式*"></a>运算表达式*</h3><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;const int N = 1e5 + 10;int num[N]; // 数组模拟栈int a, b;char op[N];void calc() { int y = num[a--]; int x = num[a--]; char ope = op[b--]; int res; if (ope == '+') res = x + y; else if (ope == '-') res = x - y; else if (ope == '*') res = x * y; else res = x / y; num[++a] = res;}int main() { unordered_map<char, int> pr = {{'+', 1}, {'-', 1}, {'*', 2}, {'/', 2}}; string str; cin >> str; for (int i = 0; str[i]; i++) { char c = str[i]; if (c == '(') op[++b] = c; // 读入`(` else if (c == ')') { // 读入`)` while (op[b] ^ '(') calc(); b--; } else if (isdigit(c)) { // 读入数字 int j = i, res = 0; while (str[j] && isdigit(str[j])) res = res * 10 + str[j++] - '0'; num[++a] = res; i = j - 1; } else { // 读入运算符 while (b and pr[c] <= pr[op[b]])//把栈中所有优先级大于等于当前运算符的都计算完 calc(); op[++b] = c; } } while (b) calc(); // 读入结束,处理结果。 cout << num[a]; // 输出结果。 return 0;}</code></pre><h3 id="求连通分量的个数"><a href="#求连通分量的个数" class="headerlink" title="求连通分量的个数"></a>求连通分量的个数</h3><h4 id="dfs1"><a href="#dfs1" class="headerlink" title="dfs1"></a>dfs1</h4><pre><code class="c++">#include <bits/stdc++.h>using namespace std;vector<int>g[1010];bool vis[1010];void dfs(int x){ vis[x]=true; for(int y:g[x]){ if(!vis[y]){ dfs(y); } }}int main(){ unordered_set<int>st; int x,y; while(cin>>x>>y){ st.insert(x); st.insert(y); g[x].push_back(y); g[y].push_back(x); } int ans=0; for(int x:st){ if(!vis[x]){ ans++; dfs(x); } } cout<<ans; return 0;}</code></pre><h4 id="dfs2"><a href="#dfs2" class="headerlink" title="dfs2"></a>dfs2</h4><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;const int N = 1e5 + 10;int h[N], e[N], ne[N], idx, st[N];int cnt = 0;void dfs(int x) { st[x] = false; for (int i = h[x]; i != -1; i = ne[i]) { int j = e[i]; if (st[j]) { dfs(j); } }}void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx++;}int main() { memset(h, -1, sizeof h); int a, b; int mx = 0; while (cin >> a >> b) { mx = max(max(a, b), mx); add(a, b); add(b, a); st[a] = true; st[b] = true; } for (int i = 1; i <= mx; i++) { if (st[i]) { dfs(i); cnt++; } } cout << cnt; return 0;}</code></pre><h4 id="并查集"><a href="#并查集" class="headerlink" title="并查集"></a>并查集</h4><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;const int N = 100010;int p[N];int find(int x) { if (p[x] != x) p[x] = find(p[x]); return p[x];}int main() { int a, b; for (int i = 0; i < N; i++) { p[i] = i; } set<int> s; while (cin >> a >> b) { s.insert(a); s.insert(b); if (find(a) != find(b)) { p[a] = find(b); } } int res = 0; for (auto x : s) { if (p[x] == x) res++; } cout << res << endl; return 0;}</code></pre><h3 id="n-后面0的个数"><a href="#n-后面0的个数" class="headerlink" title="n!后面0的个数"></a>n!后面0的个数</h3><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;int main() { int n; while (cin >> n) { int res = 0; while (n) { n /= 5; res += n; } cout << res << endl; } return 0;}</code></pre><h3 id="最长上升子序列"><a href="#最长上升子序列" class="headerlink" title="最长上升子序列"></a>最长上升子序列</h3><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;const int N = 100010;int a[N];int main() { int n; cin >> n; for (int i = 0; i < n; i++) { cin >> a[i]; } int res = 0; int i = 0, j = 1; while (j < n) { while (a[j] > a[j - 1]) j++; res = max(res, j - i); i = j; j++; } cout << res << endl; return 0;}</code></pre><p>最小面积子矩阵</p><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;const int N = 110;int n, m, k;int a[N][N], s[N][N];int main() { cin >> n >> m >> k; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { cin >> s[i][j]; } } for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (i == 0 && j == 0) continue; else if (i == 0) s[i][j] += s[i][j - 1]; else if (j == 0) s[i][j] += s[i - 1][j]; else s[i][j] = s[i][j] - s[i - 1][j - 1] + s[i][j - 1] + s[i - 1][j]; } } int res = 10000; for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { for (int ii = 0; ii <= i; ii++) { for (int jj = 0; jj <= j; jj++) { if (s[i][j] - s[i][jj] - s[ii][j] + s[ii][jj] >= k) { res = min(res, (i - ii) * (j - jj)); } } } } } cout << res << endl; return 0;}</code></pre><h3 id="取中值"><a href="#取中值" class="headerlink" title="取中值"></a>取中值</h3><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;const int N = 1000100;int n, m;int p[N], q[N];int main() { cin >> n >> m; for (int i = 0; i < n; i++) { cin >> p[i]; } for (int i = 0; i < m; i++) { cin >> q[i]; } int a, b, c, d; cin >> a >> b >> c >> d; sort(p + a - 1, p + b); sort(q + c - 1, q + d); vector<int> f; for (int i = a - 1; i < b; i++) { f.push_back(p[i]); } for (int i = c - 1; i < d; i++) { f.push_back(q[i]); } sort(f.begin(), f.end()); cout << f[f.size() / 2] << endl; return 0;}</code></pre><pre><code class="cpp">#include<bits/stdc++.h>using namespace std;int main(){ string s; while(getline(cin,s)){ int index = s.find(' '); int sum = 0; bool flag = true; while(index!=-1 && index<s.size()){ string tmp = s.substr(0,index); int t = 0; for(int i=tmp.size()-1,c=1;i>=0;--i,c*=10){ if(tmp[i]>'9'||tmp[i]<'0'){ flag = false; break; } t+=(tmp[i]-'0')*c; } if(!flag){ break; } sum+=t; s = s.substr(index+1); index = s.find(' '); } if(s.size()!=0){ for (int i = s.size() - 1, c = 1; i >= 0;--i,c*=10){ if(s[i]>'9'||s[i]<'0'){ flag = false; break; } sum += (s[i] - '0') * c; } } if(!flag){ cout<<"ERROR"<<endl; }else{ int a = sum/2; int b = sum-a; cout<<b<<" "<<a<<endl; } } return 0;}</code></pre><h3 id="最长公共子序列"><a href="#最长公共子序列" class="headerlink" title="最长公共子序列"></a>最长公共子序列</h3><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;int main() { string s1, s2; while (cin >> s1 >> s2) { s1 = " " + s1; s2 = " " + s2; int n = s1.size(), m = s2.size(); vector<vector<int>> dp(n, vector<int>(m)); for (int i = 1; i < n; i++) { for (int j = 1; j < m; j++) { if (s1[i] == s2[j]) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]); } } } cout << dp[n - 1][m - 1] << endl; } return 0;}</code></pre><h3 id="Day-of-week"><a href="#Day-of-week" class="headerlink" title="Day of week"></a>Day of week</h3><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;int n;int xq[5000][20][50];int isLeap(int y){ if(y%4==0&&y%100!=0||y%400==0)return 1; else return 0;}int m2d[5][20]={{0,31,28,31,30,31,30,31,31,30,31,30,31},{0,31,29,31,30,31,30,31,31,30,31,30,31}};int y2d[5]={365,366};string dayName[10]={"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};string monthName[20]={"","January","February","March","April","May","June","July","August","September","October","November","December"};int main(){ int d,m,y; string mn; int cnt=0; for(int y=1;y<=2100;++y){ int l=isLeap(y); for(int m=1;m<=12;++m){ for(int d=1;d<=m2d[l][m];++d){ cnt++; xq[y][m][d]=cnt%7; } } } unordered_map<string,int>mn2m; for(int i=1;i<=12;++i){ mn2m[monthName[i]]=i; } while(cin>>d>>mn>>y){ m=mn2m[mn]; cout<<dayName[xq[y][m][d]]<<'\n'; } return 0;}</code></pre><h3 id="最长公共子串"><a href="#最长公共子串" class="headerlink" title="最长公共子串"></a>最长公共子串</h3><p>垃圾用例,为啥用b的子串在a里find就只能过50%</p><p>用a的子串在b里find就能ac</p><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;int main() { string a, b; while (cin >> a >> b) { string res; bool hit = false; int n = a.size(), m = b.size(); for (int i = n; i >= 1; i--) { for (int j = 0; j + i <= n; j++) { bool flag = true; string tmp = a.substr(j, i); for (int k = 0; k < tmp.size(); k++) { if (isdigit(tmp[k])) flag = false; } if (flag && b.find(tmp) != b.npos) { hit = true; res = tmp; break; } } if (hit) break; } cout << res << endl; } return 0;}</code></pre><h3 id="玛雅人的密码"><a href="#玛雅人的密码" class="headerlink" title="玛雅人的密码"></a>玛雅人的密码</h3><pre><code class="cpp">#include <iostream>#include <cstdio>#include <algorithm>#include <map>#include <queue>using namespace std;int n;int bfs(string s){ queue<string> q; map<string,int> dist; q.push(s); dist[s] = 0; while(q.size()){ string str = q.front(); q.pop(); for(int i = 0; i <n;i ++){ if(str.substr(i,4) == "2012"){ return dist[str]; } } for(int i = 1; i < n; i ++){ string r = str; swap(r[i],r[i - 1]); if(!dist.count(r)){ q.push(r); dist[r] = dist[str] + 1; } } } return -1;}int main(){ cin >>n; string s; cin >> s; cout << bfs(s); return 0;}</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> sjtu机试 </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> 模拟 </tag>
</tags>
</entry>
<entry>
<title>模拟题</title>
<link href="/2022/07/02/%E7%AE%97%E6%B3%95/sjtu%E6%9C%BA%E8%AF%95/%E6%A8%A1%E6%8B%9F%E9%A2%98/"/>
<url>/2022/07/02/%E7%AE%97%E6%B3%95/sjtu%E6%9C%BA%E8%AF%95/%E6%A8%A1%E6%8B%9F%E9%A2%98/</url>
<content type="html"><![CDATA[<p>[TOC]</p><h2 id="STL常用函数"><a href="#STL常用函数" class="headerlink" title="STL常用函数"></a>STL常用函数</h2><p><a href="https://blog.csdn.net/fantacy10000/article/details/95974634?spm=1001.2101.3001.6661.1&utm_medium=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~TopBlog-1.topblog&depth_1-utm_source=distribute.pc_relevant_t0.none-task-blog-2~default~CTRLIST~TopBlog-1.topblog&utm_relevant_index=1">【刷题】刷题常用STL函数整理合集_达瓦里氏吨吨吨的博客-CSDN博客_stl常用函数</a></p><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220906120442.png" alt="image-20220906120435032"></p><h3 id="【set】集合"><a href="#【set】集合" class="headerlink" title="【set】集合"></a>【set】集合</h3><pre><code class="cpp">基本操作set<int> a; //声明a.begin(); //返回指向第一个元素的迭代器a.end(); //返回指向超尾的迭代器a.clear(); //清空容器aa.empty(); //判断容器是否为空a.size(); //返回当前容器元素个数a.count(x); //返回容器a中元素x的个数a.insert(x); //插入元素,其中a为set<T>型容器,x为T型变量了解a.insert(first,second); //其中first为指向区间左侧的迭代器,second为指向右侧的迭代器。作用是将first到second区间内元素插入到a(左闭右开)。a.erase(x):删除建值为x的元素a.erase(first,second):删除first到second区间内的元素(左闭右开)a.erase(iterator):删除迭代器指向的元素注:set中的删除操作是不进行任何的错误检查的,比如定位器的是否合法等等,所以用的时候自己一定要注意。lower_bound(x1):返回第一个不小于键参数x1的元素的迭代器upper_bound(x2):返回最后一个大于键参数x2的元素的迭代器由以上俩个函数,可以得到一个目标区间,即包含集合中从'x1'到'x2'的所有元素set_union():对集合取并集set_intersection():对集合取交集,它的接口与set_union()相同。</code></pre><h3 id="【map-amp-amp-unordered-map】映射"><a href="#【map-amp-amp-unordered-map】映射" class="headerlink" title="【map && unordered_map】映射"></a>【map && unordered_map】映射</h3><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220906123013.png" alt="在这里插入图片描述"></p><p>遍历map:</p><pre><code class="cpp">for (auto& [k, v] : m) { cout << k << " " << v << endl;}for(auto iter = m.begin(); iter!=m.end(); iter ++){ cout << iter.first << " " << iter.second << }</code></pre><h3 id="【heap】最大堆和最小堆"><a href="#【heap】最大堆和最小堆" class="headerlink" title="【heap】最大堆和最小堆"></a>【heap】最大堆和最小堆</h3><pre><code class="cpp">void testHeap() { vector<int> data{ 3,1,2,7,5 }; //构造堆,最大堆 make_heap(data.begin(), data.end(), less<int>()); //pop堆顶元素,最大的元素 pop_heap(data.begin(), data.end(), less<int>()); cout << data.back() << endl;//输出7 data.pop_back(); //往堆中添加元素 data.push_back(4); push_heap(data.begin(), data.end(), less<int>());//调整堆 //排序 sort_heap(data.begin(), data.end(), less<int>()); for (int x : data) cout << x << " "; cout << endl;//输出 1,2,3,4,5}</code></pre><h2 id="排版问题"><a href="#排版问题" class="headerlink" title="排版问题"></a>排版问题</h2><h3 id="1473-字符棱形"><a href="#1473-字符棱形" class="headerlink" title="1473 - 字符棱形 "></a><a href="https://www.noobdream.com/DreamJudge/Issue/page/1473/#">1473 - 字符棱形 </a></h3><h4 id="题目:"><a href="#题目:" class="headerlink" title="题目:"></a>题目:</h4><p>输入一个整数n表示棱形的对角半长度,请你用*把这个棱形画出来。</p><h5 id="输入:"><a href="#输入:" class="headerlink" title="输入:"></a>输入:</h5><p>1</p><h5 id="输出:"><a href="#输出:" class="headerlink" title="输出:"></a>输出:</h5><pre><code class="bash">*</code></pre><h5 id="输入:-1"><a href="#输入:-1" class="headerlink" title="输入:"></a>输入:</h5><p>3</p><h5 id="输出:-1"><a href="#输出:-1" class="headerlink" title="输出:"></a>输出:</h5><pre><code class="bash"> * ******** *** *</code></pre><h4 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h4><ul><li>分上下两部分。</li><li>枚举每一行的空格数、*数和行号i的关系,找规律。</li></ul><h4 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h4><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;int n; int main(int argc, char** argv) { cin >> n; for(int i = 0; i < n; i ++){ int j = n - 1 - i; for(int k = 0; k < j;k ++)cout << " "; for(int k = 0; k < 2 * i + 1; k ++)cout << "*"; cout << endl; } for(int i = 0; i < n - 1; i ++){ for(int k = 0; k < i + 1; k ++)cout << " "; for(int k = 0; k < (n - 1 - i) * 2 - 1;k ++)cout << "*"; cout << endl; } return 0; }</code></pre><h3 id="1392-杨辉三角形-西北工业大学"><a href="#1392-杨辉三角形-西北工业大学" class="headerlink" title="1392 - 杨辉三角形 - 西北工业大学"></a><a href="https://www.noobdream.com/DreamJudge/Issue/page/1392/">1392 - 杨辉三角形 - 西北工业大学</a></h3><h4 id="题目:-1"><a href="#题目:-1" class="headerlink" title="题目:"></a>题目:</h4><p>打印杨辉三角:</p><h4 id="代码-1"><a href="#代码-1" class="headerlink" title="代码"></a>代码</h4><pre><code class="cpp">#include <iostream>using namespace std;const int N = 2010;int c[N][N];int n;void init(){ for (int i = 0; i < N; i++) for (int j = 0; j <= i; j++) if (!j) c[i][j] = 1; else c[i][j] = c[i - 1][j] + c[i - 1][j - 1];}int main(){ init(); int n; scanf("%d", &n); for (int i = 1; i < n; i++) { for (int j = 0; j <= i; j++) { printf("%d ", c[i][j]); } printf("\n"); } return 0;}</code></pre><h3 id="1377-旋转矩阵-北航"><a href="#1377-旋转矩阵-北航" class="headerlink" title="1377 - 旋转矩阵 - 北航"></a><a href="https://www.noobdream.com/DreamJudge/Issue/page/1377/#">1377 - 旋转矩阵 - 北航</a></h3><h4 id="题目:-2"><a href="#题目:-2" class="headerlink" title="题目:"></a>题目:</h4><p>任意输入两个9阶以下矩阵,要求判断第二个是否是第一个的旋转矩阵,如果是,输出旋转角度(0、90、180、270),如果不是,输出-1。 要求先输入矩阵阶数,然后输入两个矩阵,每行两个数之间可以用任意个空格分隔。行之间用回车分隔,两个矩阵间用任意的回车分隔。</p><h5 id="输入样例"><a href="#输入样例" class="headerlink" title="输入样例:"></a>输入样例:</h5><pre><code class="bash">31 2 34 5 67 8 97 4 18 5 29 6 3</code></pre><h5 id="输出样例"><a href="#输出样例" class="headerlink" title="输出样例:"></a>输出样例:</h5><pre><code class="bash">90</code></pre><h4 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <algorithm>#include <cstdio>#include <iostream>using namespace std;const int N = 14;int a[N][N], b[N][N], c[N][N];int n;bool ab_is_same() { for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { if (a[i][j] != b[i][j]) return false; } } return true;}void rotate() { for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { c[j][n - 1 - i] = a[i][j]; } } for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { a[i][j] = c[i][j]; } }}int main() { while (scanf("%d", &n) != EOF) { for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) scanf("%d", &a[i][j]); for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) scanf("%d", &b[i][j]); bool flag = false; for (int i = 0; i <= 3; i++) { if (!flag) { if (ab_is_same()) { cout << i * 90 << endl; flag = true; } else { rotate(); } } } if (!flag) cout << "-1" << endl; } return 0;}</code></pre><h3 id="1216-旋转方阵"><a href="#1216-旋转方阵" class="headerlink" title="1216 - 旋转方阵"></a><a href="https://www.noobdream.com/DreamJudge/Issue/page/1216/#">1216 - 旋转方阵</a></h3><h4 id="题目:-3"><a href="#题目:-3" class="headerlink" title="题目:"></a>题目:</h4><p>打印出一个旋转方阵,见样例输出。</p><h5 id="输入样例-1"><a href="#输入样例-1" class="headerlink" title="输入样例:"></a>输入样例:</h5><pre><code class="bash">5</code></pre><h5 id="输出样例-1"><a href="#输出样例-1" class="headerlink" title="输出样例:"></a>输出样例:</h5><pre><code class="bash">1 16 15 14 132 17 24 23 123 18 25 22 114 19 20 21 105 6 7 8 9</code></pre><h4 id="代码:-1"><a href="#代码:-1" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <algorithm>#include <cstdio>#include <iostream>using namespace std;const int N = 22;int a[N][N];bool st[N][N];int n;int main() { scanf("%d", &n); //下右上左 int dx[] = {1, 0, -1, 0}, dy[] = {0, 1, 0, -1}; int tot = n * n; int i = 0, j = 0, cnt = 1; int d = 0; while (tot--) { a[i][j] = cnt++; st[i][j] = true; int ni = i + dx[d], nj = j + dy[d]; if (ni < 0 || nj < 0 || ni >= n || nj >= n || st[ni][nj]) { d = (d + 1) % 4; ni = i + dx[d], nj = j + dy[d]; } i = ni, j = nj; } for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { printf("%-3d", a[i][j]); //-表示左对齐,3表示输出宽度默认是3位, //如果变量n的宽度小于3,则在后面补空格, //如果多于3位,则按n的实际位数输出。 } printf("\n"); } return 0;}</code></pre><h3 id="1221-旋转矩阵"><a href="#1221-旋转矩阵" class="headerlink" title="1221 - 旋转矩阵"></a><a href="https://www.noobdream.com/DreamJudge/Issue/page/1221/">1221 - 旋转矩阵</a></h3><h4 id="题目:-4"><a href="#题目:-4" class="headerlink" title="题目:"></a>题目:</h4><p>旋转方阵是一个有n行m列的矩阵,每个矩阵格子里有一个数字。 同样地,旋转方阵有3种操作: 操作1:将方阵顺时针旋转90度 操作2:将方阵沿纵向对称轴翻折 操作3:将方阵逆时针旋转90度 现在将对方阵进行k次操作,输出最后的方阵状态。</p><h4 id="思路-1"><a href="#思路-1" class="headerlink" title="思路"></a>思路</h4><ul><li><p>n行数;m列数</p></li><li><p>顺时针90°:(i,j)->(j, n-1-i)</p></li><li><p>沿纵向对称轴翻折:(i, j)->(i, m-1-j)</p></li><li><p>逆时针90°:(i,j)->(m-1-j, i)</p></li></ul><h4 id="代码-2"><a href="#代码-2" class="headerlink" title="代码"></a>代码</h4><pre><code class="cpp">#include <algorithm>#include <cstdio>#include <iostream>using namespace std;const int N = 110;int a[N][N], b[N][N];int n, m, k;void opera(int o) { if (o == 1) { //顺时针旋转矩阵 for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { b[j][n - 1 - i] = a[i][j]; } } swap(n, m); } else if (o == 2) { //将矩阵对称 for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { b[i][j] = a[i][m - 1 - j]; } } } else if (o == 3) { //逆时针旋转矩阵 for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { b[m - 1 - j][i] = a[i][j]; } } swap(n, m); } for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) a[i][j] = b[i][j];}int main() { while (scanf("%d%d%d", &n, &m, &k) != EOF) { for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { scanf("%d", &a[i][j]); } } while (k--) { int o; scanf("%d", &o); opera(o); } for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { printf("%d ", a[i][j]); } printf("\n"); } } return 0;}</code></pre><h3 id="1472-2048游戏"><a href="#1472-2048游戏" class="headerlink" title="1472 - 2048游戏 "></a><a href="https://www.noobdream.com/DreamJudge/Issue/page/1472/#">1472 - 2048游戏 </a></h3><h4 id="题目:-5"><a href="#题目:-5" class="headerlink" title="题目:"></a>题目:</h4><p>2048游戏是一个4*4矩阵,用户可以按上下左右4个方向键让所有的方块向同一个方向运动,两个相同数字方块撞一起之后合并成为他们的和,每次操作之后随即生成一个2或者4.<br>合并规则:相邻会碰撞的两个数字合并且一个位置只会触发一次合并,并且优先合并移动方向顶部的位置</p><h5 id="输入样例-2"><a href="#输入样例-2" class="headerlink" title="输入样例:"></a>输入样例:</h5><pre><code class="bash">10 0 0 20 0 0 20 0 4 80 0 4 8</code></pre><h5 id="输出样例-2"><a href="#输出样例-2" class="headerlink" title="输出样例:"></a>输出样例:</h5><pre><code class="bash">0 0 8 40 0 0 160 0 0 00 0 0 0</code></pre><h4 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h4><p>以up函数为例:</p><ul><li>先进行合并操作:逐列进行遍历,若当前列的当前元素下面的第一个非零元素和自己一样,则把当前元素乘2,下面那个元素归零。</li><li>然后把每一列向上移,覆盖了0元素。</li></ul><h4 id="代码:-2"><a href="#代码:-2" class="headerlink" title="代码:"></a>代码:</h4><p>虽然有150+行,但是up,down,left,right四个函数高度对称,都是vscode中的 github Copilot插件自动生成的,太强了。。。</p><pre><code class="cpp">#include <algorithm>#include <cstdio>#include <iostream>using namespace std;const int N = 6;int a[N][N], b[N][N];int op;void up() { for (int j = 1; j <= 4; j++) { for (int i = 1; i <= 3; i++) { for (int t = i + 1; t <= 4; t++) { if (a[t][j] == 0) continue; if (a[i][j] != a[t][j]) break; if (a[i][j] == a[t][j]) { a[i][j] *= 2; a[t][j] = 0; break; } } } } for (int j = 1; j <= 4; j++) { int k = 1; for (int i = 1; i <= 4; i++) { if (a[i][j]) { a[k++][j] = a[i][j]; } } for (int i = k; i <= 4; i++) { a[i][j] = 0; } }}void down() { for (int j = 1; j <= 4; j++) { for (int i = 4; i >= 2; i--) { for (int t = i - 1; t >= 1; t--) { if (a[t][j] == 0) continue; if (a[i][j] != a[t][j]) break; if (a[i][j] == a[t][j]) { a[i][j] *= 2; a[t][j] = 0; break; } } } } for (int j = 1; j <= 4; j++) { int k = 4; for (int i = 4; i >= 1; i--) { if (a[i][j]) { a[k--][j] = a[i][j]; } } for (int i = k; i >= 1; i--) { a[i][j] = 0; } }}void left() { for (int i = 1; i <= 4; i++) { for (int j = 1; j <= 3; j++) { for (int t = j + 1; t <= 4; t++) { if (a[i][t] == 0) continue; if (a[i][j] != a[i][t]) break; if (a[i][j] == a[i][t]) { a[i][j] *= 2; a[i][t] = 0; break; } } } } for (int i = 1; i <= 4; i++) { int k = 1; for (int j = 1; j <= 4; j++) { if (a[i][j]) { a[i][k++] = a[i][j]; } } for (int j = k; j <= 4; j++) { a[i][j] = 0; } }}void right() { for (int i = 1; i <= 4; i++) { for (int j = 4; j >= 2; j--) { for (int t = j - 1; t >= 1; t--) { if (a[i][t] == 0) continue; if (a[i][j] != a[i][t]) break; if (a[i][j] == a[i][t]) { a[i][j] *= 2; a[i][t] = 0; break; } } } } for (int i = 1; i <= 4; i++) { int k = 4; for (int j = 4; j >= 1; j--) { if (a[i][j]) { a[i][k--] = a[i][j]; } } for (int j = k; j >= 1; j--) { a[i][j] = 0; } }}int main() { cin >> op; for (int i = 1; i <= 4; i++) { for (int j = 1; j <= 4; j++) { cin >> a[i][j]; } } switch (op) { case 1: up(); break; case 2: down(); break; case 3: left(); break; case 4: right(); break; } for (int i = 1; i <= 4; i++) { for (int j = 1; j <= 4; j++) { cout << a[i][j] << " "; } cout << endl; } return 0;}</code></pre><h2 id="日期问题"><a href="#日期问题" class="headerlink" title="日期问题"></a>日期问题</h2><p>日期问题三板斧:</p><pre><code class="cpp">const int months[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};int is_leap_year(int year) { if(year % 4 == 0 && year % 100 != 0 || year % 400 == 0) return 1; return 0;}int days_of_month(int year, int month) { if(month == 2) return months[month] + is_leap_year(year); return months[month];}</code></pre><h3 id="1051-日期计算"><a href="#1051-日期计算" class="headerlink" title="1051 - 日期计算"></a><a href="https://www.noobdream.com/DreamJudge/Issue/page/1051/#">1051 - 日期计算</a></h3><h4 id="题目:-6"><a href="#题目:-6" class="headerlink" title="题目:"></a>题目:</h4><p>定义一个结构体变量(包括年、月、日),编程序,要求输入年月日,计算并输出该日在本年中第几天。</p><h5 id="输入样例-3"><a href="#输入样例-3" class="headerlink" title="输入样例:"></a>输入样例:</h5><pre><code class="bash">1985 1 202006 3 12</code></pre><h5 id="输出样例-3"><a href="#输出样例-3" class="headerlink" title="输出样例:"></a>输出样例:</h5><pre><code class="bash">2071</code></pre><h4 id="代码:-3"><a href="#代码:-3" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <algorithm>#include <cstdio>#include <iostream>using namespace std;const int months[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};int is_leap_year(int year) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);}int days_of_months(int year, int month) { if (month == 2 && is_leap_year(year)) { return 29; } return months[month];}int main() { int year, month, day; while (cin >> year >> month >> day) { if(year < 0 || month < 1 || month > 12 || day < 1 || day > days_of_months(year, month)) { cout << "Input error!" << endl; continue; } int days = 0; for (int i = 1; i < month; i++) { days += days_of_months(year, i); } days += day; cout << days << endl; } return 0;}</code></pre><h3 id="1011-日期"><a href="#1011-日期" class="headerlink" title="1011 - 日期 "></a><a href="https://www.noobdream.com/DreamJudge/Issue/page/1011/#">1011 - 日期 </a></h3><p>今天是2012年4月12日星期四,编写程序,输入今天开始到12月31日之间的任意日期,输出那一天是星期几。例如输入“5(回车)20(回车)”(5月20日),输出应为“Sunday”。</p><h4 id="思路:-1"><a href="#思路:-1" class="headerlink" title="思路:"></a>思路:</h4><p>先算出来目标日期和今天差几天,方法:</p><p>加入今天4月12,目标日期12月20.</p><p>那么从4月加到11月,得到的总和天数为到12月12日的天数,在加上12月12日到12月20日有几天。</p><p>得到总的天数之后,摩7,再加当前星期(周日第0天,则周四是4),再摩7,就是12月20日的星期。</p><h4 id="代码:-4"><a href="#代码:-4" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <algorithm>#include <cstdio>#include <iostream>using namespace std;const int months[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};int is_leap_year(int year) { return year % 4 == 0 && (year % 100 != 0 || year % 400 == 0);}int days_of_months(int year, int month) { if (month == 2 && is_leap_year(year)) { return 29; } return months[month];}string weeks[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};int main() { int now_year = 2012, now_month = 4, now_day = 12, now_week = 4; int month, day; scanf("%d %d", &month, &day); int days = 0; for (int i = now_month; i < month; i++) { days += days_of_months(now_year, i); } days += day - now_day; int week = now_week + days % 7; if (week > 6) { week -= 7; } printf("%s\n", weeks[week].c_str()); return 0;}</code></pre><p>输出</p><h2 id="排序问题"><a href="#排序问题" class="headerlink" title="排序问题"></a>排序问题</h2><h3 id="1151-成绩排序"><a href="#1151-成绩排序" class="headerlink" title="1151 - 成绩排序"></a><a href="https://www.noobdream.com/DreamJudge/Issue/page/1151/">1151 - 成绩排序</a></h3><p>查找和排序</p><p>题目:输入任意(用户,成绩)序列,可以获得成绩从高到低或从低到高的排列,相同成绩<br>都按先录入排列在前的规则处理。</p><p>示例:<br>jack 70<br>peter 96<br>Tom 70<br>smith 67</p><p>从高到低 成绩<br>peter 96<br>jack 70<br>Tom 70<br>smith 67</p><p>从低到高</p><p>smith 67</p><p>jack 70<br>Tom 70<br>peter 96</p><p><strong>输入描述:</strong></p><p>输入多行,先输入要排序的人的个数,然后输入排序方法0(降序)或者1(升序)再分别输入他们的名字和成绩,以一个空格隔开</p><p><strong>输出描述:</strong></p><p>按照指定方式输出名字和成绩,名字和成绩之间以一个空格隔开</p><p><strong>要用稳定排序stable_sort()</strong></p><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;const int N = 1010;struct Student { string name; int grade;} s[N];bool cmpDesc(Student a, Student b) { return a.grade > b.grade;}bool cmpAsc(Student a, Student b) { return a.grade < b.grade;}int main() { int n, order; while (cin >> n) { cin >> order; for (int i = 0; i < n; i++) { cin >> s[i].name >> s[i].grade; } if (order == 0) { stable_sort(s, s + n, cmpDesc); } else { stable_sort(s, s + n, cmpAsc); } for (int i = 0; i < n; i++) { cout << s[i].name << " " << s[i].grade << endl; } } return 0;}</code></pre><h3 id="1010-排序"><a href="#1010-排序" class="headerlink" title="1010 - 排序 "></a><a href="https://www.noobdream.com/DreamJudge/Issue/page/1010/#">1010 - 排序 </a></h3><p>输入n个数进行排序,要求先按奇偶后按从小到大的顺序排序</p><p>输入样例:</p><pre><code class="bash">81 2 3 4 5 6 7 8</code></pre><p>输出样例:</p><pre><code class="bash">1 3 5 7 2 4 6 8</code></pre><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;const int N = 1010;int a[N];bool cmp(int a, int b) { if (a % 2 == b % 2) //同奇或同偶,则小的在前 return a < b; return (a % 2 > b % 2); //奇数排在偶数前面}int main() { int n; cin >> n; for (int i = 0; i < n; ++i) { cin >> a[i]; } sort(a, a + n, cmp); for (int i = 0; i < n; ++i) { cout << a[i] << " "; } cout << endl; return 0;}</code></pre><h2 id="模拟题:"><a href="#模拟题:" class="headerlink" title="模拟题:"></a>模拟题:</h2><h3 id="1726-挤奶顺序-AcWing题库"><a href="#1726-挤奶顺序-AcWing题库" class="headerlink" title="1726. 挤奶顺序 - AcWing题库"></a><a href="https://www.acwing.com/problem/content/1728/">1726. 挤奶顺序 - AcWing题库</a></h3><h4 id="题目大意:"><a href="#题目大意:" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>有2种限制条件:几头牛有相对位置,几头牛有固定位置</p><p>现在放1号牛,问最靠前能放到哪?</p><h4 id="思路:-2"><a href="#思路:-2" class="headerlink" title="思路:"></a>思路:</h4><p> 有三种牛:有固定位置,有相对位置,既没有固定位置也没有相对位置</p><p> 对应1号牛的三种情况。</p><h4 id="代码:-5"><a href="#代码:-5" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 110;int n, m, k;int q[N];int p[N];bool st[N];/** * 有三种牛:有固定位置,有相对位置,既没有固定位置也没有相对位置 * 对应1号牛的三种情况。 */ int main(){ cin >> n >> m >> k; bool flag = false; for(int i = 1; i <= m; i ++){ cin >> q[i]; if(q[i] == 1)flag = true; } memset(p, -1, sizeof p); for(int i = 0; i < k; i ++){ int a, b; cin >> a >> b; //第一种情况,1的位置是固定的,那么直接输出 if(a == 1){ cout << b << endl; return 0; } //p[a] = b: 牛a的位置固定到b //st[b] = true : 位置b有牛了 p[a] = b; st[b] = true; } //flag = true:第二种情况,1的位置相对于另几头牛是固定的 if(flag){ //把相对位置固定的牛从前往后放,即牛q[i]放到j的 for(int i = 1, j = 1; i <= m; i ++){ while(st[j])j ++; //如果牛q[i]已经有了固定的位置,接下来的有相对位置的牛都要从这头牛的后面开始放 //即:另j从这头牛的位置开始 if(p[q[i]] != -1)j = p[q[i]]; else{ if(q[i] == 1){ cout << j << endl; return 0; } //把j这个位置设为有牛,把牛q[i]放到j这个位置,j往后移 st[j] = true; p[q[i]] = j; j ++; } } } //第三种情况,1的位置不固定,也没有相对关系 //把有相对位置的牛从后往前放,然后从前往后找空地方,找到的第一个空地方放1号牛 else{ for(int i = m, j = n;i;i --){ while(st[j])j --; //牛q[i]已经有了固定位置,那么更新j从q[i]的位置开始 if(p[q[i]] != -1)j = p[q[i]]; else{ st[j] = true; j --; } } for(int i = 1;i <= n; i ++){ if(!st[i]){ cout << i << endl; return 0; } } } return 0;}</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> sjtu机试 </category>
</categories>
<tags>
<tag> 机试 </tag>
<tag> 模拟 </tag>
</tags>
</entry>
<entry>
<title>TwinVisor论文报告</title>
<link href="/2022/06/28/%E8%AE%BA%E6%96%87%E7%AC%94%E8%AE%B0/TwinVisor%E8%AE%BA%E6%96%87%E6%8A%A5%E5%91%8A/"/>
<url>/2022/06/28/%E8%AE%BA%E6%96%87%E7%AC%94%E8%AE%B0/TwinVisor%E8%AE%BA%E6%96%87%E6%8A%A5%E5%91%8A/</url>
<content type="html"><![CDATA[<h1 id="TwinVisor-Hardware-isolated-Confifidential-VirtualMachines-for-ARM"><a href="#TwinVisor-Hardware-isolated-Confifidential-VirtualMachines-for-ARM" class="headerlink" title="TwinVisor: Hardware-isolated Confifidential VirtualMachines for ARM"></a>TwinVisor: Hardware-isolated Confifidential VirtualMachines for ARM</h1><blockquote><p>Dingji Li, Zeyu Mi, Yubin Xia, Binyu Zang, Haibo Chen, Haibing Guan</p><p><em>SOSP ’21, October 26–29, 2021, Virtual Event, Germany</em></p><p>© 2021 Association for Computing Machinery.</p><p>ACM ISBN 978-1-4503-8709-5/21/10. . . $15.00</p><p><a href="https://doi.org/10.1145/3477132.3483554">https://doi.org/10.1145/3477132.3483554</a></p><p><strong>Keywords</strong>: Cloud Computing, Virtualization, Confifidential Computing, ARM TrustZone</p></blockquote><p>论文报告作者:<strong>张辰琦</strong></p><p>本篇报告在开头提炼和明确了论文中的几个<strong>核心概念</strong>,下面首先介绍了问题的<strong>研究背景</strong>(research background),借此提出了本文研究的<strong>主要问题</strong>(research problem)。随后,介绍了作者<strong>实现目标</strong>(research gap)与遇到的<strong>困难挑战</strong>(research challenges),针对这些挑战,讲解了作者一系列的<strong>解决方案</strong>(methods)。之后介绍了作者的<strong>实现</strong>(implement)和对实现原型的<strong>性能评估</strong>(evaluation),最后,我对这篇文章做出一些<strong>评价</strong>(comment)。</p><h2 id="基本概念"><a href="#基本概念" class="headerlink" title="基本概念"></a>基本概念</h2><p>Confidential computing:机密计算</p><blockquote><p>它利用硬件来提供可验证的隔离执行环境</p></blockquote><p>Confidential VM:机密虚拟机 = secure VM = S-VM</p><blockquote><p>它为对云提供商的云租户提供了一个孤立的执行环境</p></blockquote><p>TEE(Trusted Execution Environment)可信执行环境</p><blockquote><p>它是一种环境,其中执行的<strong>代码</strong>和访问的<strong>数据</strong>在机密性(没有人可以访问数据)和完整性(没有人可以更改代码及其行为)方面被隔离和保护</p></blockquote><p>TrustZone</p><blockquote><p>ARM公司在2003年提出来的一种成熟的硬件扩展,用来在移动平台上提供TEE。</p><p>他能够将服务器划分为两个世界,normal world (常规世界) a secure world(安全世界)</p></blockquote><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220628104851.png" alt="Fig1. TrustZone架构,其将处理器分划为安全世界和常规世界,包含3层:EL0 是应用层,EL1是内核层,EL2(如果有)是虚拟机管理程序层。"></p><p>N-visor</p><blockquote><p>TwinVisor重用常规世界内的已经存在的系统管理程序</p></blockquote><p>S-visor</p><blockquote><p>TwinVisor在安全世界创建的新的小型的系统管理程序</p></blockquote><p>Trusted computing base</p><blockquote><p>计算机系统的可信计算基础(TCB)是对其安全性至关重要的所有硬件、固件和/或软件组件的集合,在这种意义上,发生在TCB内部的bug或漏洞可能会危及整个系统的安全属性。相比之下,TCB之外的计算机系统的某些部分必须不能有不正常的行为,泄露超出根据安全策略授予它们的任何特权。</p><p>精心设计和实现系统的可信计算基础对系统的整体安全性至关重要。现代操作系统努力减少TCB(未在主体中验证)的大小,以便对其代码库进行彻底的检查(通过手动或计算机辅助的软件审计或程序验证)变得可行。</p></blockquote><h2 id="问题的背景"><a href="#问题的背景" class="headerlink" title="问题的背景"></a>问题的背景</h2><p>随着云计算的兴起,用户对数据安全性的要求也越来越高,所以<strong>机密计算</strong>也随之受关注,因为它能利用硬件提供隔离执行环境从而保护敏感的数据。机密计算有三个级别:应用级、容器级、<strong>虚拟机级</strong>,其中<strong>虚拟机级的机密计算</strong>由于与Iaas云的兼容性等众多优点被关注更多。</p><p>目前,各大云供应商都提供了<strong>机密虚拟机服务</strong>,比如谷歌云、IBM云等,ARM的用户也很关注ARM服务器什么时候才会提供机密虚拟机服务。虽然ARM最近宣布了一个ARMv9的扩展CCA能支持机密虚拟机,但是根据他们的路线图,<strong>ARM硬件在一两年内还不能用机密虚拟机</strong>,此外,目前还不清楚如何设计和实现建立在CCA上的系统。</p><p>但是,ARM有一个成熟的的硬件扩展<strong>TrustZone</strong>,它能够把服务器划分为两个独立的世界:<strong>常规世界和安全世界</strong>(a normal world and a secure world),因此TrustZone在移动平台上被广泛用来提供可信执行环境TEE。此外,<strong>TrustZone最近引入了具有安全EL2扩展的硬件虚拟化,能够支持在安全世界内高效运行虚拟机</strong>。</p><h2 id="问题的提出"><a href="#问题的提出" class="headerlink" title="问题的提出"></a>问题的提出</h2><p>我们的一个关键问题是:<strong>是否有可能通过新的软件设计,来改造成熟的硬件特性,从而让ARM服务器能够支持机密虚拟机服务?</strong></p><p>本文中,作者提出的<strong>TwinVisor</strong>,这是第一个<strong>能为ARM服务器提供硬件隔离的机密虚拟机的系统</strong>,它包括<strong>N-visor和S-Visor</strong>。</p><p><strong>N-visor</strong>是TwinVisor重用的常规世界内的已有的管理程序,作用是管理硬件资源并为安全虚拟机和常规虚拟机(S-VM和N-VM)提供服务;<strong>S-visor</strong>是TwinVisor在安全世界中创建的很多小的管理程序,它被专门用来保护安全虚拟机(S-VMs)。</p><h2 id="设计目标"><a href="#设计目标" class="headerlink" title="设计目标"></a>设计目标</h2><p>TwinVisor在不扩大TCB的情况下,能让安全世界对常规世界的软件开放。</p><p><strong>具体的目标有三个:</strong></p><p><strong>G1 安全性</strong>:安全虚拟机不能被不可信软件非法访问,TCB要小</p><p><strong>G2 高效性</strong>:有没有TwinVisor对安全虚拟机的执行效率来说应该差不多</p><p><strong>G3 最小改变</strong>:TwinVisor应该引入最小的改变</p><p><strong>核心架构:</strong></p><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220627085412.png" alt="Fig2. TwinVisor 核心架构"></p><ol><li>N-visor可以管理S-VM和N-VM的硬件资源,S-visor只管理S-VM。</li><li>N-Visor掌管硬件资源,为S-visor提供服务,S-visor负责保护他们。</li><li>当创建S-VM的时候,N-visor给它分配硬件资源,当N-visor想要运行虚拟机的时候,它把控制流交给S-visor。</li></ol><h2 id="挑战"><a href="#挑战" class="headerlink" title="挑战"></a>挑战</h2><p>现存的TurstZone设计方式给TwinVisor的实现带来了3个挑战。</p><p><strong>Challenge1 独立的权限模型:TrustZone的安全世界不比常规世界有更多的权限,这样安全世界就不能透明的拦截常规世界的一些指令,比如trap-and-emulate</strong></p><p><strong>Challenge2 静态资源分配:TrustZone采用了一种静态资源分区的策略。这导致安全虚拟机的资源利用率很低</strong></p><p><strong>Challenge3 不频繁的两个世界的转换:TrustZone的世界转换太慢了,而TwinVisor要频繁转换</strong></p><h2 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h2><p>针对这3个问题,我们分别提出了3种解决方案:</p><p><strong>S olution1:逻辑上剥夺N-Visor的权力的模型</strong></p><blockquote><p><strong>可能的方案:</strong>用PV模型。</p><p><strong>缺点</strong>:会导致大量的世界转换,会引入大量的修改</p></blockquote><p>提出了H-trap,一种类似trap的机制,使S-visor能够检查N-visor的操作</p><p>提出原因:在S-visor运行虚拟机之前,任何管理程序或者虚拟机配置都不能影响S-VM的执行</p><p>内容:</p><ol><li>所有的对配置的检查都在S-visor进入S-VM的时候再开始批量进行</li><li>H-Trap不为两个visor的交流提供任何共享的PV数据结构,而是重用已经存在的硬件接口</li><li>S-visor检查CPU寄存器和内存之间的映射,并在进入S-VM之前阻止非法状态</li><li>S-visor尽量不会让N-visor读写寄存器,当N-visor必须读到寄存器的值的时候,S-visor会选择性的开放几个寄存器。</li></ol><p><strong>Solution2:内存资源的协同管理</strong></p><p>设计了一种分割连续内存分配器(split CMA),常规世界和安全世界都能用它协作来<strong>给常规世界动态分配内存</strong></p><p><strong>重用操作系统中已有的CMA</strong>(Contiguous Memory Allocator)</p><ol><li><p>split CMA尽量保持<strong>安全内存是连续的</strong>,解决TZASC无法支持页面粒度动态地更改物理内存的安全状态的问题</p></li><li><p>split CMA可以再常规世界和常规世界分成两个模块:<strong>常规端和安全端</strong>,他们能够相互协作来转换内存的安全属性。如果S-visor不占用内存,常规端就把他们给了内存分配器,给常规内存分配,当S-visor需要更多内存,常规端就会收集内存,把内存给了安全端。反之同理。解决N-visor不能感知内存安全性的动态变化的问题</p></li><li><p><strong>内存管理</strong>:split CMA设计了<strong>层次结构</strong>来管理内存:</p><ul><li><strong>上层</strong>:把所有的空余内存都组织到一个内存池中,并且增大了内存的分配粒度(防止锁竞争导致性能下降)</li><li><strong>中层</strong>:内存池被分割成可变大小的内存块,每个块包含很多页并且只属于一个S-VM,块内存的地址跟块的大小对齐。</li><li><strong>底层</strong>:内存块被当做内存页的缓存,维持了一个bitmap来记录那个页是闲置的。</li></ul></li><li><p><strong>内存压缩</strong>:解决常规端向安全端借内存,而安全端的内存不连续的问题。做法就是把不连续的内存放到连续的最前面。</p></li></ol><p><strong>Solution3:高效的世界转换</strong></p><p>我们发现原来的世界转换包含着很多冗余操作</p><blockquote><p><strong>从前的做法</strong>:结合使用侧核轮询和共享内存。</p><p><strong>缺点</strong>:浪费CPU资源,很难决定用多少核</p></blockquote><ol><li>使用<strong>共享页面</strong>在两个hypervisor之间传输vCPU的通用寄存器的值</li><li>TwinVisor利用<strong>寄存器继承</strong>来进一步避免冗余操作。</li></ol><h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><p><strong>1.I/O虚拟化</strong></p><p>TwinVisor用 shadow I/O rings 和 shadow DMA buffers 实现<strong>对S-VM透明、重用已有的代码</strong></p><p><strong>问题:</strong></p><ul><li><p>在Linux中,二者都是从S-VM的安全区内分配的,这对N-visor不可见</p></li><li><p>Shadow I/O ring给应用程序带来很大的开销</p></li></ul><p><strong>解决:</strong></p><ul><li><p>S-visor在N-visor的常规内存中复制shadow I/O rings和shadow DMA buffers,在两个世界中同步I/O请求和DMA数据</p></li><li><p>利用由WFx指令和物理IRQs引起的常规虚拟机退出来支持TX shadow I/O ring的更新。</p></li></ul><p><strong>2.评估的原型</strong></p><p><strong>功能评估</strong>:在ARM FVP平台上实现一个全功能的S-EL2.</p><p><strong>性能评估</strong>:用了麒麟990开发板,支持VHE,其工作原理与启用了S-EL2的硬件类似。</p><p><strong>安全属性:</strong></p><ul><li><p><strong>固件和s面板在系统的生命周期内是受信任的</strong></p></li><li><p><strong>S-VM的内核镜像的完整性是由S-visor强制保证的</strong></p></li><li><p><strong>每个S-VM的CUP寄存器状态是被S-visor保护的</strong></p></li><li><p><strong>S-VM之间的内存、S-VM与常规世界的内存是彼此隔离的</strong></p></li><li><p><strong>每个S-VM的I/O数据都受到S-visor保护。</strong></p></li><li><p><strong>所有的数据和每个SVM的控制流都受到S-visor保护。</strong></p></li></ul><h2 id="性能评估"><a href="#性能评估" class="headerlink" title="性能评估"></a>性能评估</h2><ol><li><strong>微型测试</strong></li></ol><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220628114050.png" alt="Fig3. 比较TwinVisor和原型在超级调用和二阶页错误上的错误处理"></p><ul><li><p>与原始版本相比,TwinVisor引入73%的开销</p></li><li><p>快速转换机制加快了世界间的转换速度</p></li><li><p>没有shadow S2PT的TwinVisor会节省很多开销</p></li></ul><ol start="2"><li><strong>实际世界中的应用程序的性能</strong></li></ol><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220628114827.png" alt="Fig4. S-VM和N-VM在TwinVisor上的性能差异"></p><ul><li>在所有的基准测试中,TwinVisor和原始版本的性能差异在5%以内,因为快速世界转换机制只占据应用程序有效执行时间的很小一部分</li><li>TwinVisor中的N-VM和原始版本相比只占据少于1.5%的花销</li></ul><ol start="3"><li>稳定性测试</li></ol><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220628163717.png" alt="Fig5. TwinVisor和原始版本在稳定性上的比较"></p><ul><li>不管给了多少虚拟CPU,TwinVisor的花销少于5%</li><li>在混合工作负载中,所有基准测试的最大开销都小于6%。</li><li>随着S-VM的增长,TwinVisor是可扩展的。</li></ul><ol start="4"><li>split-CMA的开销</li></ol><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220628164118.png" alt="Fig6. 迁移缓存的数量对使用Memaslap基准测试的内存缓存的影响。"></p><ul><li>在最坏的情况下,测试的Memcashed的吞吐量分别下降了6.84%和1.30%,开销减小的原因是多个S-VM平摊。</li></ul><h2 id="评价"><a href="#评价" class="headerlink" title="评价"></a>评价</h2><p>本文主要目的是通过软件的设计,扩展了TrustZone的特性,来达到在ARM平台上实现硬件隔离级别的机密虚拟机的效果,为此作者提出了TwinVisor。TwinVisor的一个核心设计理念是:将管理与保护解耦,他让N-visor负责管理硬件资源、提供服务,S-visor负责保护。</p><p>在设计时,我们可以看到作者始终有着明确的设计原则和目标:首先,为了保持安全性,作者禁止N-visor访问任何CPU寄存器的值;其次,为了保持高效性,作者对冗余操作进行了精简;最后,为了只引入最少的更改、保持简洁性,作者在设计的时候选择尽量利用已有的结构,在其上做小量修改,如hypervisor、CMA、I/O机制等,而不是重新设计新的结构。</p><p>在设计之前,作者对比了现有的TrustZone与TwinVisor的设计思想的差异,明确的指出了要实现TwinVisor所面临的3个问题,即独立的权限模型、静态资源分配、不频繁的世界转换,并对这些问题逐一提出了针对性措施,条理清晰。之后,作者通过实验,测试TwinVisor与原始版本的性能差异,结果显示TwinVisor在安全虚拟机上仅造成了不到5%的性能开销,这样的结果是令人满意的。</p><p>文章最后,作者提出了对未来ARM的硬件设计建议,包括有选择性的透明指令捕获、细粒度的安全内存配置、直接世界交换。</p><h2 id="参考博客"><a href="#参考博客" class="headerlink" title="参考博客"></a>参考博客</h2><p><a href="https://blog.csdn.net/xy010902100449/article/details/115273999">[1]ARM系列之ARM Trustzone 技术浅析(一)—— TEE概述&ARM Trustzone硬件架构基础介绍</a></p><p><a href="https://blog.csdn.net/guyongqiangx/article/details/78020257">[2]一篇了解TrustZone</a></p><p><a href="http://www.voycn.com/article/hypervisorjieshaoyi">[3]Hypervisor介绍(一)</a></p><p><a href="https://zhuanlan.zhihu.com/p/460081164">[4]从Intel新酷睿处理器放弃SGX看机密计算技术的发展</a></p><p><a href="https://zhuanlan.zhihu.com/p/950286">[5]一文看懂arm架构和x86架构有什么区别</a></p><p><a href="**https://zhuanlan.zhihu.com/p/382937263**">[6]关于trap and emulated的解释</a></p>]]></content>
<categories>
<category> 论文 </category>
</categories>
<tags>
<tag> 论文 </tag>
<tag> 论文笔记 </tag>
</tags>
</entry>
<entry>
<title>图论_单源最短路问题</title>
<link href="/2022/04/15/%E7%AE%97%E6%B3%95/acwing/%E5%9B%BE%E8%AE%BA_%E5%8D%95%E6%BA%90%E6%9C%80%E7%9F%AD%E8%B7%AF%E9%97%AE%E9%A2%98/"/>
<url>/2022/04/15/%E7%AE%97%E6%B3%95/acwing/%E5%9B%BE%E8%AE%BA_%E5%8D%95%E6%BA%90%E6%9C%80%E7%9F%AD%E8%B7%AF%E9%97%AE%E9%A2%98/</url>
<content type="html"><![CDATA[<p>[TOC]</p><h3 id="1129-热浪"><a href="#1129-热浪" class="headerlink" title="1129. 热浪"></a><a href="https://www.acwing.com/problem/content/1131/">1129. 热浪</a></h3><h4 id="题目大意:"><a href="#题目大意:" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>给定若干个城镇和若干条双向路,从每条路走要有一定的花费,问从起点走到终点的最少花费是多少</p><h4 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h4><p>spfa用队列 </p><p>spfa:维持一个队列,队列中存放的是所有被更新过的点</p><p>每次从队列中取出最前面的点t,用这个点t更新它之后的所有点,即以t为头节点的邻接表的点</p><p>若能更新,即dist[j] > dist[t] + w[i]; 则更新,并且把被更新的点放入队列中。</p><h4 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;const int N = 2510, M = 6200 * 2 + 10;int n, m, s, t;int h[N], e[M], ne[M], w[M], idx;int dist[N];bool st[N];void add(int a, int b, int c){ e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++;}void spfa(){ memset(dist, 0x3f, sizeof dist); dist[s] = 0; queue<int> q; q.push(s); st[s] = true; while(q.size()) { int t = q.front(); q.pop(); st[t] = false; for(int i = h[t]; i != -1; i = ne[i]) { int j = e[i]; if(dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; if(!st[j]) { q.push(j); st[j] = true; } } } }}int main(){ cin >> n >> m >> s >>t; memset(h, -1, sizeof h); for(int i = 0; i < m; i ++) { int a, b, c; cin >> a >> b >> c; add(a, b, c), add(b, a, c); } spfa(); cout << dist[t] << endl; return 0;}</code></pre><h3 id="1128-信使"><a href="#1128-信使" class="headerlink" title="1128. 信使"></a><a href="https://www.acwing.com/problem/content/1130/">1128. 信使</a></h3><h4 id="题目大意:-1"><a href="#题目大意:-1" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>从1号节点到其他所有节点的最短距离中最长的是多少?</p><h4 id="思路:-1"><a href="#思路:-1" class="headerlink" title="思路:"></a>思路:</h4><p>求最长的最短路</p><p>单源最短路问题,但是可以用多源最短路问题的方法来做</p><p>Floyd算法比较简单</p><p>用Floyd求出1号节点到其他所有点的最短距离</p><h4 id="代码:-1"><a href="#代码:-1" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 110, INF = 0x3f3f3f3f;int n, m;int d[N][N];int main(){ cin >> n >> m; memset(d, 0x3f, sizeof d); for (int i = 1; i <= n; i ++ ) d[i][i] = 0; for (int i = 0; i < m; i ++ ) { int a, b, c; cin >> a >> b >> c; d[a][b] = d[b][a] = min(d[a][b], c); } for (int k = 1; k <= n; k ++ ) for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= n; j ++ ) d[i][j] = min(d[i][j], d[i][k] + d[k][j]); int res = 0; for (int i = 1; i <= n; i ++ ) if (d[1][i] == INF) { res = -1; break; } else res = max(res, d[1][i]); cout << res << endl; return 0;}</code></pre><h3 id="1127-香甜的黄油"><a href="#1127-香甜的黄油" class="headerlink" title="1127. 香甜的黄油"></a><a href="https://www.acwing.com/problem/content/1129/">1127. 香甜的黄油</a></h3><h4 id="题目大意:-2"><a href="#题目大意:-2" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>找到一个点,使得其他所有点到该点的距离之和最短</p><h4 id="思路:-2"><a href="#思路:-2" class="headerlink" title="思路:"></a>思路:</h4><p>spfa能够求出某个点到其他所有点的距离最小值,所以,可以在spfa结束之后求出该点到其他所有点的距离之和</p><p>对每个点用一遍spfa,保留最小值即可</p><h4 id="代码:-2"><a href="#代码:-2" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 810, M = 3000, INF = 0x3f3f3f3f;int n, p, m;int id[N];int h[N], e[M], w[M], ne[M], idx;int dist[N], q[N];bool st[N];void add(int a, int b, int c){ e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;}int spfa(int start){ memset(dist, 0x3f, sizeof dist); dist[start] = 0; int hh = 0, tt = 1; q[0] = start, st[start] = true; while (hh != tt) { int t = q[hh ++ ]; if (hh == N) hh = 0; st[t] = false; for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; if (!st[j]) { q[tt ++ ] = j; if (tt == N) tt = 0; st[j] = true; } } } } int res = 0; for (int i = 0; i < n; i ++ ) { int j = id[i]; if (dist[j] == INF) return INF; res += dist[j]; } return res;}int main(){ cin >> n >> p >> m; for (int i = 0; i < n; i ++ ) cin >> id[i]; memset(h, -1, sizeof h); for (int i = 0; i < m; i ++ ) { int a, b, c; cin >> a >> b >> c; add(a, b, c), add(b, a, c); } int res = INF; for (int i = 1; i <= p; i ++ ) res = min(res, spfa(i)); cout << res << endl; return 0;}</code></pre><h3 id="1126-最小花费"><a href="#1126-最小花费" class="headerlink" title="1126. 最小花费"></a><a href="https://www.acwing.com/problem/content/1128/">1126. 最小花费</a></h3><h4 id="题目大意:-3"><a href="#题目大意:-3" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>n个人之间相互转账,人与人之间转账收的手续费各不相同(手续费是按转账的百分比计算),问A最少需要多少钱使得转账之后B能收到100元</p><h4 id="思路:-3"><a href="#思路:-3" class="headerlink" title="思路:"></a>思路:</h4><p>求连乘的最大值等价于求连加的最大值(连乘取log就是连加)</p><p>可以用朴素dijkstra算法</p><p>dijkstra算法之后,dist[i]保留的是点i到指定起点的最值,可以指定最小或最大</p><h4 id="代码:-3"><a href="#代码:-3" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 2010;int n, m, S, T;double g[N][N];double dist[N];bool st[N];void dijkstra(){ dist[S] = 1; for (int i = 1; i <= n; i ++ ) { int t = -1; for (int j = 1; j <= n; j ++ ) if (!st[j] && (t == -1 || dist[t] < dist[j])) t = j; st[t] = true; for (int j = 1; j <= n; j ++ ) //指定保留最大值 dist[j] = max(dist[j], dist[t] * g[t][j]); }}int main(){ scanf("%d%d", &n, &m); while (m -- ) { int a, b, c; scanf("%d%d%d", &a, &b, &c); double z = (100.0 - c) / 100; g[a][b] = g[b][a] = max(g[a][b], z); } cin >> S >> T; dijkstra(); printf("%.8lf\n", 100 / dist[T]); return 0;}</code></pre><h3 id="920-最优乘车"><a href="#920-最优乘车" class="headerlink" title="920. 最优乘车"></a><a href="https://www.acwing.com/problem/content/description/922/">920. 最优乘车</a></h3><h4 id="题目大意:-4"><a href="#题目大意:-4" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>一条公交线路会经过若干个站点</p><p>现在一个人想从S站坐到T站,问最少进行几次换乘</p><h4 id="算法:"><a href="#算法:" class="headerlink" title="算法:"></a>算法:</h4><p>难在建图:一条路线上任意两个点之间的距离看成1</p><p><strong>因为两点之间的距离都是1, 所以直接用bfs即可找到最短路</strong></p><p>只要一个点的距离被更新了,就把它放在队列里面</p><p>每次从队列中去除一个点,看能不能用这个点更新其他的点,能的话把被更新的点放在队列中,用它来更新其他的点</p><h4 id="思路:-4"><a href="#思路:-4" class="headerlink" title="思路:"></a>思路:</h4><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>#include <sstream>using namespace std;const int N = 510;int m, n;bool g[N][N];int dist[N];int stop[N];int q[N];void bfs(){ int hh = 0, tt = 0; memset(dist, 0x3f, sizeof dist); q[0] = 1; dist[1] = 0; while (hh <= tt) { int t = q[hh ++ ]; for (int i = 1; i <= n; i ++ ) if (g[t][i] && dist[i] > dist[t] + 1) { dist[i] = dist[t] + 1; q[ ++ tt] = i; } }}int main(){ cin >> m >> n; string line; getline(cin, line); while (m -- ) { getline(cin, line); stringstream ssin(line); int cnt = 0, p; while (ssin >> p) stop[cnt ++ ] = p; for (int j = 0; j < cnt; j ++ ) for (int k = j + 1; k < cnt; k ++ ) g[stop[j]][stop[k]] = true; } bfs(); if (dist[n] == 0x3f3f3f3f) puts("NO"); else cout << max(dist[n] - 1, 0) << endl; return 0;}</code></pre><h3 id="1137-选择最佳线路"><a href="#1137-选择最佳线路" class="headerlink" title="1137. 选择最佳线路"></a><a href="https://www.acwing.com/problem/content/description/1139/">1137. 选择最佳线路</a></h3><h4 id="题目大意:-5"><a href="#题目大意:-5" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>张三和李四住在一个城市,给定多个单向公交线,每条线告诉从某个点到某个点所花费的时间,给定李四家附近的站点,已知张三家附近有多个公交站,都可以上车,问从张三家到李四家所花费的最短时间是多少</p><h4 id="思路:-5"><a href="#思路:-5" class="headerlink" title="思路:"></a>思路:</h4><p>多个起点也可以当做单个起点,区别在于spfa开始时就把多个节点放入队列中,并设他们的dist都为0</p><h4 id="代码:-4"><a href="#代码:-4" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 1010, M = 20010, INF = 0x3f3f3f3f;int n, m, T;int h[N], e[M], w[M], ne[M], idx;int dist[N], q[N];bool st[N];void add(int a, int b, int c){ e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;}void spfa(){ int scnt; scanf("%d", &scnt); memset(dist, 0x3f, sizeof dist); int hh = 0, tt = 0; while (scnt -- ) { int u; scanf("%d", &u); dist[u] = 0; q[tt ++ ] = u; st[u] = true; } while (hh != tt) { int t = q[hh ++ ]; if (hh == N) hh = 0; st[t] = false; for (int i = h[t]; ~i; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; if (!st[j]) { q[tt ++ ] = j; if (tt == N) tt = 0; st[j] = true; } } } }}int main(){ while (scanf("%d%d%d", &n, &m, &T) != -1) { memset(h, -1, sizeof h); idx = 0; while (m -- ) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c); } spfa(); if (dist[T] == INF) dist[T] = -1; printf("%d\n", dist[T]); } return 0;}</code></pre><h3 id="1131-拯救大兵瑞恩"><a href="#1131-拯救大兵瑞恩" class="headerlink" title="1131. 拯救大兵瑞恩"></a><a href="https://www.acwing.com/problem/content/description/1133/">1131. 拯救大兵瑞恩</a></h3><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>#include <deque>#include <set>#define x first#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 11, M = 360, P = 1 << 10;int n, m, k, p;int h[N * N], e[M], w[M], ne[M], idx;int g[N][N], key[N * N];int dist[N * N][P];bool st[N * N][P];set<PII> edges;void add(int a, int b, int c){ e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;}void build(){ int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= m; j ++ ) for (int u = 0; u < 4; u ++ ) { int x = i + dx[u], y = j + dy[u]; if (!x || x > n || !y || y > m) continue; int a = g[i][j], b = g[x][y]; if (!edges.count({a, b})) add(a, b, 0); }}int bfs(){ memset(dist, 0x3f, sizeof dist); dist[1][0] = 0; deque<PII> q; q.push_back({1, 0}); while (q.size()) { PII t = q.front(); q.pop_front(); if (st[t.x][t.y]) continue; st[t.x][t.y] = true; if (t.x == n * m) return dist[t.x][t.y]; if (key[t.x]) { int state = t.y | key[t.x]; if (dist[t.x][state] > dist[t.x][t.y]) { dist[t.x][state] = dist[t.x][t.y]; q.push_front({t.x, state}); } } for (int i = h[t.x]; ~i; i = ne[i]) { int j = e[i]; if (w[i] && !(t.y >> w[i] - 1 & 1)) continue; // 有门并且没有钥匙 if (dist[j][t.y] > dist[t.x][t.y] + 1) { dist[j][t.y] = dist[t.x][t.y] + 1; q.push_back({j, t.y}); } } } return -1;}int main(){ cin >> n >> m >> p >> k; for (int i = 1, t = 1; i <= n; i ++ ) for (int j = 1; j <= m; j ++ ) g[i][j] = t ++ ; memset(h, -1, sizeof h); while (k -- ) { int x1, y1, x2, y2, c; cin >> x1 >> y1 >> x2 >> y2 >> c; int a = g[x1][y1], b = g[x2][y2]; edges.insert({a, b}), edges.insert({b, a}); if (c) add(a, b, c), add(b, a, c); } build(); int s; cin >> s; while (s -- ) { int x, y, c; cin >> x >> y >> c; key[g[x][y]] |= 1 << c - 1; } cout << bfs() << endl; return 0;}</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> acwing </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> 图论 </tag>
</tags>
</entry>
<entry>
<title>二分</title>
<link href="/2022/04/10/%E7%AE%97%E6%B3%95/acwing/%E4%BA%8C%E5%88%86/"/>
<url>/2022/04/10/%E7%AE%97%E6%B3%95/acwing/%E4%BA%8C%E5%88%86/</url>
<content type="html"><![CDATA[<p>[TOC]</p><h3 id="1460-我在哪?"><a href="#1460-我在哪?" class="headerlink" title="1460. 我在哪?"></a><a href="https://www.acwing.com/problem/content/1462/">1460. 我在哪?</a></h3><h4 id="题目大意:"><a href="#题目大意:" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>给一个字符串,问最小的k,使得长度大于k的子串两两不同</p><h4 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h4><p>当k >= k_min,子串互不相同,所以存在二段性,可以用二分找到k_min</p><h4 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#include <unordered_set>using namespace std;int n;string str;bool check(int mid){ unordered_set<string> hash; for (int i = 0; i + mid - 1 < str.size(); i ++ ) { auto s = str.substr(i, mid); if (hash.count(s)) return false; hash.insert(s); } return true;}int main(){ cin >> n; cin >> str; int l = 1, r = n; while (l < r) { int mid = l + r >> 1; if (check(mid)) r = mid; else l = mid + 1; } cout << r << endl; return 0;}</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> acwing </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> 二分 </tag>
</tags>
</entry>
<entry>
<title>排序</title>
<link href="/2022/04/10/%E7%AE%97%E6%B3%95/acwing/%E6%8E%92%E5%BA%8F/"/>
<url>/2022/04/10/%E7%AE%97%E6%B3%95/acwing/%E6%8E%92%E5%BA%8F/</url>
<content type="html"><![CDATA[<p>[TOC]</p><h1 id="二路归并"><a href="#二路归并" class="headerlink" title="二路归并"></a>二路归并</h1><h3 id="1934-贝茜放慢脚步"><a href="#1934-贝茜放慢脚步" class="headerlink" title="1934. 贝茜放慢脚步"></a><a href="https://www.acwing.com/problem/content/1936/">1934. 贝茜放慢脚步</a></h3><h4 id="题目大意:"><a href="#题目大意:" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>事件包含两种,一种是到了减速时间,一种是到了减速距离,每发生一个事件速度会减慢,问贝茜滑完一千米所需的总时间</p><h4 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h4><ul><li><p>我们需要知道时间,就要知道速度,就必须知道当前发生了多少事件。所以需要将距离和时间统一单位,可以统一到时间上。</p></li><li><p>我们将减速时间存储到数组 $a$ 中,将减速距离存到数组 $b$ 中。</p></li><li><p>假设目前的时间是 $t$ ,目前走了距离 $s$ ,速度是 $v$ ,下一个减速时间是 $ a_i$ ,下一个减速距离是$b_j$,那么到下一个减速时间还剩 $a_i - t$ ,到下一个减速距离还剩的时间是 $\frac{b_j - s}{v}$,需要用二路归并的算法,将到时间的减速和到距离的减速统一到一个时间轴上。</p></li></ul><h4 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#include <vector>using namespace std;const int N = 10010;int n;vector<int> a, b;int main(){ cin >> n; char str[2]; int x; while (n -- ) { scanf("%s%d", str, &x); if (*str == 'T') a.push_back(x); else b.push_back(x); } //设置终点距离是1000 b.push_back(1000); //对时间和距离排序 sort(a.begin(), a.end()); sort(b.begin(), b.end()); //记录当前的时间、距离、速度,其中v表示速度的倒数,这样每次减速只需将v+1 double t = 0, s = 0, v = 1; int i = 0, j = 0; while(i < a.size() || j < b.size()){ //当距离走完了或是接下来减速时间先于减速距离到来 if (j == b.size() || i < a.size() && a[i] - t < (b[j] - s) * v){ //更新当减速时间到来时的距离,并更新当前时间 s += (a[i] - t) / v; t = a[i]; v ++ ; i ++ ; } else{ //要先更新时间,在更新距离,否则b[j] - s = 0 t += (b[j] - s) * v; s = b[j]; v ++; j ++; } } printf("%.0lf\n",t); return 0;}</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> acwing </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> 排序 </tag>
</tags>
</entry>
<entry>
<title>构造</title>
<link href="/2022/04/10/%E7%AE%97%E6%B3%95/acwing/%E6%9E%84%E9%80%A0/"/>
<url>/2022/04/10/%E7%AE%97%E6%B3%95/acwing/%E6%9E%84%E9%80%A0/</url>
<content type="html"><![CDATA[<p>[TOC]</p><h3 id="1684-大型植被恢复"><a href="#1684-大型植被恢复" class="headerlink" title="1684. 大型植被恢复"></a><a href="https://www.acwing.com/problem/content/1686/">1684. 大型植被恢复</a></h3><h4 id="题目大意:"><a href="#题目大意:" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>有N个点,每个点可以和其他最多3个点连一条边,现在给每个点染色,一共有4种颜色:1234,边相连的2个点颜色不能相同,求字典序最小的染色方法</p><h4 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h4><p>从前往后遍历每个点,给它染上第一个能染的色,然后把它相连的点都标记为不能染这个色。由于有4种色,每个点最多连3条边,所以一定有一个色可以染</p><h4 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 110, M = 310;int n, m;int h[N], e[M], ne[M], idx;bool st[N][5];void add(int a, int b){ e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;}int main(){ cin >> n >> m; memset(h, -1, sizeof h); while (m -- ) { int a, b; cin >> a >> b; add(a, b), add(b, a); } for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= 4; j ++ ) if (!st[i][j]) { cout << j; for (int u = h[i]; ~u; u = ne[u]) st[e[u]][j] = true; break; } return 0;}</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> acwing </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> 构造法 </tag>
</tags>
</entry>
<entry>
<title>枚举</title>
<link href="/2022/04/10/%E7%AE%97%E6%B3%95/acwing/%E6%9E%9A%E4%B8%BE/"/>
<url>/2022/04/10/%E7%AE%97%E6%B3%95/acwing/%E6%9E%9A%E4%B8%BE/</url>
<content type="html"><![CDATA[<p>[TOC]</p><h1 id="枚举"><a href="#枚举" class="headerlink" title="枚举"></a>枚举</h1><h3 id="1443-拍照"><a href="#1443-拍照" class="headerlink" title="1443. 拍照"></a><a href="https://www.acwing.com/problem/content/1445/">1443. 拍照</a></h3><h4 id="题目大意:"><a href="#题目大意:" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>给定b数组,其中$b_i=a_{i−1}+a_i$</p><p>求a数组的可能解中的字典序最小的解</p><p>其中a要满足1-n中取值且不能重复</p><h4 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h4><p>对每个a[1],都能唯一确定整个a数组</p><p>只需要在求a数组的过程中判断每个数是否满足:不重复且范围在1-n</p><p>因为要字典序最小,所以在给a[1]赋值的时候从小到大赋值,求到第一个可以的a数组即是答案</p><h4 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 1010;int n;int a[N], b[N];bool st[N];bool check(int a1){ memset(st, 0, sizeof st); a[1] = a1; st[a1] = true; for (int i = 2; i <= n; i ++ ) { a[i] = b[i - 1] - a[i - 1]; if (a[i] < 1 || a[i] > n) return false; if (st[a[i]]) return false; st[a[i]] = true; } for (int i = 1; i <= n; i ++ ) cout << a[i] << ' '; return true;}int main(){ cin >> n; for (int i = 1; i < n; i ++ ) cin >> b[i]; for (int i = 1; i <= n; i ++ ) if (check(i)) break; return 0;}</code></pre><h3 id="3347-菊花链"><a href="#3347-菊花链" class="headerlink" title="3347. 菊花链"></a><a href="https://www.acwing.com/problem/content/3350/">3347. 菊花链</a></h3><h4 id="题目大意:-1"><a href="#题目大意:-1" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>有多少个区间,其中存在整个区间的平均数</p><h4 id="思路:-1"><a href="#思路:-1" class="headerlink" title="思路:"></a>思路:</h4><p>区间内是否存在某个数字:hash set</p><p>有时候需要用到快慢指针,本题不需要</p><h4 id="代码:-1"><a href="#代码:-1" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#include <unordered_set>using namespace std;const int N = 110;int n;int p[N];int main(){ cin >> n; for (int i = 0; i < n; i ++ ) cin >> p[i]; int res = 0; for (int i = 0; i < n; i ++ ) { unordered_set<int> hash; for (int j = i, s = 0; j < n; j ++ ) { s += p[j]; hash.insert(p[j]); int cnt = j - i + 1; if (s % cnt == 0 && hash.count(s / cnt)) res ++ ; } } cout << res << endl; return 0;}</code></pre><h3 id="1789-牛为什么过马路-II"><a href="#1789-牛为什么过马路-II" class="headerlink" title="1789. 牛为什么过马路 II "></a><a href="https://www.acwing.com/problem/content/1791/">1789. 牛为什么过马路 II </a></h3><h4 id="题目大意:-2"><a href="#题目大意:-2" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>给一个长度为52的字符串,26个字母每个出现2次,表示一个环,把相同的字母连线,问有几个字母发生了交叉?</p><h4 id="思路:-2"><a href="#思路:-2" class="headerlink" title="思路:"></a>思路:</h4><p>发生交叉:有且仅有一个字母在两个相同字母中间</p><h4 id="代码:-2"><a href="#代码:-2" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#include <vector>using namespace std;vector<int> p[26];int main(){ string s; cin >> s; for (int i = 0; i < s.size(); i ++ ) p[s[i] - 'A'].push_back(i); int res = 0; for (int i = 0; i < 26; i ++ ) for (int j = i + 1; j < 26; j ++ ) { int cnt = 0; for (auto y: p[j]) if (p[i][0] < y && y < p[i][1]) cnt ++ ; if (cnt == 1) res ++ ; } cout << res << endl; return 0;}</code></pre><h3 id="1875-贝茜的报复"><a href="#1875-贝茜的报复" class="headerlink" title="1875. 贝茜的报复"></a><a href="https://www.acwing.com/problem/content/1877/">1875. 贝茜的报复</a></h3><h4 id="题目大意:-3"><a href="#题目大意:-3" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>多少种给变量赋值的方法可以使得表达式的计算结果为偶数</p><h4 id="思路:-3"><a href="#思路:-3" class="headerlink" title="思路:"></a>思路:</h4><p>二进制枚举,每位代表一个数</p><h4 id="代码:-3"><a href="#代码:-3" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#include <unordered_map>using namespace std;int main(){ int n; cin >> n; //存储每种字母取奇数和偶数分别有几种情况,例如 cnt[0]['B'] = 3 代表B字母取偶数有3中情况 unordered_map<char,int> cnt[2]; while(n --){ char c; int x; cin >> c >> x; cnt[abs(x) % 2][c] ++; } char str[] = "BESIGOM"; int res = 0; //记录在某种情况下某个字母是取奇数还是偶数,例如 v['B'] = 1代表当前情况B取奇数 unordered_map<char,int> v; //枚举每一种情况,用一个二进制数来表示 i = 1110000 表示: BES取奇数,IGOM取偶数 for(int i = 0; i < 1 << 7; i ++){ //检验i的每一位是0还是1,代表每个字母是取奇数还是偶数 for(int j = 0; j < 7; j ++){ v[str[j]] = i >> j & 1; } if((v['B'] + v['I']) * (v['G'] + v['O'] + v['E'] + v['S']) * v['M'] % 2 == 0) { int sum = 1; for(int j = 0; j < 7; j ++){ sum *= cnt[i >> j & 1][str[j]]; } res += sum; } } cout << res << endl; return 0;}</code></pre><h3 id="1855-愤怒的奶牛"><a href="#1855-愤怒的奶牛" class="headerlink" title="1855. 愤怒的奶牛"></a><a href="https://www.acwing.com/problem/content/1857/">1855. 愤怒的奶牛</a></h3><h4 id="题目大意:-4"><a href="#题目大意:-4" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>每次爆炸,会引爆周围的草,并使下一次的爆炸半径+1,问能够引爆的干草捆最大数量</p><h4 id="思路:-4"><a href="#思路:-4" class="headerlink" title="思路:"></a>思路:</h4><p>枚举第一次引爆的牛,计算引爆干草的数量</p><h4 id="代码:-4"><a href="#代码:-4" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 110, INF = 2e9;int n;int p[N];int main(){ cin >> n; p[0] = -INF, p[n + 1] = INF; for (int i = 1; i <= n; i ++ )cin >> p[i]; sort(p + 1, p + n + 1); int res = 0; for (int i = 1; i <= n; i ++ ){ //l 代表当前向左爆炸的半径,开始是1,每移动一次a则l + 1,r代表当前向又爆炸的范围 int l = 1, r = 1, a = i, b = i; //当可以进行下一次引爆 while (p[a] - p[a - 1] <= l) { //找到下一次可以引爆的最左边的地方 int k = a - 1; //当a能够到k左边一个位置,则将k-- while (p[a] - p[k - 1] <= l) k -- ; a = k; l ++ ; } while (p[b + 1] - p[b] <= r) { int k = b + 1; while (p[k + 1] - p[b] <= r) k ++ ; b = k; r ++ ; } res = max(res, b - a + 1); } cout << res << endl; return 0;}</code></pre><h3 id="1826-农田缩减"><a href="#1826-农田缩减" class="headerlink" title="1826. 农田缩减"></a><a href="https://www.acwing.com/problem/content/1828/">1826. 农田缩减</a></h3><h4 id="题目大意:-5"><a href="#题目大意:-5" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>二维平面,给了一堆点的坐标,问去掉那个点后,剩下的坐标用矩形框起来,这个矩形面积最小</p><h4 id="思路:-5"><a href="#思路:-5" class="headerlink" title="思路:"></a>思路:</h4><p>枚举每个点被删除,看被删除的点是不是最大值或是最小值,是的话就赋值为次小次大,不是的话就赋值为最小最大</p><h4 id="代码:-5"><a href="#代码:-5" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 50010;int n;int x[N], y[N], a[N], b[N];int main(){ scanf("%d", &n); for (int i = 0; i < n; i ++ ) { scanf("%d%d", &x[i], &y[i]); a[i] = x[i], b[i] = y[i]; } sort(a, a + n); sort(b, b + n); int res = 2e9; //枚举每个点被删除 for (int i = 0; i < n; i ++ ) { //分别记录x的最小,最大,y的最小,最大 int x1, x2, y1, y2; //看被删除的点是不是最大值或是最小值 //是的话就赋值为次小次大,不是的话就赋值为最小最大 x1 = x[i] == a[0] ? a[1] : a[0]; x2 = x[i] == a[n - 1] ? a[n - 2] : a[n - 1]; y1 = y[i] == b[0] ? b[1] : b[0]; y2 = y[i] == b[n - 1] ? b[n - 2] : b[n - 1]; res = min(res, (x2 - x1) * (y2 - y1)); } printf("%d\n", res); return 0;}</code></pre><h3 id="1913-公平摄影"><a href="#1913-公平摄影" class="headerlink" title="1913. 公平摄影"></a><a href="https://www.acwing.com/problem/content/1915/">1913. 公平摄影</a></h3><h4 id="题目大意:-6"><a href="#题目大意:-6" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>两种牛,站成一排,照片中要么只出现一种牛,要么两种牛的数量一样,问照片的尺寸(最左边的牛和最右边的牛的坐标之差)最大是多少</p><h4 id="思路:-6"><a href="#思路:-6" class="headerlink" title="思路:"></a>思路:</h4><p>设两种牛分别是1和-1,则区间[i,j]内两种牛的数量一样等价于该区间的区间和为0,即$S_j - S_{i - 1} = 0$即$S_j=S_{i-1}$</p><p>记$S^<code>_i=S_{i-1},则S_j=S^</code>_{i}$,记录每个$S^`_i$第一次出现的位置,当该数值之后再出现的时候,用新的位置减去第一次出现的位置</p><h4 id="代码:-6"><a href="#代码:-6" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#include <unordered_map>#define x first#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 100010;int n;PII q[N];int main(){ scanf("%d", &n); for (int i = 1; i <= n; i ++ ) { int x; char str[2]; scanf("%d%s", &x, str); if (*str == 'G') q[i] = {x, 1}; else q[i] = {x, -1}; } sort(q + 1, q + n + 1); unordered_map<int, int> hash; int res = 0, sum = 0, last; for (int i = 1; i <= n; i ++ ) { //记录每个S`i第一次出现的位置(sum没加q[i].y,则sum为S`i) if (!hash.count(sum)) hash[sum] = q[i].x; sum += q[i].y; //如果当前sum以前出现过,则用当前位置减去第一次出现的位置 if (hash.count(sum)) res = max(res, q[i].x - hash[sum]); //计算最长的全是1或-1的区间的长度,如果当前点和上一个点不一样,则当前点是下一个区间的起点 if (i == 1 || q[i].y != q[i - 1].y) last = q[i].x; res = max(res, q[i].x - last); } printf("%d\n", res); return 0;}</code></pre><h3 id="1978-奶牛过马路"><a href="#1978-奶牛过马路" class="headerlink" title="1978. 奶牛过马路"></a><a href="https://www.acwing.com/problem/content/description/1980/">1978. 奶牛过马路</a></h3><h4 id="题目大意:-7"><a href="#题目大意:-7" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>一群牛过马路,第i头牛从ai点走到对面的bi点,问有几头牛的路线和别的牛的路线都没有交叉</p><h4 id="思路:-7"><a href="#思路:-7" class="headerlink" title="思路:"></a>思路:</h4><p>如果第i头牛和别的牛没有交叉,保证:$max(b_1,b_2…b_{i-1}) < b_i$,$min(b_{i+1}…b_n)>b_i$</p><p>即:b[i]的前缀最大值小于b[i],b[i]的后缀最小值小于b[i]</p><h4 id="代码:-7"><a href="#代码:-7" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#define x first#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 100010, INF = 1e8;int n;PII q[N];int smax[N], smin[N];int main(){ scanf("%d", &n); for (int i = 1; i <= n; i ++ ) scanf("%d%d", &q[i].x, &q[i].y); sort(q + 1, q + n + 1); smax[0] = -INF, smin[n + 1] = INF; for (int i = 1; i <= n; i ++ ) smax[i] = max(smax[i - 1], q[i].y); for (int i = n; i; i -- ) smin[i] = min(smin[i + 1], q[i].y); int res = 0; for (int i = 1; i <= n; i ++ ) if (smax[i - 1] < q[i].y && smin[i + 1] > q[i].y) res ++ ; printf("%d\n", res); return 0;}</code></pre><h3 id="1969-品种邻近"><a href="#1969-品种邻近" class="headerlink" title="1969. 品种邻近"></a><a href="https://www.acwing.com/problem/content/description/1971/">1969. 品种邻近</a></h3><h4 id="题目大意:-8"><a href="#题目大意:-8" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>一排牛,每只牛有一个品种ID。</p><p>如果两头牛的品种ID相同,且距离小于k,则这叫一个拥挤奶牛对</p><p>问拥挤奶牛对中品种ID最大的是多少。</p><h4 id="思路:-8"><a href="#思路:-8" class="headerlink" title="思路:"></a>思路:</h4><p>判断某个ID在之前出现的位置:</p><p>法一:用一个map存储每个id最后出现的位置</p><p>法二:将当前牛的前k个位置放入一个队列中,并用一个数组记录队列中的每个元素出现的次数,若当前牛的品种ID出现在队列中,则这是一个拥挤奶牛对</p><h4 id="代码:-8"><a href="#代码:-8" class="headerlink" title="代码:"></a>代码:</h4><p>法一:</p><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#include <map>using namespace std;const int M = 1000010;int cnt[M];map<int,int> m;int main(){ int n; int k; cin >> n >> k; int res = -1; for(int i = 0; i < n; i ++){ int id; cin >> id; if(m.count(id)){ if(i - m[id] <= k)res = max(res, id); } m[id] = i; } cout << res << endl; return 0;}</code></pre><p>法二:</p><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#include <queue>using namespace std;const int M = 1000010;int cnt[M];queue<int> q;int main(){ int n; int k; cin >> n >> k; int res = -1; for(int i = 0; i < n; i ++){ int id; cin >> id; if(cnt[id])res = max(res, id); q.push(id); cnt[id] ++; if(q.size() > k){ cnt[q.front()] --; q.pop(); } } cout << res << endl; return 0;}</code></pre><h3 id="1960-闪烁"><a href="#1960-闪烁" class="headerlink" title="1960. 闪烁"></a>1960. 闪烁</h3><h4 id="题目大意:-9"><a href="#题目大意:-9" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>农夫约翰对牛棚里昏暗的灯光感到不满,刚刚安装了一个新吊灯。</p><p>新吊灯由 N 个灯泡组成,这 N 个灯泡围成一圈,编号为 0∼N−1。</p><p>奶牛对这个新吊灯非常着迷,并且喜欢玩以下游戏:</p><p>对于第 i 个灯泡,如果在 T−1 时刻,它左侧的灯泡(当 i>0 时,为第 i−1个灯泡;当 i=0 时,为第 N−1 个灯泡)是开着,那么在 T 时刻,就切换这个灯泡的状态。</p><p>这个游戏将持续 B 单位时间。</p><p>给定灯泡的初始状态,请确定在 B 单位时间后,它们的最终状态。</p><h4 id="思路:-9"><a href="#思路:-9" class="headerlink" title="思路:"></a>思路:</h4><p>用一个数state的每一位表示某个灯的亮灭</p><p>记录从当前到每个state的走的步数</p><p>如果走到了之前走到的state,则说明走到了环的入口</p><h4 id="代码:-9"><a href="#代码:-9" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;typedef long long LL;const int N = 1 << 16;int n;LL m;//p数组表示某一种状态从原点走几步走到int p[N];int update(int state){ int res = 0; for(int i = 0; i < n;i ++){ int j = (i - 1 + n) % n; //x是原来的第i位,y是原来的第i - 1位 int x = state >> i & 1, y = state >> j & 1; //如果y是1,则把新的状态(res)的第i位置为与x相反 if(y) { if(x) res |= 0 << i; else res |= 1 << i; } //否则,res的第i位即x else{ res |= x << i; } // res |= (x ^ y) << i; // 等价于 res |= (x ^ y) << i; } return res;}void print(int state){ for (int i = 0; i < n; i ++ ) cout << (state >> i & 1) << endl;}int main(){ cin >> n >> m; //state的二进制表示形式表示每一位的灯是亮还是灭 int state = 0; //逐位设置每个灯是亮还是灭 for(int i = 0; i < n; i ++){ int x; cin >> x; state |= x << i; } memset(p, -1, sizeof(p)); p[state] = 0; for(int i = 1;;i ++){ //update函数返回接下来一步的状态 state = update(state); //走到了最后一步,则输出 if(i == m){ print(state); break; } //新的一步在之前没走过,则记录从源点走到该状态所需的步数i,p[state] = i; else if(p[state] == -1)p[state] = i; //新的一步在之前出现过,说明走到了循环的入口,state即循环入口的状态 else{ //循环的长度,即当前的步数减去从源点走到循环入口的长度 int len = i - p[state]; int r = (m - i) % len; while(r --)state = update(state); print(state); break; } } return 0;}</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> acwing </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> 枚举 </tag>
</tags>
</entry>
<entry>
<title>trick</title>
<link href="/2022/03/31/%E7%AE%97%E6%B3%95/acwing/trick/"/>
<url>/2022/03/31/%E7%AE%97%E6%B3%95/acwing/trick/</url>
<content type="html"><![CDATA[<p>记录一些生疏的、牛批的函数和骗分技巧。</p><span id="more"></span><p>[TOC]</p><h3 id="1-count-if"><a href="#1-count-if" class="headerlink" title="1. count_if"></a>1. count_if</h3><pre><code class="cpp">//找有多少个满足lambda 的元素vector<int> a = {0, 2, 3, 4};cout << count_if(a.begin(), a.end(), [](int a1)->bool{return a1 <= 2;});//输出:2</code></pre><h3 id="2-count"><a href="#2-count" class="headerlink" title="2.count"></a>2.count</h3><pre><code class="cpp">//找有多少个等于val 的元素vector<int> a = {0, 2, 3, 4};cout << count(a.begin(), a.end(), 2);//输出:1</code></pre><h3 id="3-打表法"><a href="#3-打表法" class="headerlink" title="3.打表法"></a>3.打表法</h3><p>输出回文素数</p><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 6e7+10,M=1e8+10;int n;int cnt;int primes[N];bool st[M];void get_primes(int n) { for (int i = 2; i <= n; i ++ ) { if (!st[i]) primes[cnt ++ ] = i; for (int j = 0; primes[j] <= n / i; j ++ ) { st[primes[j] * i] = true; if (i % primes[j] == 0) break; } }}bool is_huiwen(int n){ string s=to_string(n); for(int i=0;i<s.size();i++) if(s[i]!=s[s.size()-i-1]) return 0; return 1;}int main(){ freopen("1.txt","w",stdout); get_primes(M-1); for(int i=0;i<cnt;i++) if(is_huiwen(primes[i])) cout<<primes[i]<<','; return 0;}</code></pre><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;int a[]={***}从文件里复制int main(){ int d,f; cin>>d>>f; for(int i=1;i<=781;i++) if(a[i]>=d&&a[i]<=f) cout<<a[i]<<endl; return 0;}</code></pre><h3 id="nth-element"><a href="#nth-element" class="headerlink" title="nth_element()"></a>nth_element()</h3><p>nth_element(a, a + k, a + n):将a中第k小的数放在a[k], k从0开始,小于a[k]的放在a[k]前面,大于a[k]的放在a[k]后面</p><p>不用比较函数,将第3+1小的元素4放在a[3]上</p><pre><code class="cpp">#include<cstdio>#include<algorithm>using namespace std;int main(){ int n = 9, k = 3; int a[10] = {5, 7, 6, 3, 8, 4, 2, 9, 1}; for(int i = 0; i < n; i ++)printf("%d ",a[i]); printf("\n"); nth_element(a, a + k, a + n); for(int i = 0; i < n; i ++)printf("%d ",a[i]); printf("\n"); return 0;}</code></pre><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220331195531.png" alt="image-20220331195530942"></p><p>用比较函数,将第2+1大的元素7放在a[2]</p><pre><code class="cpp">#include<cstdio>#include<algorithm>using namespace std;int cmp(int a,int b){return a>b;}int main(){ int n = 9, k = 2; int a[10] = {5, 7, 6, 3, 8, 4, 2, 9, 1}; for(int i = 0; i < n; i ++)printf("%d ",a[i]); printf("\n"); nth_element(a, a + k, a + n, cmp); for(int i = 0; i < n; i ++)printf("%d ",a[i]); printf("\n"); return 0;}</code></pre><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220331200056.png" alt="image-20220331200056845"></p><p>最长上升子序列问题</p><p>在vector dp里面找到第一个比nums[i]大的位置,并将nums[i]赋给他</p><pre><code class="cpp">auto itr = lower_bound(dp.begin(), dp.end(), nums[i]);*itr = nums[i];</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> acwing </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> trick </tag>
</tags>
</entry>
<entry>
<title>动态规划01</title>
<link href="/2022/03/31/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%9201/"/>
<url>/2022/03/31/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%9201/</url>
<content type="html"><![CDATA[<p>动态规划是一种通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。</p><p>由于动态规划并不是某种具体的算法,而是一种解决特定问题的方法,因此它会出现在各式各样的数据结构中,与之相关的题目种类也更为繁杂。</p><span id="more"></span><h3 id="895-最长上升子序列"><a href="#895-最长上升子序列" class="headerlink" title="895. 最长上升子序列"></a><a href="https://www.acwing.com/problem/content/897/">895. 最长上升子序列</a></h3><h4 id="题目:"><a href="#题目:" class="headerlink" title="题目:"></a>题目:</h4><p>给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。</p><h4 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h4><p><img src="/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92.assets/image-20220414170004607.png" alt="image-20220414170004607"></p><p>代码:</p><p>$O(n^2)$</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><algorithm></span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="type">const</span> <span class="type">int</span> N = <span class="number">1010</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> n;</span><br><span class="line"><span class="type">int</span> a[N], f[N];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="built_in">scanf</span>(<span class="string">"%d"</span>, &n);</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i <= n; i ++ ) <span class="built_in">scanf</span>(<span class="string">"%d"</span>, &a[i]);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i <= n; i ++ )</span><br><span class="line"> {</span><br><span class="line"> f[i] = <span class="number">1</span>; <span class="comment">// 只有a[i]一个数</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j < i; j ++ )</span><br><span class="line"> <span class="keyword">if</span> (a[j] < a[i])</span><br><span class="line"> f[i] = <span class="built_in">max</span>(f[i], f[j] + <span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> res = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i <= n; i ++ ) res = <span class="built_in">max</span>(res, f[i]);</span><br><span class="line"></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%d\n"</span>, res);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br><span class="line"> </span><br></pre></td></tr></table></figure><p>q[i]:所有长度为i的上升子序列中结尾最小的数</p><p>$O(nlogn)$</p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><algorithm></span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="type">const</span> <span class="type">int</span> N = <span class="number">100010</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> n;</span><br><span class="line"><span class="type">int</span> a[N];</span><br><span class="line"><span class="type">int</span> q[N];<span class="comment">//所有不同长度下上升序列结尾的最小值</span></span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="built_in">scanf</span>(<span class="string">"%d"</span>, &n);</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < n; i ++ ) <span class="built_in">scanf</span>(<span class="string">"%d"</span>, &a[i]);</span><br><span class="line"></span><br><span class="line"> <span class="type">int</span> len = <span class="number">0</span>;<span class="comment">//最大长度</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">0</span>; i < n; i ++ )</span><br><span class="line"> <span class="comment">//从q数组(单调递增)中找出小于a[i]的最大的数,q[r],则a[i]是长度为r + 1 的</span></span><br><span class="line"> <span class="comment">//上升子序列的最小值,即q[r+1]</span></span><br><span class="line"> {</span><br><span class="line"> <span class="type">int</span> l = <span class="number">0</span>, r = len;</span><br><span class="line"> <span class="keyword">while</span> (l < r)</span><br><span class="line"> {</span><br><span class="line"> <span class="type">int</span> mid = l + r + <span class="number">1</span> >> <span class="number">1</span>;</span><br><span class="line"> <span class="keyword">if</span> (q[mid] < a[i]) l = mid;</span><br><span class="line"> <span class="keyword">else</span> r = mid - <span class="number">1</span>;</span><br><span class="line"> }</span><br><span class="line"> len = <span class="built_in">max</span>(len, r + <span class="number">1</span>);</span><br><span class="line"> q[r + <span class="number">1</span>] = a[i];</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%d\n"</span>, len);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="897-最长公共子序列"><a href="#897-最长公共子序列" class="headerlink" title="897. 最长公共子序列 "></a><a href="https://www.acwing.com/problem/content/899/">897. 最长公共子序列 </a></h3><h4 id="题目:-1"><a href="#题目:-1" class="headerlink" title="题目:"></a>题目:</h4><h4 id="思路:-1"><a href="#思路:-1" class="headerlink" title="思路:"></a>思路:</h4><h4 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h4><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><iostream></span></span></span><br><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><algorithm></span></span></span><br><span class="line"></span><br><span class="line"><span class="keyword">using</span> <span class="keyword">namespace</span> std;</span><br><span class="line"></span><br><span class="line"><span class="type">const</span> <span class="type">int</span> N = <span class="number">1010</span>;</span><br><span class="line"></span><br><span class="line"><span class="type">int</span> n, m;</span><br><span class="line"><span class="type">char</span> a[N], b[N];</span><br><span class="line"><span class="type">int</span> f[N][N];</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="type">int</span> <span class="title">main</span><span class="params">()</span></span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="built_in">scanf</span>(<span class="string">"%d%d"</span>, &n, &m);</span><br><span class="line"> <span class="built_in">scanf</span>(<span class="string">"%s%s"</span>, a + <span class="number">1</span>, b + <span class="number">1</span>); <span class="comment">//读字符串不要用 cin</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> i = <span class="number">1</span>; i <= n; i ++ )</span><br><span class="line"> <span class="keyword">for</span> (<span class="type">int</span> j = <span class="number">1</span>; j <= m; j ++ )</span><br><span class="line"> {</span><br><span class="line"> f[i][j] = <span class="built_in">max</span>(f[i - <span class="number">1</span>][j], f[i][j - <span class="number">1</span>]);</span><br><span class="line"> <span class="keyword">if</span> (a[i] == b[j]) f[i][j] = <span class="built_in">max</span>(f[i][j], f[i - <span class="number">1</span>][j - <span class="number">1</span>] + <span class="number">1</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="built_in">printf</span>(<span class="string">"%d\n"</span>, f[n][m]);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> <span class="number">0</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<categories>
<category> 算法 </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> 动态规划 </tag>
</tags>
</entry>
<entry>
<title>动态规划</title>
<link href="/2022/03/31/%E7%AE%97%E6%B3%95/acwing/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/"/>
<url>/2022/03/31/%E7%AE%97%E6%B3%95/acwing/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92/</url>
<content type="html"><![CDATA[<p>动态规划是一种通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。</p><p>由于动态规划并不是某种具体的算法,而是一种解决特定问题的方法,因此它会出现在各式各样的数据结构中,与之相关的题目种类也更为繁杂。</p><span id="more"></span><p>[TOC]</p><h3 id="895-最长上升子序列"><a href="#895-最长上升子序列" class="headerlink" title="895. 最长上升子序列"></a><a href="https://www.acwing.com/problem/content/897/">895. 最长上升子序列</a></h3><h4 id="题目:"><a href="#题目:" class="headerlink" title="题目:"></a>题目:</h4><p>给定一个长度为 N 的数列,求数值严格单调递增的子序列的长度最长是多少。</p><h4 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h4><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220906122900.png" alt="image-20220414170004607"></p><p>代码:</p><p>$O(n^2)$</p><pre><code class="cpp">#include <iostream>#include <algorithm>using namespace std;const int N = 1010;int n;int a[N], f[N];int main(){ scanf("%d", &n); for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]); for (int i = 1; i <= n; i ++ ) { f[i] = 1; // 只有a[i]一个数 for (int j = 1; j < i; j ++ ) if (a[j] < a[i]) f[i] = max(f[i], f[j] + 1); } int res = 0; for (int i = 1; i <= n; i ++ ) res = max(res, f[i]); printf("%d\n", res); return 0;} </code></pre><p>q[i]:所有长度为i的上升子序列中结尾最小的数</p><p>$O(nlogn)$</p><pre><code class="cpp">#include <iostream>#include <algorithm>using namespace std;const int N = 100010;int n;int a[N];int q[N];//所有不同长度下上升序列结尾的最小值int main(){ scanf("%d", &n); for (int i = 0; i < n; i ++ ) scanf("%d", &a[i]); int len = 0;//最大长度 for (int i = 0; i < n; i ++ ) //从q数组(单调递增)中找出小于a[i]的最大的数,q[r],则a[i]是长度为r + 1 的 //上升子序列的最小值,即q[r+1] { int l = 0, r = len; while (l < r) { int mid = l + r + 1 >> 1; if (q[mid] < a[i]) l = mid; else r = mid - 1; } len = max(len, r + 1); q[r + 1] = a[i]; } printf("%d\n", len); return 0;}</code></pre><h3 id="897-最长公共子序列"><a href="#897-最长公共子序列" class="headerlink" title="897. 最长公共子序列 "></a><a href="https://www.acwing.com/problem/content/899/">897. 最长公共子序列 </a></h3><h4 id="题目:-1"><a href="#题目:-1" class="headerlink" title="题目:"></a>题目:</h4><p>给定两个长度分别为 N 和 M 的字符串A 和 B,求既是 A 的子序列又是 B 的子序列的字符串长度最长是多少。</p><h4 id="思路:-1"><a href="#思路:-1" class="headerlink" title="思路:"></a>思路:</h4><h4 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <algorithm>using namespace std;const int N = 1010;int n, m;char a[N], b[N];int f[N][N];int main(){ scanf("%d%d", &n, &m); scanf("%s%s", a + 1, b + 1); //读字符串不要用 cin for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= m; j ++ ) { f[i][j] = max(f[i - 1][j], f[i][j - 1]); if (a[i] == b[j]) f[i][j] = max(f[i][j], f[i - 1][j - 1] + 1); } printf("%d\n", f[n][m]); return 0;}</code></pre><h3 id="1017-怪盗基德的滑翔翼"><a href="#1017-怪盗基德的滑翔翼" class="headerlink" title="1017. 怪盗基德的滑翔翼"></a><a href="https://www.acwing.com/problem/content/1019/">1017. 怪盗基德的滑翔翼</a></h3><h4 id="题目大意:"><a href="#题目大意:" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>一排楼,基德可以从任意楼顶往一边滑翔,最多可以在几个楼顶上停歇?</p><h4 id="思路:-2"><a href="#思路:-2" class="headerlink" title="思路:"></a>思路:</h4><p>从前往后和从后往前找最长上升子序列,取最大的</p><h4 id="代码:-1"><a href="#代码:-1" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 110;int n;int h[N];int f[N];int main(){ int T; scanf("%d", &T); while (T -- ) { scanf("%d", &n); for (int i = 0; i < n; i ++ ) scanf("%d", &h[i]); int res = 0; for (int i = 0; i < n; i ++ ) { f[i] = 1; for (int j = 0; j < i; j ++ ) if (h[i] < h[j]) f[i] = max(f[i], f[j] + 1); res = max(res, f[i]); } memset(f, 0, sizeof f); for (int i = n - 1; i >= 0; i -- ) { f[i] = 1; for (int j = n - 1; j > i; j -- ) if (h[i] < h[j]) f[i] = max(f[i], f[j] + 1); res = max(res, f[i]); } printf("%d\n", res); } return 0;}</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> acwing </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> 动态规划 </tag>
</tags>
</entry>
<entry>
<title>思维题</title>
<link href="/2022/03/31/%E7%AE%97%E6%B3%95/acwing/%E6%80%9D%E7%BB%B4%E9%A2%98/"/>
<url>/2022/03/31/%E7%AE%97%E6%B3%95/acwing/%E6%80%9D%E7%BB%B4%E9%A2%98/</url>
<content type="html"><![CDATA[<p>所用算法较难想出来</p><span id="more"></span><p>[TOC]</p><h3 id="1696-困牛排序"><a href="#1696-困牛排序" class="headerlink" title="1696. 困牛排序"></a><a href="https://www.acwing.com/problem/content/1698/">1696. 困牛排序</a></h3><h4 id="题目大意:"><a href="#题目大意:" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>一个无序数组,每次只能把第一个数字插入到后面某个位置,问最少几次能把数组变得有序</p><h4 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h4><p>要把整个数组变成升序,就要把最后一个降序也变成升序,所以从后往前找到第一个降序</p><h4 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 110;int n;int q[N];int main(){ cin >> n; for (int i = 1; i <= n; i ++ ) cin >> q[i]; for (int i = n - 1; i; i -- ) if (q[i] > q[i + 1]) { cout << i << endl; return 0; } cout << 0 << endl; return 0;}</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> acwing </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> 思维题 </tag>
</tags>
</entry>
<entry>
<title>差分</title>
<link href="/2022/03/31/%E7%AE%97%E6%B3%95/acwing/%E5%B7%AE%E5%88%86/"/>
<url>/2022/03/31/%E7%AE%97%E6%B3%95/acwing/%E5%B7%AE%E5%88%86/</url>
<content type="html"><![CDATA[<p> 差分是一种和前缀和相对的策略,可以当做是求和的逆运算。</p><span id="more"></span><p>[TOC]</p><h2 id="模板"><a href="#模板" class="headerlink" title="模板"></a>模板</h2><h3 id="一维差分"><a href="#一维差分" class="headerlink" title="一维差分"></a><strong>一维差分</strong></h3><p>给区间[l, r]中的每个数加上c:</p><pre><code class="cpp">B[l] += c, B[r + 1] -= c</code></pre><p><strong>Acwing 797.差分</strong></p><p>输入一个长度为 n 的整数序列。</p><p>接下来输入 m 个操作,每个操作包含三个整数 l,r,c表示将序列中 [l,r] 之间的每个数加上 c。</p><p>请你输出进行完所有操作后的序列。</p><p><strong>输入格式</strong></p><p>第一行包含两个整数 nn 和 mm。</p><p>第二行包含 nn 个整数,表示整数序列。</p><p>接下来 mm 行,每行包含三个整数 l,r,cl,r,c,表示一个操作。</p><p><strong>输出格式</strong></p><p>共一行,包含 nn 个整数,表示最终序列。</p><p><strong>数据范围</strong></p><p>1≤n,m≤100000<br>1≤l≤r≤n<br>−1000≤c≤1000<br>−1000≤整数序列中元素的值≤1000</p><p><strong>输入样例:</strong></p><pre><code>6 31 2 2 1 2 11 3 13 5 11 6 1</code></pre><p><strong>输出样例:</strong></p><pre><code>3 4 5 3 4 2</code></pre><pre><code class="cpp">#include <iostream>using namespace std;const int N = 100010;int n, m;int a[N], b[N];void insert(int l, int r, int c){ b[l] += c; b[r + 1] -= c;}int main(){ scanf("%d%d", &n, &m); for (int i = 1; i <= n; i ++ ) scanf("%d", &a[i]); for (int i = 1; i <= n; i ++ ) insert(i, i, a[i]); while (m -- ) { int l, r, c; scanf("%d%d%d", &l, &r, &c); insert(l, r, c); } for (int i = 1; i <= n; i ++ ) b[i] += b[i - 1]; for (int i = 1; i <= n; i ++ ) printf("%d ", b[i]); return 0;}</code></pre><h3 id="二维差分"><a href="#二维差分" class="headerlink" title="二维差分"></a><strong>二维差分</strong></h3><p>给以(x1, y1)为左上角,(x2, y2)为右下角的子矩阵中的所有元素加上c:</p><pre><code class="cpp">S[x1, y1] += c;S[x2 + 1, y1] -= c;S[x1, y2 + 1] -= c;S[x2 + 1, y2 + 1] += c;</code></pre><p>差分矩阵</p><p>输入一个 n 行 m 列的整数矩阵,再输入 q个操作,每个操作包含五个整数 x1,y1,x2,y2,c,其中 (x1,y1)和 (x2,y2)表示一个子矩阵的左上角坐标和右下角坐标。</p><p>每个操作都要将选中的子矩阵中的每个元素的值加上 c。</p><p>请你将进行完所有操作后的矩阵输出。</p><p><strong>输入格式</strong></p><p>第一行包含整数 n,m,q</p><p>接下来 n 行,每行包含 m 个整数,表示整数矩阵。</p><p>接下来 q 行,每行包含 5 个整数 x1,y1,x2,y2,c表示一个操作。</p><p><strong>输出格式</strong></p><p>共 n 行,每行 m 个整数,表示所有操作进行完毕后的最终矩阵。</p><p><strong>数据范围</strong></p><p>1≤n,m≤1000<br>1≤q≤100000<br>1≤x1≤x2≤n<br>1≤y1≤y2≤m<br>−1000≤c≤1000<br>−1000≤矩阵内元素的值≤1000</p><p><strong>输入样例</strong>:</p><pre><code>3 4 31 2 2 13 2 2 11 1 1 11 1 2 2 11 3 2 3 23 1 3 4 1</code></pre><p><strong>输出样例</strong>:</p><pre><code>2 3 4 14 3 4 12 2 2 2</code></pre><pre><code class="cpp">#include <iostream>using namespace std;const int N = 1010;int n, m, q;int a[N][N], b[N][N];void insert(int x1, int y1, int x2, int y2, int c){ b[x1][y1] += c;//a[i][j]所有右下角的 +c b[x2 + 1][y1] -= c; b[x1][y2 + 1] -= c; b[x2 + 1][y2 + 1] += c;}int main(){ scanf("%d%d%d", &n, &m, &q); for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= m; j ++ ) scanf("%d", &a[i][j]); for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= m; j ++ ) insert(i, j, i, j, a[i][j]); while (q -- ) { int x1, y1, x2, y2, c; cin >> x1 >> y1 >> x2 >> y2 >> c; insert(x1, y1, x2, y2, c); } for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= m; j ++ ) b[i][j] += b[i - 1][j] + b[i][j - 1] - b[i - 1][j - 1]; for (int i = 1; i <= n; i ++ ) { for (int j = 1; j <= m; j ++ ) printf("%d ", b[i][j]); puts(""); } return 0;}</code></pre><h2 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h2><h3 id="100-增减序列"><a href="#100-增减序列" class="headerlink" title="100 增减序列"></a>100 增减序列</h3><h4 id="题目大意"><a href="#题目大意" class="headerlink" title="题目大意"></a>题目大意</h4><p>给定一个长度为 n 的数列 a1,a2,…,an,每次可以选择一个区间 [l,r],使下标在这个区间内的数都加一或者都减一。</p><p>求至少需要多少次操作才能使数列中的所有数都一样,并求出在保证最少次数的前提下,最终得到的数列可能有多少种。</p><h4 id="分析"><a href="#分析" class="headerlink" title="分析"></a>分析</h4><ul><li><p>构建差分数组<code>b[i] = a[i] - a[i - 1]</code></p></li><li><p>最终目的是使<code>a</code>数组全部一样,即<code>b[2] 至 b[n] = 0</code>,<code>b[1] = a[1]</code>为任意值,这个值就是<code>a</code>数组最后全部相等的值,该值的取值个数就是最终数列可能有多少种。即问:操作几次,能使 b[2] ~ b[n] 全为0,b[1]最终有几种取值。</p></li><li><p>对a数组的区间[l ,r] 操作一次,比如+1, 相当于 b[l] += 1, b[r + 1] -= 1; 即改变两个b的值。PS:可以对b[n + 1]操作,此时对a数组无影响。</p></li><li><p>设b[2] ~ b[n]的正数之和为p,负数的绝对值之和为q,则要把b[2] ~ b[n] 都变为0,至少要进行操作 $max(p, q)$次,其中最多可以对b[1]进行$|p - q|$次操作,最少进行0次,即b[1]的可能取值最多为 $1 + |p - q|$种。</p></li></ul><h4 id="源代码"><a href="#源代码" class="headerlink" title="源代码"></a>源代码</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;typedef long long LL;const int N = 100010;LL n, c, a[N];LL p, q;int main(){ cin >> n; for (int i = 1; i <= n; i ++ ){ cin >> a[i]; } for(int i = 2; i <= n; i ++){ int c = a[i] - a[i - 1]; if(c > 0)p += c; else q -= c; } cout << max(p, q) << endl << abs(p - q) + 1; return 0;}</code></pre><h3 id="103-最高的牛"><a href="#103-最高的牛" class="headerlink" title="103. 最高的牛"></a>103. 最高的牛</h3><h4 id="题目大意-1"><a href="#题目大意-1" class="headerlink" title="题目大意"></a>题目大意</h4><p>有 N 头牛站成一行,被编队为 1、2、3…N,每头牛的身高都为整数。</p><p>当且仅当两头牛中间的牛身高都比它们矮时,两头牛方可看到对方。</p><p>现在,我们只知道其中最高的牛是第 P 头,它的身高是 H ,剩余牛的身高未知。</p><p>但是,我们还知道这群牛之中存在着 M 对关系,每对关系都指明了某两头牛 A 和 B 可以相互看见。</p><p>求每头牛的身高的最大可能值是多少。</p><h4 id="分析-1"><a href="#分析-1" class="headerlink" title="分析"></a>分析</h4><ul><li><p>读入的数据之间不可能有交叉,只可能是嵌套的关系。比如a,b ; c d ;那么只可能是[a,b]包含[c,d],或是[c,d]包含[a,b],两个区间不能交叉</p></li><li><p>当读入区间[a,b]之后,把[a + 1, b - 1]的牛的身高全都-1,即height[a + 1] – , height[b] ++;</p></li></ul><h4 id="源代码-1"><a href="#源代码-1" class="headerlink" title="源代码"></a>源代码</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#include <set>using namespace std;const int N = 10010;int height[N];int main(){ int n, p, h, m; cin >> n >> p >> h >> m; height[1] = h; set<pair<int,int>> existed; for(int i = 0, a, b; i < m; i ++){ cin >> a >> b; if(a > b)swap(a, b); if(!existed.count({a, b})){ existed.insert({a, b}); //把a,b中间,即[a + 1,b - 1]里的牛的身高减一 height[a + 1] --, height[b] ++; } } for (int i = 1; i <= n; i ++ ){ height[i] += height[i - 1]; cout << height[i] << endl; } return 0;}</code></pre><h3 id="1715-桶列表"><a href="#1715-桶列表" class="headerlink" title="1715. 桶列表"></a><a href="https://www.acwing.com/problem/content/1717/">1715. 桶列表</a></h3><h4 id="题目大意:"><a href="#题目大意:" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>每头牛在某个区间内需要一定数量的桶</p><p>问最少需要多少桶能满足所有牛的需要</p><h4 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h4><p>差分、求和、求最小值</p><h4 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 1010;int n;int b[N];int main(){ cin >> n; while (n -- ) { int l, r, c; cin >> l >> r >> c; b[l] += c, b[r] -= c; } int res = 0, sum = 0; for (int i = 1; i <= 1000; i ++ ) { sum += b[i]; res = max(res, sum); } cout << res << endl; return 0;}</code></pre><h3 id="1987-粉刷栅栏"><a href="#1987-粉刷栅栏" class="headerlink" title="1987. 粉刷栅栏 "></a><a href="https://www.acwing.com/problem/content/description/1989/">1987. 粉刷栅栏 </a></h3><h4 id="题目大意:-1"><a href="#题目大意:-1" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>一头牛绑着个粉刷,在栏杆走,给了每次从当前位置走的距离和方向,问走完以后有多长的距离是刷过两次以上的</p><h4 id="思路:-1"><a href="#思路:-1" class="headerlink" title="思路:"></a>思路:</h4><h4 id="代码:-1"><a href="#代码:-1" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#include <map>using namespace std;int main(){ // b 表示差分数组 map<int,int> b; int n; cin >> n; int x = 0, y; char s[2]; while(n --){ //读入移动的距离以及方向 scanf("%d%s",&y,s); if(*s == 'R'){ //假如当前是1,往右走3,则1-4之间是走过的,4之后是未走过的,b[1]++,b[4]-- b[x] ++; b[x + y] --; x += y; } else{ b[x - y] ++; b[x] --; x -= y; } } int last, res = 0, sum = 0; for(auto [x, v] : b){ if(sum >= 2)res += x - last; sum += v; //记录上次停下的位置 last = x; } cout << res << endl; return 0;}</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> acwing </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> 差分 </tag>
</tags>
</entry>
<entry>
<title>图论_基础</title>
<link href="/2022/03/31/%E7%AE%97%E6%B3%95/acwing/%E5%9B%BE%E8%AE%BA_%E5%9F%BA%E7%A1%80/"/>
<url>/2022/03/31/%E7%AE%97%E6%B3%95/acwing/%E5%9B%BE%E8%AE%BA_%E5%9F%BA%E7%A1%80/</url>
<content type="html"><![CDATA[<p>图论 (Graph theory) 是数学的一个分支,图是图论的主要研究对象。图 (Graph) 是由若干给定的顶点及连接两顶点的边所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系。顶点用于代表事物,连接两顶点的边则用于表示两个事物间具有这种关系。</p><span id="more"></span><p>[TOC]</p><h2 id="一般问题"><a href="#一般问题" class="headerlink" title="一般问题"></a>一般问题</h2><h3 id="848-有向图的拓扑序列"><a href="#848-有向图的拓扑序列" class="headerlink" title="848. 有向图的拓扑序列"></a><a href="https://www.acwing.com/problem/content/850/">848. 有向图的拓扑序列</a></h3><p>给定一个 n 个点 m 条边的有向图,点的编号是 1 到 n,图中可能存在重边和自环。</p><p>请输出任意一个该有向图的拓扑序列,如果拓扑序列不存在,则输出 −1。</p><p>若一个由图中所有点构成的序列 A 满足:对于图中的每条边 (x,y),x 在 A 中都出现在 y 之前,则称 A 是该图的一个拓扑序列。</p><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 100010;int n, m;int h[N], e[N], ne[N], idx;int d[N];//某个节点的入度int q[N];void add(int a, int b){ e[idx] = b, ne[idx] = h[a], h[a] = idx ++;}//入队列:q[++ tt] = x//出队列:x =q[hh ++]bool topsort(){ int hh = 0, tt = -1; for (int i = 1; i <= n; i ++ ) if (!d[i])//d中是e[i] q[++ tt] = i;//队列中存储的是e[i] while (hh <= tt)//当队列不空 { int t = q[hh ++];//从队列头部取出一个元素,t是一个idx for (int i = h[t]; i != -1; i = ne[i])//取出这个元素的所有出度的idx { int j = e[i];//将idx转化为e[i] if (-- d[j] == 0) q[++ tt] = j; } } return tt == n - 1;}int main(){ scanf("%d%d", &n, &m); memset(h, -1, sizeof h); for (int i = 0; i < m; i ++ ) { int a, b; scanf("%d%d", &a, &b); add(a, b); d[b] ++ ; } if (!topsort()) puts("-1"); else { for (int i = 0; i < n; i ++ ) printf("%d ", q[i]); puts(""); } return 0;}</code></pre><p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220906122757.png" alt="img"></p><h2 id="Dijkstra"><a href="#Dijkstra" class="headerlink" title="Dijkstra"></a>Dijkstra</h2><p>朴素Dijkstra</p><pre><code class="cpp">int g[N][N]; // 存储每条边int dist[N]; // 存储1号点到每个点的最短距离bool st[N]; // 存储每个点的最短路是否已经确定// 求1号点到n号点的最短路,如果不存在则返回-1int dijkstra(){ memset(dist, 0x3f, sizeof dist); dist[1] = 0; for (int i = 0; i < n - 1; i ++ ) { int t = -1; // 在还未确定最短路的点中,寻找距离最小的点 for (int j = 1; j <= n; j ++ ) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; // 用t更新其他点的距离 for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], dist[t] + g[t][j]); st[t] = true; } if (dist[n] == 0x3f3f3f3f) return -1; return dist[n];}</code></pre><h3 id="849-Dijkstra求最短路-I"><a href="#849-Dijkstra求最短路-I" class="headerlink" title="849. Dijkstra求最短路 I "></a><a href="https://www.acwing.com/problem/content/description/851/">849. Dijkstra求最短路 I </a></h3><p>给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,所有边权均为正值。</p><p>请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 −1。</p><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 510;int n, m;int g[N][N];int dist[N];bool st[N];int dijkstra(){ memset(dist, 0x3f, sizeof dist); dist[1] = 0; for (int i = 0; i < n - 1; i ++ ) {//遍历当前所有未被却认为距离最短的点,即st[i] 为false的点//选出其中距离源点最近的点的下标,即dist[j]最小的点,用t来记录这个点的下标//用t来更新其他点到源点的距离,为min(dist[j] , dist[t] + g[t][j]);//把之前用t记录的点标记为已经确认为的最短距离的点,即另st[t] = true; int t = -1; for (int j = 1; j <= n; j ++ ) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; for (int j = 1; j <= n; j ++) dist[j] = min(dist[j], dist[t] + g[t][j]); st[t] = true; } if (dist[n] == 0x3f3f3f3f) return -1; return dist[n];}int main(){ scanf("%d%d", &n, &m); memset(g, 0x3f, sizeof g); while (m --) { int a, b, c; scanf("%d%d%d", &a, &b, &c); g[a][b] = min(g[a][b], c); } printf("%d\n", dijkstra()); return 0;}</code></pre><h2 id="Bellman-Ford"><a href="#Bellman-Ford" class="headerlink" title="Bellman-Ford"></a>Bellman-Ford</h2><pre><code class="cpp">int n, m; // n表示点数,m表示边数int dist[N]; // dist[x]存储1到x的最短路距离struct Edge // 边,a表示出点,b表示入点,w表示边的权重{ int a, b, w;}edges[M];// 求1到n的最短路距离,如果无法从1走到n,则返回-1。int bellman_ford(){ memset(dist, 0x3f, sizeof dist); dist[1] = 0; // 如果第n次迭代仍然会松弛三角不等式,就说明存在一条长 度是n+1的最短路径,由抽屉原理,路径中至少存在两个相同的点 ,说明图中存在负权回路。 for (int i = 0; i < n; i ++ ) { for (int j = 0; j < m; j ++ ) { int a = edges[j].a, b = edges[j].b, w = edges[j].w; if (dist[b] > dist[a] + w) dist[b] = dist[a] + w; } } if (dist[n] > 0x3f3f3f3f / 2) return -1; return dist[n];}</code></pre><h3 id="853-有边数限制的最短路"><a href="#853-有边数限制的最短路" class="headerlink" title="853. 有边数限制的最短路"></a><a href="https://www.acwing.com/problem/content/description/855/">853. 有边数限制的最短路</a></h3><p>给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, <strong>边权可能为负数</strong>。</p><p>请你求出从 1 号点到 n 号点的最多经过 k 条边的最短距离,如果无法从 1 号点走到 n 号点,输出 <code>impossible</code>。</p><p>注意:图中可能 <strong>存在负权回路</strong> 。</p><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 510, M = 10010;struct Edge{ int a, b, c;}edges[M];int n, m, k;int dist[N];int last[N];void bellman_ford(){ memset(dist, 0x3f, sizeof dist); dist[1] = 0; for (int i = 0; i < k; i ++ ) {//last是备份,上一次的迭代结果//循环k次,因为多只能走k条路径//遍历所有的边,e是取出的当前遍历的边//e.b是e这条边所指向的节点,dist[e.b]是从节点1到e这条边指向的节点的最短路径//更新dist[e.b]用的是当前值和上一次迭代结果+当前边的权重e.c//只有当前边e的源点的e.a的dist已经被更新过了,last[e.a]才不是无穷,更新的dist[e.b]才有意义。//这时候min(dist[e.b],last[e.a] + e.c)取的才是last[e.a] +e.c memcpy(last, dist, sizeof dist); for (int j = 0; j < m; j ++ ) { auto e = edges[j]; dist[e.b] = min(dist[e.b], last[e.a] + e.c); } }}int main(){ scanf("%d%d%d", &n, &m, &k); for (int i = 0; i < m; i ++ ) { int a, b, c; scanf("%d%d%d", &a, &b, &c); edges[i] = {a, b, c}; } bellman_ford(); if (dist[n] > 0x3f3f3f3f / 2) puts("impossible"); else printf("%d\n", dist[n]); return 0;}</code></pre><h2 id="spfa"><a href="#spfa" class="headerlink" title="spfa"></a>spfa</h2><p>spfa 算法(队列优化的Bellman-Ford算法)</p><pre><code class="cpp">int n; // 总点数int h[N], w[N], e[N], ne[N], idx; // 邻接表存储所有边int dist[N]; // 存储每个点到1号点的最短距离bool st[N]; // 存储每个点是否在队列中// 求1号点到n号点的最短路距离,如果从1号点无法走到n号点则返回-1int spfa(){ memset(dist, 0x3f, sizeof dist); dist[1] = 0; queue<int> q; q.push(1); st[1] = true; while (q.size()) { auto t = q.front(); q.pop(); st[t] = false; for (int i = h[t]; i != -1; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; if (!st[j]) // 如果队列中已存在j,则不需要将j重复插入 { q.push(j); st[j] = true; } } } } if (dist[n] == 0x3f3f3f3f) return -1; return dist[n];}</code></pre><h3 id="851-spfa求最短路"><a href="#851-spfa求最短路" class="headerlink" title="851. spfa求最短路 "></a><a href="https://www.acwing.com/problem/content/description/853/">851. spfa求最短路 </a></h3><p>给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, <strong>边权可能为负数</strong>。</p><p>请你求出 1 号点到 n 号点的最短距离,如果无法从 1 号点走到 n 号点,则输出 <code>impossible</code>。</p><p>数据保证不存在负权回路。</p><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>#include <queue>using namespace std;const int N = 100010;int n, m;int h[N], w[N], e[N], ne[N], idx;int dist[N];bool st[N];void add(int a, int b, int c){ e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;}int spfa(){ memset(dist, 0x3f, sizeof dist); dist[1] = 0; queue<int> q; q.push(1); st[1] = true;spfa是改进版的bellman_ford算法因为bellman_ford只有在条边的源点的dist更新之后,这条边的汇点的dist才有可能更新所以只用更新:更新过的节点及其之后所连的点的dist现在用q队列存储所有已经被更新,但是其后的节点未更新的节点(只要dist更新了,就放到队列中)每次从q头部取出一个元素q.front设为t,设其st为false,即表示t已经不在队列中了st[t]是e为t的点是否在队列中,防止队列中出现重复元素开始遍历t为头部的其后拉链中的所有点注意dist的[]中是当前节点i的e[i]如果判断dist[e[i]]>dist[t] + w[i] 即:从点1到点i的距离dist[e[i]]大于从点1到点t的距离dist[t]+从点t到点i的距离w[i]则更新dist[i]为后者如果e[i]不在队列中,即st[e[i]]=false,则把e[i]加入队列中,同时设st[e[i]]为true while (q.size()) { int t = q.front(); q.pop(); st[t] = false; for (int i = h[t]; i != -1; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; if (!st[j]) { q.push(j); st[j] = true; } } } } return dist[n];}int main(){ scanf("%d%d", &n, &m); memset(h, -1, sizeof h); while (m -- ) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c); } int t = spfa(); if (t == 0x3f3f3f3f) puts("impossible"); else printf("%d\n", t); return 0;}</code></pre><h3 id="852-spfa判断负环"><a href="#852-spfa判断负环" class="headerlink" title="852. spfa判断负环"></a><a href="https://www.acwing.com/problem/content/description/854/">852. spfa判断负环</a></h3><p>给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环, <strong>边权可能为负数</strong>。</p><p>请你判断图中是否存在负权回路。</p><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>#include <queue>using namespace std;const int N = 2010, M = 10010;int n, m;int h[N], w[M], e[M], ne[M], idx;int dist[N], cnt[N];bool st[N];void add(int a, int b, int c){ e[idx] = b, w[idx] = c, ne[idx] = h[a], h[a] = idx ++ ;}bool spfa(){ queue<int> q; for (int i = 1; i <= n; i ++ ) { st[i] = true; q.push(i); } while (q.size()) { int t = q.front(); q.pop(); st[t] = false; for (int i = h[t]; i != -1; i = ne[i]) { int j = e[i]; if (dist[j] > dist[t] + w[i]) { dist[j] = dist[t] + w[i]; cnt[j] = cnt[t] + 1; if (cnt[j] >= n) return true; cnt[e[i]]是源点到e[i]所经过的边数,为e[t] + 1,t是前一条边的源点 如果一个点的最短路径有n条边,那么有n+1个节点,与题意n个点不符 if (!st[j]) { q.push(j); st[j] = true; } } } } return false;}int main(){ scanf("%d%d", &n, &m); memset(h, -1, sizeof h); while (m -- ) { int a, b, c; scanf("%d%d%d", &a, &b, &c); add(a, b, c); } if (spfa()) puts("Yes"); else puts("No"); return 0;}</code></pre><h2 id="Floyd"><a href="#Floyd" class="headerlink" title="Floyd"></a>Floyd</h2><h3 id="854-Floyd求最短路"><a href="#854-Floyd求最短路" class="headerlink" title="854. Floyd求最短路"></a><a href="https://www.acwing.com/problem/content/description/856/">854. Floyd求最短路</a></h3><p>给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环,边权可能为负数。</p><p>再给定 k 个询问,每个询问包含两个整数 x 和 y,表示查询从点 x 到点 y 的最短距离,如果路径不存在,则输出 <code>impossible</code>。</p><p>数据保证图中不存在负权回路。</p><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 210, INF = 1e9;int n, m, Q;int d[N][N];void floyd(){ for (int k = 1; k <= n; k ++ ) for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= n; j ++ ) d[i][j] = min(d[i][j], d[i][k] + d[k][j]);}int main(){ scanf("%d%d%d", &n, &m, &Q); for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= n; j ++ ) if (i == j) d[i][j] = 0; else d[i][j] = INF; while (m -- ) { int a, b, c; scanf("%d%d%d", &a, &b, &c); d[a][b] = min(d[a][b], c); } floyd(); while (Q -- ) { int a, b; scanf("%d%d", &a, &b); int t = d[a][b]; if (t > INF / 2) puts("impossible"); else printf("%d\n", t); } return 0;}</code></pre><h3 id="1471-牛奶工厂"><a href="#1471-牛奶工厂" class="headerlink" title="1471. 牛奶工厂"></a><a href="https://www.acwing.com/problem/content/1473/">1471. 牛奶工厂</a></h3><h4 id="题目大意:"><a href="#题目大意:" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>每个点之间都是单向通道,问是否存在一个点,所有的点都能到这个点</p><h4 id="思路1:"><a href="#思路1:" class="headerlink" title="思路1:"></a>思路1:</h4><p>O(n^3)</p><p>直接Floyd构造出来邻接矩阵,然后看是否有一列全是1</p><h4 id="代码1:"><a href="#代码1:" class="headerlink" title="代码1:"></a>代码1:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 110;int n;int g[N][N];int main(){ cin >> n; for (int i = 1; i <= n; i ++ ) g[i][i] = 1; for (int i = 0; i < n - 1; i ++ ) { int a, b; cin >> a >> b; g[a][b] = 1; } for (int k = 1; k <= n; k ++ ) for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= n; j ++ ) if (g[i][k] && g[k][j]) g[i][j] = 1; for (int i = 1; i <= n; i ++ ) { bool flag = true; for (int j = 1; j <= n; j ++ ) if (!g[j][i]) { flag = false; break; } · if (flag) { cout << i << endl; return 0; } } puts("-1"); return 0;}</code></pre><h4 id="思路2:"><a href="#思路2:" class="headerlink" title="思路2:"></a>思路2:</h4><p>O(n)</p><ol><li><p>如果有解的话,解一定唯一</p></li><li><p>有解 等价于:有且仅有一个点,出度为0</p></li></ol><h4 id="代码2"><a href="#代码2" class="headerlink" title="代码2"></a>代码2</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 110;int n;int d[N];int main(){ cin >> n; for (int i = 0; i < n - 1; i ++ ) { int a, b; cin >> a >> b; d[a] ++ ; } int cnt = 0, id; for (int i = 1; i <= n; i ++ ) if (!d[i]) { cnt ++ ; id = i; } if (cnt == 1) cout << id << endl; else puts("-1"); return 0;}</code></pre><h2 id="Prim"><a href="#Prim" class="headerlink" title="Prim"></a>Prim</h2><h3 id="858-Prim算法求最小生成树"><a href="#858-Prim算法求最小生成树" class="headerlink" title="858. Prim算法求最小生成树"></a><a href="https://www.acwing.com/problem/content/description/860/">858. Prim算法求最小生成树</a></h3><p>给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环,边权可能为负数。</p><p>求最小生成树的树边权重之和,如果最小生成树不存在则输出 <code>impossible</code>。</p><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 510, INF = 0x3f3f3f3f;int n, m;int g[N][N];int dist[N];bool st[N];dist[i] 表示i点到已求连通图的最短距离st[i] 表示i点是否在已求连通图中进行n次遍历每次遍历中,在所有的不在已求连通图的点中找出一个距离已知连通图最近的点,记录下标t如果不是第一次遍历,且找到的距离已知连通图最近的距离是无穷,则返回INF表示不存在最小生成树否则,将t点的距离加到生成树的边之和res中把t点加到已知的连通图中,即令st[t] = true;更新所有点到已知连通图的最短距离,min(dist[j],g[t][j]);int prim(){ memset(dist, 0x3f, sizeof dist); int res = 0; for (int i = 0; i < n; i ++ ) { int t = -1; for (int j = 1; j <= n; j ++ ) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; if (i && dist[t] == INF) return INF; if (i) res += dist[t]; st[t] = true; for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], g[t][j]); } return res;}int main(){ scanf("%d%d", &n, &m); memset(g, 0x3f, sizeof g); while (m -- ) { int a, b, c; scanf("%d%d%d", &a, &b, &c); g[a][b] = g[b][a] = min(g[a][b], c); } int t = prim(); if (t == INF) puts("impossible"); else printf("%d\n", t); return 0;}</code></pre><h3 id="1140-最短网络"><a href="#1140-最短网络" class="headerlink" title="1140. 最短网络"></a><a href="https://www.acwing.com/problem/content/1142/">1140. 最短网络</a></h3><h4 id="题目大意:-1"><a href="#题目大意:-1" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>给一个n*n的方针,a[i][j]代表节点i和j的距离,现在要用光纤连通各个点以建立互联网,问所用光纤的最短距离</p><h4 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h4><p>prim算法:由一个中心点不断向外扩张,每次只扩张离已知集合的最短的一条边用邻接矩阵来存储</p><p>dist[N]数组存储的是所有点跟已知集合的直接相连的边的最短的距离</p><h4 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 110;int n;int w[N][N];int dist[N];bool st[N];int prim(){ int res = 0; memset(dist, 0x3f, sizeof dist); dist[1] = 0; for (int i = 0; i < n; i ++ ) { int t = -1; for (int j = 1; j <= n; j ++ ) if (!st[j] && (t == -1 || dist[t] > dist[j])) t = j; res += dist[t]; st[t] = true; for (int j = 1; j <= n; j ++ ) dist[j] = min(dist[j], w[t][j]); } return res;}int main(){ cin >> n; for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= n; j ++ ) cin >> w[i][j]; cout << prim() << endl; return 0;}</code></pre><h2 id="Kruskal"><a href="#Kruskal" class="headerlink" title="Kruskal"></a>Kruskal</h2><h3 id="859-Kruskal算法求最小生成树"><a href="#859-Kruskal算法求最小生成树" class="headerlink" title="859. Kruskal算法求最小生成树"></a><a href="https://www.acwing.com/problem/content/description/861/">859. Kruskal算法求最小生成树</a></h3><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 100010, M = 200010, INF = 0x3f3f3f3f;int n, m;int p[N];struct Edge{ int a, b, w; bool operator< (const Edge &W)const { return w < W.w; }}edges[M];int find(int x){ if (p[x] != x) p[x] = find(p[x]); return p[x];}int kruskal(){ 1.快速排序 2.枚举每条边a,b,w 如果a,b不连通,(即a,b不在一个集合里面),则将这条边加到集合里面去,a,b的集合合并 sort(edges, edges + m); for (int i = 1; i <= n; i ++ ) p[i] = i; // 初始化并查集 int res = 0, cnt = 0; for (int i = 0; i < m; i ++ ) { int a = edges[i].a, b = edges[i].b, w = edges[i].w; a = find(a), b = find(b); if (a != b) { p[a] = b; res += w; cnt ++ ; } } if (cnt < n - 1) return INF; return res;}int main(){ scanf("%d%d", &n, &m); for (int i = 0; i < m; i ++ ) { int a, b, w; scanf("%d%d%d", &a, &b, &w); edges[i] = {a, b, w}; } int t = kruskal(); if (t == INF) puts("impossible"); else printf("%d\n", t); return 0;}</code></pre><h3 id="1141-局域网"><a href="#1141-局域网" class="headerlink" title="1141. 局域网"></a><a href="https://www.acwing.com/problem/content/1143/">1141. 局域网</a></h3><p>kruskal用结构体来存储,之后要按照边从小到大排序,所以要重载小于号</p><p>集合思想</p><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 110, M = 210;int n, m;struct Edge{ int a, b, w; bool operator< (const Edge &t)const { return w < t.w; }}e[M];int p[N];int find(int x){ if (p[x] != x) p[x] = find(p[x]); return p[x];}int main(){ cin >> n >> m; for (int i = 1; i <= n; i ++ ) p[i] = i; for (int i = 0; i < m; i ++ ) { int a, b, w; cin >> a >> b >> w; e[i] = {a, b, w}; } sort(e, e + m); int res = 0; for (int i = 0; i < m; i ++ ) { int a = find(e[i].a), b = find(e[i].b), w = e[i].w; if (a != b) p[a] = b; else res += w; } cout << res << endl; return 0;}</code></pre><h3 id="1143-联络员"><a href="#1143-联络员" class="headerlink" title="1143. 联络员"></a><a href="https://www.acwing.com/problem/content/1145/">1143. 联络员</a></h3><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 2010, M = 10010;int n, m;struct Edge{ int a, b, w; bool operator< (const Edge &t) const { return w < t.w; }}e[M];int p[N];int find(int x){ if (p[x] != x) p[x] = find(p[x]); return p[x];}int main(){ cin >> n >> m; for (int i = 1; i <= n; i ++ ) p[i] = i; int res = 0, k = 0; for (int i = 0; i < m; i ++ ) { int t, a, b, w; cin >> t >> a >> b >> w; if (t == 1) { res += w; p[find(a)] = find(b); } else e[k ++ ] = {a, b, w}; } sort(e, e + k); for (int i = 0; i < k; i ++ ) { int a = find(e[i].a), b = find(e[i].b), w = e[i].w; if (a != b) { p[a] = b; res += w; } } cout << res << endl; return 0;}</code></pre><h2 id="二分图"><a href="#二分图" class="headerlink" title="二分图"></a>二分图</h2><h3 id="860-染色法判定二分图"><a href="#860-染色法判定二分图" class="headerlink" title="860. 染色法判定二分图"></a><a href="https://www.acwing.com/problem/content/description/862/">860. 染色法判定二分图</a></h3><p>给定一个 n 个点 m 条边的无向图,图中可能存在重边和自环。</p><p>请你判断这个图是否是二分图。</p><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 100010, M = 200010;int n, m;int h[N], e[M], ne[M], idx;int color[N];void add(int a, int b){ e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;}bool dfs(int u, int c){ color[u] = c; for (int i = h[u]; i != -1; i = ne[i]) { int j = e[i]; if (!color[j]) { if (!dfs(j, 3 - c)) return false; } else if (color[j] == c) return false; } return true;}int main(){ scanf("%d%d", &n, &m); memset(h, -1, sizeof h); while (m -- ) { int a, b; scanf("%d%d", &a, &b); add(a, b), add(b, a); } bool flag = true; for (int i = 1; i <= n; i ++ ) if (!color[i]) { if (!dfs(i, 1)) { flag = false; break; } } if (flag) puts("Yes"); else puts("No"); return 0;}</code></pre><h3 id="861-二分图的最大匹配"><a href="#861-二分图的最大匹配" class="headerlink" title="861. 二分图的最大匹配"></a><a href="https://www.acwing.com/problem/content/description/863/">861. 二分图的最大匹配</a></h3><p>给定一个二分图,其中左半部包含 n1 个点(编号 1∼n1),右半部包含 n2 个点(编号 1∼n2),二分图共包含 m 条边。</p><p>数据保证任意一条边的两个端点都不可能在同一部分中。</p><p>请你求出二分图的最大匹配数。</p><blockquote><p>二分图的匹配:给定一个二分图 G,在 G 的一个子图 M 中,M 的边集 {E} 中的任意两条边都不依附于同一个顶点,则称 M 是一个匹配。</p><p>二分图的最大匹配:所有匹配中包含边数最多的一组匹配被称为二分图的最大匹配,其边数即为最大匹配数。</p></blockquote><pre><code class="cpp">int n1, n2; // n1表示第一个集合中的点数,n2表示第二个集合中的点数int h[N], e[M], ne[M], idx; // 邻接表存储所有边,匈牙利算法中只会用到从第一个集合指向第二个集合的边,所以这里只用存一个方向的边int match[N]; // 存储第二个集合中的每个点当前匹配的第一个集合中的点是哪个bool st[N]; // 表示第二个集合中的每个点是否已经被遍历过bool find(int x){ for (int i = h[x]; i != -1; i = ne[i]) { int j = e[i]; if (!st[j]) { st[j] = true; if (match[j] == 0 || find(match[j])) { match[j] = x; return true; } } } return false;}// 求最大匹配数,依次枚举第一个集合中的每个点能否匹配第二个集合中的点int res = 0;for (int i = 1; i <= n1; i ++ ){ memset(st, false, sizeof st); if (find(i)) res ++ ;}</code></pre><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 510, M = 100010;int n1, n2, m;int h[N], e[M], ne[M], idx;int match[N];bool st[N];h[N]后面的拉链是记录的是每个男生的有好感的对象match[i]是女生i已经匹配了的对象st[i]是女生i在这一轮有没有被考虑过逐个遍历男生:每次遍历时,将所有的女生已经遍历的对象清空进入find函数,返回这个男生x能不能找到对象遍历他所有心仪的对象 j= e[i]如果这个女生还没有被考虑过,即st[j] = false那么就让st为true如果这个女生还没有找到对象,或是他的对象还可以考虑其他女生,则可以将这个女生匹配给x即返回true,且将match[j]设为xvoid add(int a, int b){ e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;}bool find(int x){ for (int i = h[x]; i != -1; i = ne[i]) { int j = e[i]; if (!st[j]) { st[j] = true; if (match[j] == 0 || find(match[j])) { match[j] = x; return true; } } } return false;}int main(){ scanf("%d%d%d", &n1, &n2, &m); memset(h, -1, sizeof h); while (m -- ) { int a, b; scanf("%d%d", &a, &b); add(a, b); } int res = 0; for (int i = 1; i <= n1; i ++ ) { memset(st, false, sizeof st); if (find(i)) res ++ ; } printf("%d\n", res); return 0;}</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> acwing </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> 图论 </tag>
</tags>
</entry>
<entry>
<title>数学</title>
<link href="/2022/03/31/%E7%AE%97%E6%B3%95/acwing/%E6%95%B0%E5%AD%A6/"/>
<url>/2022/03/31/%E7%AE%97%E6%B3%95/acwing/%E6%95%B0%E5%AD%A6/</url>
<content type="html"><![CDATA[<p>计算机科学与数学紧密相关,而在算法竞赛中尤其强调以数论、排列组合、概率期望、多项式为代表离散、具体的数学:其注重程序实现和现实问题,可以出现在几乎任何类别的题目中。</p><p>实际上,算法竞赛中涉及到的算法和数据结构以及自动机等也可以被认为属于数学范畴,但是这些内容被细分到诸如字符串等的具体章节加以应用背景以更好理解。本篇主要介绍数学中一些基础概念、代数、数论、博弈论及概率论等知识。</p><span id="more"></span><p>[TOC]</p><h2 id="模板"><a href="#模板" class="headerlink" title="模板"></a>模板</h2><h3 id="判断回文数"><a href="#判断回文数" class="headerlink" title="判断回文数"></a>判断回文数</h3><pre><code class="cpp">bool pd_h(int x){ int y = x, num = 0; while (y != 0) { num = num * 10 + y % 10; y /= 10; } if (num == x) return true; else return false;}</code></pre><h3 id="试除法判定质数"><a href="#试除法判定质数" class="headerlink" title="试除法判定质数"></a>试除法判定质数</h3><p> —— 模板题 AcWing 866. 试除法判定质数</p><pre><code class="cpp">bool is_prime(int x){ if (x < 2) return false; for (int i = 2; i <= x / i; i ++ ) if (x % i == 0) return false; return true;}</code></pre><h3 id="试除法分解质因数"><a href="#试除法分解质因数" class="headerlink" title="试除法分解质因数"></a>试除法分解质因数</h3><p> —— 模板题 AcWing 867. 分解质因数</p><pre><code class="cpp">void divide(int x){ for (int i = 2; i <= x / i; i ++ ) if (x % i == 0) { int s = 0; while (x % i == 0) x /= i, s ++ ; cout << i << ' ' << s << endl; } if (x > 1) cout << x << ' ' << 1 << endl; cout << endl;}</code></pre><h3 id="朴素筛法求素数"><a href="#朴素筛法求素数" class="headerlink" title="朴素筛法求素数"></a>朴素筛法求素数</h3><p> —— 模板题 AcWing 868. 筛质数</p><pre><code class="cpp">int primes[N], cnt; // primes[]存储所有素数bool st[N]; // st[x]存储x是否被筛掉void get_primes(int n){ for (int i = 2; i <= n; i ++ ) { if (st[i]) continue; primes[cnt ++ ] = i; for (int j = i + i; j <= n; j += i) st[j] = true; }}</code></pre><h3 id="线性筛法求素数"><a href="#线性筛法求素数" class="headerlink" title="线性筛法求素数"></a>线性筛法求素数</h3><p> —— 模板题 AcWing 868. 筛质数</p><pre><code class="cpp">int primes[N], cnt; // primes[]存储所有素数bool st[N]; // st[x]存储x是否被筛掉void get_primes(int n){ for (int i = 2; i <= n; i ++ ) { if (!st[i]) primes[cnt ++ ] = i; for (int j = 0; primes[j] <= n / i; j ++ ) { st[primes[j] * i] = true; if (i % primes[j] == 0) break; } }}</code></pre><h3 id="试除法求所有约数"><a href="#试除法求所有约数" class="headerlink" title="试除法求所有约数"></a>试除法求所有约数</h3><p> —— 模板题 AcWing 869. 试除法求约数</p><pre><code class="cpp">vector<int> get_divisors(int x){ vector<int> res; for (int i = 1; i <= x / i; i ++ ) if (x % i == 0) { res.push_back(i); if (i != x / i) res.push_back(x / i); } sort(res.begin(), res.end()); return res;}</code></pre><h3 id="约数个数和约数之和"><a href="#约数个数和约数之和" class="headerlink" title="约数个数和约数之和"></a>约数个数和约数之和</h3><p>—— 模板题 AcWing 870. 约数个数, AcWing 871. 约数之和</p><p>如果 N = p1^c1 * p2^c2 * … *pk^ck<br>约数个数: (c1 + 1) * (c2 + 1) * … * (ck + 1)<br>约数之和: (p1^0 + p1^1 + … + p1^c1) * … * (pk^0 + pk^1 + … + pk^ck)<br>欧几里得算法 —— 模板题 AcWing 872. 最大公约数</p><pre><code class="cpp">int gcd(int a, int b){ return b ? gcd(b, a % b) : a;}</code></pre><h3 id="求欧拉函数"><a href="#求欧拉函数" class="headerlink" title="求欧拉函数"></a>求欧拉函数</h3><p>—— 模板题 AcWing 873. 欧拉函数</p><pre><code class="cpp">int phi(int x){ int res = x; for (int i = 2; i <= x / i; i ++ ) if (x % i == 0) { res = res / i * (i - 1); while (x % i == 0) x /= i; } if (x > 1) res = res / x * (x - 1); return res;}</code></pre><h3 id="筛法求欧拉函数"><a href="#筛法求欧拉函数" class="headerlink" title="筛法求欧拉函数"></a>筛法求欧拉函数</h3><p>—— 模板题 AcWing 874. 筛法求欧拉函数</p><pre><code class="cpp">int primes[N], cnt; // primes[]存储所有素数int euler[N]; // 存储每个数的欧拉函数bool st[N]; // st[x]存储x是否被筛掉void get_eulers(int n){ euler[1] = 1; for (int i = 2; i <= n; i ++ ) { if (!st[i]) { primes[cnt ++ ] = i; euler[i] = i - 1; } for (int j = 0; primes[j] <= n / i; j ++ ) { int t = primes[j] * i; st[t] = true; if (i % primes[j] == 0) { euler[t] = euler[i] * primes[j]; break; } euler[t] = euler[i] * (primes[j] - 1); } }}</code></pre><p>快速幂</p><p> —— 模板题 AcWing 875. 快速幂</p><p>求 m^k mod p,时间复杂度 O(logk)。</p><pre><code class="cpp">int qmi(int m, int k, int p){ int res = 1 % p, t = m; while (k) { if (k&1) res = res * t % p; t = t * t % p; k >>= 1; } return res;}</code></pre><h3 id="扩展欧几里得算法"><a href="#扩展欧几里得算法" class="headerlink" title="扩展欧几里得算法"></a>扩展欧几里得算法</h3><p>—— 模板题 AcWing 877. 扩展欧几里得算法</p><pre><code class="cpp">// 求x, y,使得ax + by = gcd(a, b)int exgcd(int a, int b, int &x, int &y){ if (!b) { x = 1; y = 0; return a; } int d = exgcd(b, a % b, y, x); y -= (a/b) * x; return d;}</code></pre><h3 id="高斯消元"><a href="#高斯消元" class="headerlink" title="高斯消元"></a>高斯消元</h3><p> —— 模板题 AcWing 883. 高斯消元解线性方程组</p><pre><code class="cpp">// a[N][N]是增广矩阵int gauss(){ int c, r; for (c = 0, r = 0; c < n; c ++ ) { int t = r; for (int i = r; i < n; i ++ ) // 找到绝对值最大的行 if (fabs(a[i][c]) > fabs(a[t][c])) t = i; if (fabs(a[t][c]) < eps) continue; for (int i = c; i <= n; i ++ ) swap(a[t][i], a[r][i]); // 将绝对值最大的行换到最顶端 for (int i = n; i >= c; i -- ) a[r][i] /= a[r][c]; // 将当前行的首位变成1 for (int i = r + 1; i < n; i ++ ) // 用当前行将下面所有的列消成0 if (fabs(a[i][c]) > eps) for (int j = n; j >= c; j -- ) a[i][j] -= a[r][j] * a[i][c]; r ++ ; } if (r < n) { for (int i = r; i < n; i ++ ) if (fabs(a[i][n]) > eps) return 2; // 无解 return 1; // 有无穷多组解 } for (int i = n - 1; i >= 0; i -- ) for (int j = i + 1; j < n; j ++ ) a[i][n] -= a[i][j] * a[j][n]; return 0; // 有唯一解}</code></pre><h3 id="求组合数"><a href="#求组合数" class="headerlink" title="求组合数"></a>求组合数</h3><p>—— 模板题 AcWing 885. 求组合数 I</p><pre><code class="cpp">// c[a][b] 表示从a个苹果中选b个的方案数for (int i = 0; i < N; i ++ ) for (int j = 0; j <= i; j ++ ) if (!j) c[i][j] = 1; else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;</code></pre><h3 id="通过预处理逆元的方式求组合数"><a href="#通过预处理逆元的方式求组合数" class="headerlink" title="通过预处理逆元的方式求组合数"></a>通过预处理逆元的方式求组合数</h3><p> —— 模板题 AcWing 886. 求组合数 II<br>首先预处理出所有阶乘取模的余数fact[N],以及所有阶乘取模的逆元infact[N]<br>如果取模的数是质数,可以用费马小定理求逆元</p><pre><code class="cpp">int qmi(int a, int k, int p) // 快速幂模板{ int res = 1; while (k) { if (k & 1) res = (LL)res * a % p; a = (LL)a * a % p; k >>= 1; } return res;}// 预处理阶乘的余数和阶乘逆元的余数fact[0] = infact[0] = 1;for (int i = 1; i < N; i ++ ){ fact[i] = (LL)fact[i - 1] * i % mod; infact[i] = (LL)infact[i - 1] * qmi(i, mod - 2, mod) % mod;}</code></pre><h3 id="Lucas定理"><a href="#Lucas定理" class="headerlink" title="Lucas定理"></a>Lucas定理</h3><p> —— 模板题 AcWing 887. 求组合数 III<br>若p是质数,则对于任意整数 1 <= m <= n,有:<br> C(n, m) = C(n % p, m % p) * C(n / p, m / p) (mod p)</p><pre><code class="cpp">int qmi(int a, int k, int p) // 快速幂模板{ int res = 1 % p; while (k) { if (k & 1) res = (LL)res * a % p; a = (LL)a * a % p; k >>= 1; } return res;}int C(int a, int b, int p) // 通过定理求组合数C(a, b){ if (a < b) return 0; LL x = 1, y = 1; // x是分子,y是分母 for (int i = a, j = 1; j <= b; i --, j ++ ) { x = (LL)x * i % p; y = (LL) y * j % p; } return x * (LL)qmi(y, p - 2, p) % p;}int lucas(LL a, LL b, int p){ if (a < p && b < p) return C(a, b, p); return (LL)C(a % p, b % p, p) * lucas(a / p, b / p, p) % p;}</code></pre><h3 id="分解质因数法求组合数"><a href="#分解质因数法求组合数" class="headerlink" title="分解质因数法求组合数"></a>分解质因数法求组合数</h3><p>—— 模板题 AcWing 888. 求组合数 IV<br>当我们需要求出组合数的真实值,而非对某个数的余数时,分解质因数的方式比较好用:</p><pre><code>1. 筛法求出范围内的所有质数 2. 通过 C(a, b) = a! / b! / (a - b)! 这个公式求出每个质因子的次数。 n! 中p的次数是 n / p + n / p^2 + n / p^3 + ... 3. 用高精度乘法将所有质因子相乘</code></pre><pre><code class="cpp">int primes[N], cnt; // 存储所有质数int sum[N]; // 存储每个质数的次数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; primes[j] <= n / i; j ++ ) { st[primes[j] * i] = true; if (i % primes[j] == 0) break; } }}int get(int n, int p) // 求n!中的次数{ int res = 0; while (n) { res += n / p; n /= p; } return res;}vector<int> mul(vector<int> a, int b) // 高精度乘低精度模板{ vector<int> c; int t = 0; for (int i = 0; i < a.size(); i ++ ) { t += a[i] * b; c.push_back(t % 10); t /= 10; } while (t) { c.push_back(t % 10); t /= 10; } return c;}get_primes(a); // 预处理范围内的所有质数for (int i = 0; i < cnt; i ++ ) // 求每个质因数的次数{ int p = primes[i]; sum[i] = get(a, p) - get(b, p) - get(a - b, p);}vector<int> res;res.push_back(1);for (int i = 0; i < cnt; i ++ ) // 用高精度乘法将所有质因子相乘 for (int j = 0; j < sum[i]; j ++ ) res = mul(res, primes[i]);</code></pre><h3 id="卡特兰数"><a href="#卡特兰数" class="headerlink" title="卡特兰数"></a>卡特兰数</h3><p>—— 模板题 AcWing 889. 满足条件的01序列<br>给定n个0和n个1,它们按照某种顺序排成长度为2n的序列,满足任意前缀中0的个数都不少于1的个数的序列的数量为: Cat(n) = C(2n, n) / (n + 1)<br>NIM游戏 —— 模板题 AcWing 891. Nim游戏<br>给定N堆物品,第i堆物品有Ai个。两名玩家轮流行动,每次可以任选一堆,取走任意多个物品,可把一堆取光,但不能不取。取走最后一件物品者获胜。两人都采取最优策略,问先手是否必胜。</p><p>我们把这种游戏称为NIM博弈。把游戏过程中面临的状态称为局面。整局游戏第一个行动的称为先手,第二个行动的称为后手。若在某一局面下无论采取何种行动,都会输掉游戏,则称该局面必败。<br>所谓采取最优策略是指,若在某一局面下存在某种行动,使得行动后对面面临必败局面,则优先采取该行动。同时,这样的局面被称为必胜。我们讨论的博弈问题一般都只考虑理想情况,即两人均无失误,都采取最优策略行动时游戏的结果。<br>NIM博弈不存在平局,只有先手必胜和先手必败两种情况。</p><p>定理: NIM博弈先手必胜,当且仅当 A1 ^ A2 ^ … ^ An != 0</p><p>公平组合游戏ICG<br>若一个游戏满足:</p><p>由两名玩家交替行动;<br>在游戏进程的任意时刻,可以执行的合法行动与轮到哪名玩家无关;<br>不能行动的玩家判负;<br>则称该游戏为一个公平组合游戏。<br>NIM博弈属于公平组合游戏,但城建的棋类游戏,比如围棋,就不是公平组合游戏。因为围棋交战双方分别只能落黑子和白子,胜负判定也比较复杂,不满足条件2和条件3。</p><p>有向图游戏<br>给定一个有向无环图,图中有一个唯一的起点,在起点上放有一枚棋子。两名玩家交替地把这枚棋子沿有向边进行移动,每次可以移动一步,无法移动者判负。该游戏被称为有向图游戏。<br>任何一个公平组合游戏都可以转化为有向图游戏。具体方法是,把每个局面看成图中的一个节点,并且从每个局面向沿着合法行动能够到达的下一个局面连有向边。</p><p>Mex运算<br>设S表示一个非负整数集合。定义mex(S)为求出不属于集合S的最小非负整数的运算,即:<br>mex(S) = min{x}, x属于自然数,且x不属于S</p><p>SG函数<br>在有向图游戏中,对于每个节点x,设从x出发共有k条有向边,分别到达节点y1, y2, …, yk,定义SG(x)为x的后继节点y1, y2, …, yk 的SG函数值构成的集合再执行mex(S)运算的结果,即:<br>SG(x) = mex({SG(y1), SG(y2), …, SG(yk)})<br>特别地,整个有向图游戏G的SG函数值被定义为有向图游戏起点s的SG函数值,即SG(G) = SG(s)。</p><p>有向图游戏的和 —— 模板题 AcWing 893. 集合-Nim游戏<br>设G1, G2, …, Gm 是m个有向图游戏。定义有向图游戏G,它的行动规则是任选某个有向图游戏Gi,并在Gi上行动一步。G被称为有向图游戏G1, G2, …, Gm的和。<br>有向图游戏的和的SG函数值等于它包含的各个子游戏SG函数值的异或和,即:<br>SG(G) = SG(G1) ^ SG(G2) ^ … ^ SG(Gm)</p><p>定理<br>有向图游戏的某个局面必胜,当且仅当该局面对应节点的SG函数值大于0。<br>有向图游戏的某个局面必败,当且仅当该局面对应节点的SG函数值等于0。</p><h2 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h2><h3 id="1346-回文平方"><a href="#1346-回文平方" class="headerlink" title="1346. 回文平方"></a><a href="https://www.acwing.com/problem/content/1348/">1346. 回文平方</a></h3><p>回文数是指数字从前往后读和从后往前读都相同的数字。</p><p>例如数字 12321 就是典型的回文数字。</p><p>现在给定你一个整数 B,请你判断 1∼300之间的所有整数中,有哪些整数的<strong>平方</strong>转化为 B 进制后,其 B 进制表示是回文数字。</p><h4 id="输入格式"><a href="#输入格式" class="headerlink" title="输入格式"></a>输入格式</h4><p>一个整数 B。</p><h4 id="输出格式"><a href="#输出格式" class="headerlink" title="输出格式"></a>输出格式</h4><p>每行包含两个<strong>在 B 进制下</strong>表示的数字。</p><p>第一个表示满足平方值转化为 B 进制后是回文数字那个数,第二个数表示第一个数的平方。</p><p>所有满足条件的数字按从小到大顺序依次输出。</p><h4 id="数据范围"><a href="#数据范围" class="headerlink" title="数据范围"></a>数据范围</h4><p>2≤B≤20,<br>对于大于 9 的数字,用 A 表示 10,用 B 表示 11,以此类推。</p><h4 id="输入样例:"><a href="#输入样例:" class="headerlink" title="输入样例:"></a>输入样例:</h4><pre><code>10</code></pre><h4 id="输出样例:"><a href="#输出样例:" class="headerlink" title="输出样例:"></a>输出样例:</h4><pre><code>1 12 43 911 12122 48426 676101 10201111 12321121 14641202 40804212 44944264 69696</code></pre><h4 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h4><ul><li>短除法进行进制的转换。</li><li>双指针检查是否是回文序列</li></ul><h4 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;char get(int x){ if (x <= 9) return x + '0'; return x - 10 + 'A';}string base(int n, int b){ string num; while (n) num += get(n % b), n /= b; reverse(num.begin(), num.end()); return num;}bool check(string num){ for (int i = 0, j = num.size() - 1; i < j; i ++, j -- ) if (num[i] != num[j]) return false; return true;}int main(){ int b; cin >> b; for (int i = 1; i <= 300; i ++ ) { auto num = base(i * i, b); if (check(num)) cout << base(i, b) << ' ' << num << endl; } return 0;}</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> acwing </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> 数学 </tag>
</tags>
</entry>
<entry>
<title>贪心</title>
<link href="/2022/03/31/%E7%AE%97%E6%B3%95/acwing/%E8%B4%AA%E5%BF%83/"/>
<url>/2022/03/31/%E7%AE%97%E6%B3%95/acwing/%E8%B4%AA%E5%BF%83/</url>
<content type="html"><![CDATA[<p>贪心算法(英语:greedy algorithm),是用计算机来模拟一个“贪心”的人做出决策的过程。这个人十分贪婪,每一步行动总是按某种指标选取最优的操作。而且他目光短浅,总是只看眼前,并不考虑以后可能造成的影响。</p><p>可想而知,并不是所有的时候贪心法都能获得最优解,所以一般使用贪心法的时候,都要确保自己能证明其正确性。</p><span id="more"></span><p>[TOC]</p><h2 id="贪心"><a href="#贪心" class="headerlink" title="贪心"></a>贪心</h2><h3 id="1660-社交距离-II"><a href="#1660-社交距离-II" class="headerlink" title="1660. 社交距离 II "></a><a href="https://www.acwing.com/problem/content/1662/">1660. 社交距离 II </a></h3><h4 id="题目大意"><a href="#题目大意" class="headerlink" title="题目大意"></a>题目大意</h4><p>在社交距离为R内的牛会被互相感染</p><p>R未知</p><p>要找到最少的母体数量</p><p><strong>输入格式</strong></p><p>输入的第一行包含 N。</p><p>以下 N 行每行用两个整数 x 和 s 描述一头奶牛,其中 x 为位置,s 为 0 表示健康的奶牛,1 表示染病的奶牛,并且所有可能因传播而染病的奶牛均已染病。</p><p><strong>输出格式</strong></p><p>输出在疾病开始传播之前已经得病的奶牛的最小数量。</p><p><strong>数据范围</strong></p><p>1≤N≤1000,</p><p>0≤x≤106</p><pre><code>输入样例:67 11 115 13 110 06 1输出样例:3</code></pre><h4 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h4><p>先找到$R_{min}$,它一定 < $min$({两只牛的距离 | 其中两头牛一头被感染一头未被感染 })</p><p>要想让母体最少,那么就让R尽可能的大,最大取到$R_{min}$</p><p>注:若$R_{min}$的求法是 $R = min$(R,q[i].x - q[i - 1].x),那么$R_{min}$最后求出来的是等于最小的距离,而不是小于</p><p>此时我们要将R - 1</p><h4 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#define x first#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 1010;int n;PII q[N];int main(){ cin >> n; for (int i = 0; i < n; i ++ ) cin >> q[i].x >> q[i].y; sort(q, q + n); int R = 1e8; for (int i = 1; i < n; i ++ ) if (q[i].y != q[i - 1].y) R = min(R, q[i].x - q[i - 1].x); R -- ; int res = 0; for (int i = 0; i < n; i ++ ) if (q[i].y) { int j = i + 1; while (j < n && q[j].y && q[j].x - q[j - 1].x <= R) j ++ ; res ++ ; i = j - 1; } cout << res << endl; return 0;}</code></pre><h3 id="55-连续子数组的最大和"><a href="#55-连续子数组的最大和" class="headerlink" title="55. 连续子数组的最大和"></a><a href="https://www.acwing.com/problem/content/50/">55. 连续子数组的最大和</a></h3><p>输入一个 <strong>非空</strong> 整型数组,数组里的数可能为正,也可能为负。</p><p>数组中一个或连续的多个整数组成一个子数组。</p><p>求所有子数组的和的最大值。</p><p>要求时间复杂度为 O(n)。</p><h4 id="数据范围"><a href="#数据范围" class="headerlink" title="数据范围"></a>数据范围</h4><p>数组长度 [1,1000]。</p><h4 id="样例"><a href="#样例" class="headerlink" title="样例"></a>样例</h4><pre><code>输入:[1, -2, 3, 10, -4, 7, 2, -5]输出:18</code></pre><pre><code class="cpp">class Solution {public: int maxSubArray(vector<int>& nums) { int res = INT_MIN, s= 0; for(auto x : nums){ if(s < 0)s = 0; s += x; res = max(res,s); } return res; }};</code></pre><h3 id="430-纪念品分组"><a href="#430-纪念品分组" class="headerlink" title="430. 纪念品分组"></a><a href="https://www.acwing.com/problem/content/description/432/">430. 纪念品分组</a></h3><p>元旦快到了,校学生会让乐乐负责新年晚会的纪念品发放工作。</p><p>为使得参加晚会的同学所获得的纪念品价值相对均衡,他要把购来的纪念品根据价格进行分组,但每组最多只能包括两件纪念品,并且每组纪念品的价格之和不能超过一个给定的整数。</p><p>为了保证在尽量短的时间内发完所有纪念品,乐乐希望分组的数目最少。</p><p>你的任务是写一个程序,找出所有分组方案中分组数最少的一种,输出最少的分组数目。</p><h4 id="输入格式"><a href="#输入格式" class="headerlink" title="输入格式"></a>输入格式</h4><p>输入文件包含 n+2 行:</p><p>第 1 行包括一个整数 w,为每组纪念品价格之和的上限。</p><p>第 2 行为一个整数 n,表示购来的纪念品的总件数。</p><p>第 3−n+2 行每行包含一个正整数 Pi,表示所对应纪念品的价格。</p><h4 id="输出格式"><a href="#输出格式" class="headerlink" title="输出格式"></a>输出格式</h4><p>输出文件仅一行,包含一个整数,即最少的分组数目。</p><h4 id="数据范围-1"><a href="#数据范围-1" class="headerlink" title="数据范围"></a>数据范围</h4><p>1≤n≤30000,<br>80≤w≤2008,<br>5≤Pi≤w</p><h4 id="输入样例:"><a href="#输入样例:" class="headerlink" title="输入样例:"></a>输入样例:</h4><pre><code>1009902020305060708090</code></pre><h4 id="输出样例:"><a href="#输出样例:" class="headerlink" title="输出样例:"></a>输出样例:</h4><pre><code>6</code></pre><h4 id="分析:"><a href="#分析:" class="headerlink" title="分析:"></a>分析:</h4><p>(贪心,排序,双指针) O(nlogn)<br>直觉上讲,分组的时候应该尽可能让每一组的价值之和大一些。</p><p>由此得到如下算法:</p><ul><li>将所有物品按价值排序;</li><li>从小到大枚举每个物品,每次给当前物品找一个价值尽可能大的且总价值没有超过上限的“同伴物品”,将两个物品分在一组,这一步可以使用双指针算法优化到 O(n)。</li><li>这样求出的组数就是最小值。</li></ul><p>双指针的i,j分别从头和尾走,只走一遍,所以复杂度为O(n)</p><h4 id="代码-1"><a href="#代码-1" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <algorithm>using namespace std;const int N = 30010;int n, m;int w[N];bool st[N];int main(){ cin >> m >> n; for (int i = 0; i < n; i ++ ) cin >> w[i]; sort(w, w + n); int res = 0; for (int i = 0, j = n - 1; i < n; i ++ ) { if (st[i]) continue; while (j >= i && (st[j] || w[i] + w[j] > m)) j -- ; st[i] = st[j] = true; res ++ ; } cout << res << endl; return 0;}</code></pre><h3 id="104-货仓选址"><a href="#104-货仓选址" class="headerlink" title="104. 货仓选址"></a><a href="https://www.acwing.com/problem/content/106/">104. 货仓选址</a></h3><p>在一条数轴上有 N 家商店,它们的坐标分别为 A1∼AN</p><p>现在需要在数轴上建立一家货仓,每天清晨,从货仓到每家商店都要运送一车商品。</p><p>为了提高效率,求把货仓建在何处,可以使得货仓到每家商店的距离之和最小。</p><h4 id="输入格式-1"><a href="#输入格式-1" class="headerlink" title="输入格式"></a>输入格式</h4><p>第一行输入整数 N。</p><p>第二行 N 个整数 A1∼AN</p><h4 id="输出格式-1"><a href="#输出格式-1" class="headerlink" title="输出格式"></a>输出格式</h4><p>输出一个整数,表示距离之和的最小值。</p><h4 id="数据范围-2"><a href="#数据范围-2" class="headerlink" title="数据范围"></a>数据范围</h4><p>1≤N≤100000<br>0≤Ai≤40000</p><h4 id="输入样例:-1"><a href="#输入样例:-1" class="headerlink" title="输入样例:"></a>输入样例:</h4><pre><code>46 2 9 1</code></pre><h4 id="输出样例:-1"><a href="#输出样例:-1" class="headerlink" title="输出样例:"></a>输出样例:</h4><pre><code>12</code></pre><h4 id="思路-1"><a href="#思路-1" class="headerlink" title="思路"></a>思路</h4><ul><li>当n=2的时候,无疑是选在中间任意位置,所以可以排序后两两看成一对,所以要让仓库的左边的商店数量和右面的商店数量相等。</li><li>所以,仓库应该选在中位数的位置。</li></ul><h4 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h4><p>算法1:可以排序</p><p>算法2:用nth_element(),把中位数放在a[n / 2]的位置</p><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 100010;int n;int a[N];int main(){ cin >> n; for (int i = 0; i < n; i ++ )cin >>a[i]; nth_element(a, a + n/2, a + n); int sum = 0; for (int i = 0; i < n; i ++ )sum += abs(a[i] - a[n / 2]); cout << sum << endl; return 0;}</code></pre><h3 id="1478-喝饮料"><a href="#1478-喝饮料" class="headerlink" title="1478 - 喝饮料"></a><a href="https://www.noobdream.com/DreamJudge/Issue/page/1478/#">1478 - 喝饮料</a></h3><h4 id="题目:"><a href="#题目:" class="headerlink" title="题目:"></a>题目:</h4><p>商店里有n中饮料,第i种饮料有mi毫升,价格为wi。</p><p>小明现在手里有x元,他想吃尽量多的饮料,于是向你寻求帮助,怎么样买才能吃的最多。</p><p>请注意,每一种饮料都可以只买一部分。</p><h4 id="输入样例"><a href="#输入样例" class="headerlink" title="输入样例:"></a>输入样例:</h4><pre><code>233 6 6 123 6632 2366 661 58 5-1 -1</code></pre><h4 id="输出样例"><a href="#输出样例" class="headerlink" title="输出样例:"></a>输出样例:</h4><pre><code>136.000</code></pre><h4 id="思路-2"><a href="#思路-2" class="headerlink" title="思路:"></a>思路:</h4><p>先按照单价从小到大排序,然后从小到大买,如果大于价格,则全买,如果小于价格,则能买多少卖多少</p><h4 id="代码:-1"><a href="#代码:-1" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;struct node { double w, m;} p[1005];bool cmp(node a, node b) { return a.w / a.m < b.w / b.m; //按单价从小到大排序}const int N = 10010;int main() { int n, x; while (scanf("%d%d", &x, &n) != EOF) { if (x == -1 && n == -1) break; for (int i = 0; i < n; i++) { scanf("%lf%lf", &p[i].m, &p[i].w); } sort(p, p + n, cmp); double ans = 0; for (int i = 0; i < n; i++) { if (x >= p[i].w) { ans += p[i].m; x -= p[i].w; } else { ans += p[i].m * x / p[i].w; break; } } printf("%.3lf\n", ans); }}</code></pre><h2 id="贪心-区间问题"><a href="#贪心-区间问题" class="headerlink" title="贪心-区间问题"></a>贪心-区间问题</h2><h3 id="905-区间选点"><a href="#905-区间选点" class="headerlink" title="905. 区间选点"></a><a href="https://www.acwing.com/problem/content/907/">905. 区间选点</a></h3><p>给定 N 个闭区间 [ai,bi],请你在数轴上选择尽量少的点,使得每个区间内至少包含一个选出的点。</p><img src="https://raw.githubusercontent.com/zhangchenqi123/imgCloud/main/img/20220305102933.png" alt="image-20220305102933715" style="zoom:50%;" /><p><strong>思路:</strong></p><ol><li>先按照区间右端点排序</li><li>选当前区间的右端点(该点能覆盖更多的其他区间),将能覆盖的区间排除。找到第一个不能覆盖的区间,此时将答案+1,并更新当前区间为此区间。</li></ol><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#define x first#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 100010;int n;PII p[N];bool cmp(pair<int, int>a, pair<int, int>b){ return a.y < b.y;}int main(){ cin >> n; for (int i = 0; i < n; i ++ )cin >> p[i].x >> p[i].y; sort(p, p + n, cmp); int res = 0, ed = -2e9; for (int i = 0; i < n; i ++ ){ if(p[i].x > ed){ ed = p[i].y; res ++; } } cout << res << endl; return 0;}</code></pre><h3 id="908-最大不相交区间数量"><a href="#908-最大不相交区间数量" class="headerlink" title="908. 最大不相交区间数量"></a><a href="https://www.acwing.com/problem/content/910/">908. 最大不相交区间数量</a></h3><p>给定 N 个闭区间 [ai,bi],请你在数轴上选择若干区间,使得选中的区间之间互不相交(包括端点)。</p><p>输出可选取区间的最大数量。</p><p><strong>思路:</strong></p><p>跟问题1完全一样</p><h3 id="906-区间分组"><a href="#906-区间分组" class="headerlink" title="906. 区间分组"></a><a href="https://www.acwing.com/problem/content/908/">906. 区间分组</a></h3><p>给定 N 个闭区间 [ai,bi],请你将这些区间分成若干组,使得每组内部的区间两两之间(包括端点)没有交集,并使得组数尽可能小。</p><p>输出最小组数。</p><p><strong>思路:</strong></p><ol><li>将所有区间的左端点从小到大排序</li><li>从前往后遍历每个区间,判断能否将当前区间放入现有的某个组,即是否有一个组,其中的所有区间的最右端点在当前区间的左端点的左边,即与当前区间没有交集。(其实判断所有组的最右端点的最小的值是否满足即可)<ol><li>如果存在这样的组,则将当前区间放入这个组中,并更新这个组的最右端点</li><li>如果不存在这样的组,则开辟新的组,并将当前区间放入这个组中</li></ol></li></ol><p>可以用一个小根堆来记录所有组的最右端点</p><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#include <queue>#define x first#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 100010;int n;PII p[N];int main(){ cin >> n; for (int i = 0; i < n; i ++ ) cin >> p[i].x >> p[i].y; sort(p, p + n); priority_queue<int,vector<int>, greater<int>> heap; for (int i = 0; i < n; i ++ ) { if(heap.empty() || heap.top() >= p[i].x)heap.push(p[i].y); else{ heap.pop(); heap.push(p[i].y); } } cout << heap.size() << endl; return 0;}</code></pre><h3 id="907-区间覆盖"><a href="#907-区间覆盖" class="headerlink" title="907. 区间覆盖"></a><a href="https://www.acwing.com/problem/content/909/">907. 区间覆盖</a></h3><p>给定 N 个闭区间 [ai,bi] 以及一个线段区间 [s,t],请你选择尽量少的区间,将指定线段区间完全覆盖。</p><p>输出最少区间数,如果无法完全覆盖则输出 −1。</p><p><strong>思路:</strong></p><ol><li>所有的区间按照左端点从小到大排序</li><li>选择所有能覆盖start的区间中右端点最靠右的区间,并更新start和end</li></ol><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#include <queue>#define x first#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 100010;int n;PII p[N];int main(){ int st, end; cin >> st >> end; cin >> n; for (int i = 0; i < n; i ++ ) cin >> p[i].x >> p[i].y; sort(p, p + n); int res = 0; bool success = false; for (int i = 0; i < n; i ++ ){ int j = i, r = -2e9; while(j < n && p[j].x <= st){ r = max(r, p[j].y); j ++; } if(r < st){ break; } res ++; if(r >= end){ success = true; break; } st = r; i = j - 1; } if(!success)res = -1; cout << res << endl; return 0;}</code></pre><h4 id="源代码"><a href="#源代码" class="headerlink" title="源代码"></a><strong>源代码</strong></h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#define x first#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 110;int n;PII p[N];int main(){ cin >> n; for (int i = 0; i < n; i ++ )cin >> p[i].x >> p[i].y; sort(p, p + n); int res = 0; //枚举删除的区间i for(int i = 0; i < n; i ++){ //sum是去掉第i个区间后,剩余区间最大的覆盖范围 //st是枚举到当合并区间的左端点,ed是枚举到当前合并区间的右端点 int sum = 0, st = -1, ed = -1; for(int j = 0; j < n;j ++){ if(j != i){ //当前j区间的左端点在当前融合区间的右端点的左边,则可以融合j区间,更新当前融合区间的右端点 if(p[j].x <= ed) ed = max(ed, p[j].y); //否则开辟一个新的融合区间,并将上一个融合区间的覆盖范围加入到sum里 else{ sum += ed - st; st = p[j].x, ed = p[j].y; } } } //加上最后一个融合区间 sum += ed - st; res = max(res, sum); } cout << res << endl; return 0;}</code></pre><h3 id="110-防晒"><a href="#110-防晒" class="headerlink" title="110. 防晒"></a><a href="https://www.acwing.com/problem/content/112/">110. 防晒</a></h3><p>有 CC 头奶牛进行日光浴,第 ii 头奶牛需要 minSPF[i]minSPF[i] 到 maxSPF[i]maxSPF[i] 单位强度之间的阳光。</p><p>每头奶牛在日光浴前必须涂防晒霜,防晒霜有 LL 种,涂上第 ii 种之后,身体接收到的阳光强度就会稳定为 SPF[i]SPF[i],第 ii 种防晒霜有 cover[i]cover[i] 瓶。</p><p>求最多可以满足多少头奶牛进行日光浴。</p><h4 id="输入格式-2"><a href="#输入格式-2" class="headerlink" title="输入格式"></a>输入格式</h4><p>第一行输入整数 C 和 L。</p><p>接下来的 C 行,按次序每行输入一头牛的 minSPF 和 maxSPF 值,即第 i 行输入 minSPF[i] 和 maxSPF[i]。</p><p>再接下来的 L 行,按次序每行输入一种防晒霜的 SPF 和 cover 值,即第 i 行输入 SPF[i] 和 cover[i]。</p><p>每行的数据之间用空格隔开。</p><h4 id="输出格式-2"><a href="#输出格式-2" class="headerlink" title="输出格式"></a>输出格式</h4><p>输出一个整数,代表最多可以满足奶牛日光浴的奶牛数目。</p><h4 id="数据范围-3"><a href="#数据范围-3" class="headerlink" title="数据范围"></a>数据范围</h4><p>1≤C,L≤2500<br>1≤minSPF≤maxSPF≤1000<br>1≤SPF≤1000</p><h4 id="输入样例:-2"><a href="#输入样例:-2" class="headerlink" title="输入样例:"></a>输入样例:</h4><pre><code>3 23 102 51 56 24 1</code></pre><h4 id="输出样例:-2"><a href="#输出样例:-2" class="headerlink" title="输出样例:"></a>输出样例:</h4><pre><code>2</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> acwing </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> 贪心 </tag>
</tags>
</entry>
<entry>
<title>双指针</title>
<link href="/2022/03/31/%E7%AE%97%E6%B3%95/acwing/%E5%8F%8C%E6%8C%87%E9%92%88/"/>
<url>/2022/03/31/%E7%AE%97%E6%B3%95/acwing/%E5%8F%8C%E6%8C%87%E9%92%88/</url>
<content type="html"><![CDATA[<p>通过快慢指针减少重复遍历。</p><span id="more"></span><p>[TOC]</p><h2 id="双指针"><a href="#双指针" class="headerlink" title="双指针"></a>双指针</h2><h3 id="782-避嫌抢劫"><a href="#782-避嫌抢劫" class="headerlink" title="782. 避嫌抢劫"></a><a href="https://www.acwing.com/problem/content/description/784/">782. 避嫌抢劫</a></h3><p>小镇沿街分布(可以理解为都在数轴上),有 n 家银行(位置以数轴的坐标表示,金额表示可以被抢走的金额)。</p><p>两个绑匪试图分别抢劫一个银行,为了让警方多奔波他们商定选择的两个银行距离不小于 d。</p><p>请问,符合约定的情况下他们能抢到的总金额最大是多少。</p><h4 id="输入格式"><a href="#输入格式" class="headerlink" title="输入格式"></a>输入格式</h4><p>输入包含 n+1 行。</p><p>第一行包含两个整数 n 和 d,分别表示银行的数量和约定的距离。</p><p>接下来 n 行,每行包含两个整数 a 和 b,分别表示坐标和金额。</p><h4 id="输出格式"><a href="#输出格式" class="headerlink" title="输出格式"></a>输出格式</h4><p>输出一个数字表示可以获得的最大金额。</p><h4 id="数据范围"><a href="#数据范围" class="headerlink" title="数据范围"></a>数据范围</h4><p>1≤n≤2×105,<br>1≤d,a,b≤108</p><p><strong>注意</strong>:数据中保证至少存在两个银行之间的距离不小于 d。</p><h4 id="输入样例:"><a href="#输入样例:" class="headerlink" title="输入样例:"></a>输入样例:</h4><pre><code>6 31 13 54 86 410 311 2</code></pre><h4 id="输出样例:"><a href="#输出样例:" class="headerlink" title="输出样例:"></a>输出样例:</h4><pre><code>11</code></pre><h4 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h4><p>对每个银行按照坐标从小到大排序,然后双指针算法从前往后遍历</p><p>对于一个特定的<code>i</code>,假设<code>j</code>指向<code>i</code>之后的第一个和<code>i</code>相隔大于<code>d</code>的位置,那么<code>j</code>及<code>j</code>之后的银行都是可以被抢劫的,此时我们从<code>j</code>即<code>j</code>之后的银行中挑一家钱数最多的银行抢劫,受益最大,即:答案应该更新为:$res = max(res, a[i].y + M)$, 其中M的表达式:$M = max(a[k].y), k = j, j + 1, … ,n -1$</p><p>如果我们对于每一个<code>i</code>,都要遍历找<code>j</code>及<code>j</code>之后的钱数最多的银行,那么时间复杂度为$O(n^2)$,会超时</p><p>所以我们可以开一个“后缀最大值”数组<code>b</code>,其中<code>b[j]</code>表示:<code>j</code>及<code>j</code>之后的银行中钱数的最大值。</p><h4 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#define x first#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 200010;int n, d;PII a[N];int b[N];int main(){ cin >> n >> d; for (int i = 0; i < n; i ++ )cin >> a[i].x >> a[i].y; int res = 0; sort(a, a + n); int bm = 0;//behind_max for(int i = n - 1; i >= 0; i --){ bm = max(bm, a[i].y); b[i] = bm; } for(int i = 0, j = 1; i < n; i ++){ while(j < n && a[j].x - a[i].x < d)j ++; if(j >= n)break; res = max(res, a[i].y + b[j]); } cout << res << endl; return 0;}</code></pre><h3 id="61-最长不含重复字符的子字符串"><a href="#61-最长不含重复字符的子字符串" class="headerlink" title="61. 最长不含重复字符的子字符串"></a><a href="https://www.acwing.com/problem/content/57/">61. 最长不含重复字符的子字符串</a></h3><p>请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。</p><p>假设字符串中只包含从 <code>a</code> 到 <code>z</code> 的字符。</p><h4 id="数据范围-1"><a href="#数据范围-1" class="headerlink" title="数据范围"></a>数据范围</h4><p>输入字符串长度 [0,1000]。</p><h4 id="样例"><a href="#样例" class="headerlink" title="样例"></a>样例</h4><pre><code>输入:"abcabc"输出:3</code></pre><h4 id="思路:-1"><a href="#思路:-1" class="headerlink" title="思路:"></a>思路:</h4><ul><li>用哈希表存储两个指针之间的每个字母出现的次数</li><li>每次把j向后移动一个,同时检查最后一个字母出现的次数,若大于1,则把i往后移,直到最后一个字母出现的次数为1.</li><li><strong>以后指针为标准进行遍历</strong></li></ul><h4 id="代码:-1"><a href="#代码:-1" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">class Solution {public: int longestSubstringWithoutDuplication(string s) { unordered_map<char,int> hash; int res = 0; for(int i = 0,j = 0;j < s.size();j ++){ if(++hash[s[j]] > 1){ //就像j对前面的i说,你跟我中间有个人跟我重复了!你赶紧给我找出来踢出去! //i也不知道哪个人重复了,就逐个往后找,每退一步就问一下j,现在还有重复吗 //j如果说没有了,那j就可以继续往后了,j要说还有,i就只能继续往后退 while(i < j){ hash[s[i ++]] --; if(hash[s[j]] == 1)break; } } res = max(res,j - i + 1); } return res; }};</code></pre><h3 id="76-和为S的连续正数序列"><a href="#76-和为S的连续正数序列" class="headerlink" title="76. 和为S的连续正数序列"></a><a href="https://www.acwing.com/problem/content/72/">76. 和为S的连续正数序列</a></h3><p>输入一个非负整数 S,打印出所有和为 S 的连续正数序列(至少含有两个数)。</p><p>例如输入 15,由于 1+2+3+4+5=4+5+6=7+8=15,所以结果打印出 3 个连续序列 1∼5、4∼6 和 7∼8。</p><h4 id="数据范围-2"><a href="#数据范围-2" class="headerlink" title="数据范围"></a>数据范围</h4><p>0≤S≤1000</p><h4 id="样例-1"><a href="#样例-1" class="headerlink" title="样例"></a>样例</h4><pre><code>输入:15输出:[[1,2,3,4,5],[4,5,6],[7,8]]</code></pre><h4 id="思路:-2"><a href="#思路:-2" class="headerlink" title="思路:"></a>思路:</h4><ul><li><p>s中存储:以i开头的,总和不超过目标值S的,最长的连续子数组的和。</p></li><li><p>i,j具有单调性:当i向后移的时候,由于总和减少,j一定不会向前移。</p></li><li><p><strong>以前指针i为标准进行遍历</strong></p></li><li><p><strong>数字和的问题也可以联想前缀和!</strong></p></li></ul><h4 id="代码:-2"><a href="#代码:-2" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">class Solution {public: vector<vector<int> > findContinuousSequence(int sum) { vector<vector<int>> res; for(int i = 1, j = 1, s = 1;i <= sum/2; i ++){ while(s < sum)s += ++j; if(s == sum && j > i){ vector<int> line; for(int k = i;k <= j; k ++)line.push_back(k); res.push_back(line); } s -= i; } return res; }};</code></pre><h3 id="77-翻转单词顺序"><a href="#77-翻转单词顺序" class="headerlink" title="77. 翻转单词顺序"></a><a href="https://www.acwing.com/problem/content/73/">77. 翻转单词顺序</a></h3><p>输入一个英文句子,<strong>单词之间用一个空格隔开,且句首和句尾没有多余空格</strong>。</p><p>翻转句子中单词的顺序,但单词内字符的顺序不变。</p><p>为简单起见,标点符号和普通字母一样处理。</p><p>例如输入字符串<code>"I am a student."</code>,则输出<code>"student. a am I"</code>。</p><h4 id="数据范围-3"><a href="#数据范围-3" class="headerlink" title="数据范围"></a>数据范围</h4><p>输入字符串长度 、[0,1000]。</p><h4 id="样例-2"><a href="#样例-2" class="headerlink" title="样例"></a>样例</h4><pre><code>输入:"I am a student."输出:"student. a am I"</code></pre><h4 id="思路:-3"><a href="#思路:-3" class="headerlink" title="思路:"></a>思路:</h4><ul><li>首先反转整个串,然后再逐个反转每个单词。</li><li>注意reverse函数反转单词:reverse(单词的第一个字母,单词后面的空格)</li></ul><h4 id="代码"><a href="#代码" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">class Solution {public: string reverseWords(string s) { reverse(s.begin(),s.end()); for(int i = 0; i < s.size(); i ++){ int j = i; while(j < s.size() && s[j] != ' ')j ++; reverse(s.begin() + i, s.begin() + j); i = j; } return s; }};</code></pre><h2 id="双指针-子数组"><a href="#双指针-子数组" class="headerlink" title="双指针+子数组"></a>双指针+子数组</h2><h3 id="3801-最佳连续子数组"><a href="#3801-最佳连续子数组" class="headerlink" title="3801. 最佳连续子数组"></a><a href="https://www.acwing.com/problem/content/3804/">3801. 最佳连续子数组</a></h3><p><strong>标签:双指针、子数组</strong></p><p>给定一个长度为 n 的数组 a1,a2,…,an。</p><p>请你找到其中的最佳<strong>连续</strong>子数组。</p><p>最佳连续子数组需满足:</p><ol><li>子数组内各元素的算术平均数(即所有元素之和除以元素个数)尽可能大。</li><li>满足条件 1 的前提下,子数组的长度尽可能长。</li></ol><p>输出最佳连续子数组的长度。</p><h4 id="分析:"><a href="#分析:" class="headerlink" title="分析:"></a>分析:</h4><p>子区间长度平均值最大值:就是最大的数</p><p>等于最大值的元素有多个,并且相邻,则这个最大值子区间的长度就是所求长度。</p><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 100010;int n, t;int a[N];int main(){ cin >> t; while(t -- ){ int n; cin >> n; int mx = 0; for(int i = 0; i < n; i ++){ cin >> a[i]; mx = max(mx, a[i]); } int res = 0; for(int i = 0; i < n; i ++){ if(a[i] == mx){ int j = i + 1; while(j < n && a[j] == a[i])j ++; res = max(res, j - i); i = j - 1; } } cout << res << endl; } return 0;}</code></pre><h4 id="总结:"><a href="#总结:" class="headerlink" title="总结:"></a>总结:</h4><p>双指针、子数组问题,用i从前往后遍历,当i遍历到符合条件的时候,进入if语句:出现j,j = i + 1,当退出if语句的时候,i = j - 1:</p><pre><code class="cpp">for(int i = 0; i < n;i ++){ if(a[i] ...){ int j = i + 1; while(j < n && a[j] ..)j ++ ...; i = j - 1; }}</code></pre><p>同样,如下题:</p><h3 id="2074-倒计数"><a href="#2074-倒计数" class="headerlink" title="2074. 倒计数"></a><a href="https://www.acwing.com/problem/content/2076/">2074. 倒计数</a></h3><p>艾弗里有一个由 N 个正整数构成的数组。</p><p>数组中的第 i 个整数是 Ai。</p><p>如果一个连续的子数组的长度为 m,并且按顺序包含整数 m,m−1,m−2,…,2,1,则称它为 m 倒计数。</p><p>例如,[3,2,1] 是 3 倒计数。</p><p>请帮助艾弗里计算她的数组中有多少个 K 倒计数。</p><p>例如,输入:</p><pre><code>12 3//长度12的数组里,有几个3倒计数?1 2 3 7 9 3 2 1 8 3 2 1</code></pre><p>输出:</p><pre><code class="cpp">2</code></pre><p>解答:</p><pre><code class="cpp">cin >> n >> k;for(int i = 0; i < n; i ++){ cin >> a[i];}for(int i = 0; i < n; i ++){ if(a[i] == k){ int j = i + 1; int k_ = k - 1; while(j < n && a[j] == k_ && k_){ k_ --, j ++; if(k_ == 0){ res ++; break; } } i = j - 1; }}cout << res << endl;</code></pre><h2 id="双指针-前缀和"><a href="#双指针-前缀和" class="headerlink" title="双指针+前缀和"></a>双指针+前缀和</h2><h3 id="1922-懒惰的牛"><a href="#1922-懒惰的牛" class="headerlink" title="1922. 懒惰的牛"></a><a href="https://www.acwing.com/problem/content/description/1924/">1922. 懒惰的牛</a></h3><h4 id="题目大意:"><a href="#题目大意:" class="headerlink" title="题目大意:"></a>题目大意:</h4><p>在一个数轴上,若干点上有草堆,问在某个固定长的区域内,草堆的最大数量是多少</p><h4 id="思路:-4"><a href="#思路:-4" class="headerlink" title="思路:"></a>思路:</h4><p>求某个区间内的和,我们可以联想到:<strong>双指针(滑动窗口)算法</strong>、<strong>前缀和算法</strong>、<strong>差分算法</strong></p><h4 id="代码:-3"><a href="#代码:-3" class="headerlink" title="代码:"></a>代码:</h4><p><strong>双指针:</strong></p><p>以后指针i为基准</p><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#define x first#define y secondusing namespace std;const int N = 100010;typedef pair<int, int> PII;int n, m;PII q[N];int main(){ cin >> n >> m; for (int i = 0; i < n; i ++ )cin >> q[i].y >> q[i].x; sort(q, q + n); int res = 0, sum = 0; for (int i = 0, j = 0; i < n; i ++ ){ //i向后移 sum += q[i].y; //当第i堆草和第j堆草的间隔距离大于2*m时,将j向后移动,同时将sum减去j原来的草堆数量 while(q[i].x - q[j].x > 2 * m) sum -= q[j ++].y; res = max(res, sum); } cout << res << endl; return 0;}</code></pre><p><strong>前缀和:</strong></p><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 1000010; // x 的范围int n, k;int s[N];int main(){ cin >> n >> k; for (int i = 0; i < n; i ++ ){ int x, c; cin >> c >> x; s[x + 1] += c; //算前缀和的时候,数组下标从1开始 } for(int i = 1; i < N; i ++)s[i] += s[i - 1]; int res = -1; for(int i = 1; i < N; i ++){ int l = max(1, i - k), r = min(i + k, N - 1); res = max(res, s[r] - s[l - 1]); } cout << res << endl; return 0;}</code></pre><p><strong>差分:</strong></p><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>#include <map>using namespace std;const int N = 1e6+10;map<int,int> b;//构造差分数组,原数组每个位置初始为0,有草的位置x可以影响到x-k和x+k范围内的点,//相当于给[x-k,x+k]范围内的点都加上了草的数量int main(){ int n,k; cin>>n>>k; for(int i=0;i<n;i++) { int x,y; cin>>x>>y; b[y-k]+=x,b[y+k+1]-=x; } int res=0,sum=0; for(auto x:b) { sum+=x.second; res=max(res,sum); } cout<<res<<endl; return 0;}</code></pre><h3 id="102-最佳牛围栏"><a href="#102-最佳牛围栏" class="headerlink" title="102. 最佳牛围栏 "></a><a href="https://www.acwing.com/problem/content/104/">102. 最佳牛围栏 </a></h3><p>农夫约翰的农场由 N 块田地组成,每块地里都有一定数量的牛,其数量不会少于 1 头,也不会超过 2000 头。</p><p>约翰希望用围栏将一部分连续的田地围起来,并使得围起来的区域内每块地包含的牛的数量的平均值达到最大。</p><p>围起区域内至少需要包含 F 块地,其中 F 会在输入中给出。</p><p>在给定条件下,计算围起区域内每块地包含的牛的数量的平均值可能的最大值是多少。</p><h4 id="输入格式-1"><a href="#输入格式-1" class="headerlink" title="输入格式"></a>输入格式</h4><p>第一行输入整数 N 和 F,数据间用空格隔开。</p><p>接下来 N 行,每行输入一个整数,第i+1 行输入的整数代表第 i 片区域内包含的牛的数目。</p><h4 id="输出格式-1"><a href="#输出格式-1" class="headerlink" title="输出格式"></a>输出格式</h4><p>输出一个整数,表示平均值的最大值乘以 1000 再 <strong>向下取整</strong> 之后得到的结果。</p><h4 id="数据范围-4"><a href="#数据范围-4" class="headerlink" title="数据范围"></a>数据范围</h4><p>1≤N≤100000<br>1≤F≤N</p><h4 id="输入样例:-1"><a href="#输入样例:-1" class="headerlink" title="输入样例:"></a>输入样例:</h4><pre><code>10 66 4210385941</code></pre><h4 id="输出样例:-1"><a href="#输出样例:-1" class="headerlink" title="输出样例:"></a>输出样例:</h4><pre><code>6500</code></pre><h4 id="思路:-5"><a href="#思路:-5" class="headerlink" title="思路:"></a>思路:</h4><ol><li>由于平均值的最大值具有二段性:小于等于最大值的可能出现,大于最大值的都不可能出现,因此可以用二分法</li><li>二分的check(avg)函数:检查是否存在一个区间,其平均值大于avg。<ol><li>某个区间的平均值大于avg,相当于区间内的所有数都-avg,然后看总和是否大于0.</li><li>区间和的问题,用前缀和。</li><li>是否存在区间[i, j],区间和大于0,sum[j] - sum[i] >= 0,相当于sum[j] >= sum[i]是否有解,那么我们可以让sum[i]尽可能的小,可以用一个变量来存储sum[i]在遍历过程中的最小值。</li></ol></li></ol><h4 id="代码:-4"><a href="#代码:-4" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 100010;int n, f;int a[N];double sum[N];bool check(double avg){ for (int i = 1; i <= n; i ++ )sum[i] = sum[i - 1] + a[i] - avg; double mi = 0; for(int i = 0, j = f; j <= n; i ++, j ++) { mi = min(mi, sum[i]); if(sum[j] >= mi)return true; } return false;} int main(){ cin >> n >> f; for (int i = 1; i <= n; i ++ ){ cin >> a[i]; } double l = 0, r = 2000; while(r - l > 1e-5){ double mid = (l + r) / 2; if(check(mid))l = mid; else r = mid; } cout << int(r * 1000) << endl; return 0; }</code></pre><h3 id="1528-火星购物"><a href="#1528-火星购物" class="headerlink" title="1528. 火星购物 "></a><a href="https://www.acwing.com/problem/content/description/1530/">1528. 火星购物 </a></h3><p>在火星购物是一个完全不同的体验。</p><p>火星人用被链子连在一起的钻石付款,每颗钻石都有一定的价值。</p><p>付款时,只能在链条的某一位置上切割一次,然后一些钻石就会一个接着一个的从链条上脱落。</p><p>一旦钻石脱离链条,就不能在穿回去。</p><p>例如,如果我们有一个钻石链条,上面有 8 颗钻石,价值分别为 3,2,1,5,4,6,8,7 元,而我们需要支付的价格为 15 元。</p><p>我们有三种选择:</p><ol><li>在 4 和 6 之间剪断链条,然后取走前 5 颗钻石,价值为 3+2+1+5+4=15。</li><li>在 5 之前或 6 之后剪断链条,然后取走第 4∼6 颗钻石,价值为 5+4+6=15。</li><li>在 8 之前剪断链条,然后取走后 2 颗钻石,价值为 8+7=15。</li></ol><p>现在给定钻石链条上钻石的价值以及顾客需要支付的价钱,请你帮助顾客确定他有多少种选择方案。</p><p>如果无法恰好凑出顾客需要支付的确切金额,那就凑出足够支付的情况下的最少金额,从而避免钻石浪费。</p><h4 id="输入格式-2"><a href="#输入格式-2" class="headerlink" title="输入格式"></a>输入格式</h4><p>第一行包含两个整数 N 和 M,分别表示链条上的钻石数量以及顾客需要支付的金额。</p><p>第二行包含 N 个整数,D1,D2,…,DN,表示钻石的价值。</p><h4 id="输出格式-2"><a href="#输出格式-2" class="headerlink" title="输出格式"></a>输出格式</h4><p>每行输出一种可行方案,具体格式为 <code>i-j</code>,表示 Di+…+Dj=M</p><p>如果方案不唯一,则按照 i 递增的顺序输出所有方案。</p><p>如果无法准确凑出 M,仍然输出 <code>i-j</code>,表示 Di+…+Dj>M 并且 Di+…+Dj−M的值最小。</p><p>如果方案不唯一,则按照 i递增的顺序输出所有方案。</p><h4 id="数据范围-5"><a href="#数据范围-5" class="headerlink" title="数据范围"></a>数据范围</h4><p>1≤N≤105<br>1≤M≤108<br>1≤Di≤103<br>∑Di≥M</p><h4 id="输入样例1:"><a href="#输入样例1:" class="headerlink" title="输入样例1:"></a>输入样例1:</h4><pre><code>16 153 2 1 5 4 6 8 7 16 10 15 11 9 12 14 13</code></pre><h4 id="输出样例1:"><a href="#输出样例1:" class="headerlink" title="输出样例1:"></a>输出样例1:</h4><pre><code>1-54-67-811-11</code></pre><h4 id="输入样例2:"><a href="#输入样例2:" class="headerlink" title="输入样例2:"></a>输入样例2:</h4><pre><code>5 132 4 5 7 9</code></pre><h4 id="输出样例2:"><a href="#输出样例2:" class="headerlink" title="输出样例2:"></a>输出样例2:</h4><pre><code>2-44-5</code></pre><h4 id="非前缀和做法:"><a href="#非前缀和做法:" class="headerlink" title="非前缀和做法:"></a>非前缀和做法:</h4><ul><li>s中存储的是:以i开头的,j结尾的区间和</li><li><strong>注意,第二次循环的时候要先将s归零!!!</strong></li></ul><h4 id="代码-1"><a href="#代码-1" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 100010;int n, m;int a[N];int main(){ cin >> n >> m; for(int i = 1; i <= n; i ++){ cin >> a[i]; } int s = 0; int mins = 1e9; bool flag = false;//false为无精确解 for(int i = 1, j = 0; j <= n && i <= n;s -= a[i ++]){ while(s < m){ s += a[++ j]; if(j > n)break; } if(s == m){ cout << i << '-' << j << endl; flag = true; }if(s > m){ mins = min(mins, s); } } if(!flag){ s = 0; for(int i = 1, j = 0; j <= n && i <= n;s -= a[i ++]){ while(s < m){ s += a[++ j]; if(j > n)break; } if(s == mins){ cout << i << '-' << j << endl; } } } return 0;}</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> acwing </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> 双指针 </tag>
</tags>
</entry>
<entry>
<title>BFS</title>
<link href="/2022/03/31/%E7%AE%97%E6%B3%95/acwing/BFS/"/>
<url>/2022/03/31/%E7%AE%97%E6%B3%95/acwing/BFS/</url>
<content type="html"><![CDATA[<p>BFS 全称是 Breadth First Search,中文名是宽度优先搜索,也叫广度优先搜索。</p><p>是图上最基础、最重要的搜索算法之一。</p><p>所谓宽度优先。就是每次都尝试访问同一层的节点。 如果同一层都访问完了,再访问下一层。</p><p>这样做的结果是,BFS 算法找到的路径是从起点开始的 最短 合法路径。换言之,这条路径所包含的边数最小。</p><p>在 BFS 结束时,每个节点都是通过从起点到该点的最短路径访问的。</p><p>算法过程可以看做是图上火苗传播的过程:最开始只有起点着火了,在每一时刻,有火的节点都向它相邻的所有节点传播火苗。</p><span id="more"></span><p>[TOC]</p><h2 id="BFS"><a href="#BFS" class="headerlink" title="BFS"></a>BFS</h2><h3 id="图中点的层次遍历"><a href="#图中点的层次遍历" class="headerlink" title="图中点的层次遍历"></a>图中点的层次遍历</h3><pre><code class="cpp">queue<int> q;st[1] = true; // 表示1号点已经被遍历过q.push(1);while (q.size()){ int t = q.front(); q.pop(); for (int i = h[t]; i != -1; i = ne[i]) { int j = e[i]; if (!st[j]) { st[j] = true; // 表示点j已经被遍历过 q.push(j); } }}</code></pre><h3 id="844-走迷宫"><a href="#844-走迷宫" class="headerlink" title="844. 走迷宫"></a><a href="https://www.acwing.com/problem/content/846/">844. 走迷宫</a></h3><h4 id="代码"><a href="#代码" class="headerlink" title="代码"></a>代码</h4><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>#include <queue>using namespace std;typedef pair<int, int> PII;const int N = 110;int n, m;int g[N][N], d[N][N];int bfs(){ queue<PII> q; memset(d, -1, sizeof d); d[0][0] = 0; q.push({0, 0}); int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; while (q.size()) { auto t = q.front(); q.pop(); for (int i = 0; i < 4; i ++ ) { int x = t.first + dx[i], y = t.second + dy[i]; if (x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1) { d[x][y] = d[t.first][t.second] + 1; q.push({x, y}); } } } return d[n - 1][m - 1];}int main(){ cin >> n >> m; for (int i = 0; i < n; i ++ ) for (int j = 0; j < m; j ++ ) cin >> g[i][j]; cout << bfs() << endl; return 0;}</code></pre><h3 id="846-树的重心"><a href="#846-树的重心" class="headerlink" title="846. 树的重心"></a><a href="https://www.acwing.com/problem/content/848/">846. 树的重心</a></h3><h4 id="题目:"><a href="#题目:" class="headerlink" title="题目:"></a>题目:</h4><p>给定一颗树,树中包含 n 个结点(编号 1∼n)和 n−1 条无向边。</p><p>请你找到树的重心,并输出将重心删除后,剩余各个连通块中点数的最大值。</p><p>重心定义:重心是指树中的一个结点,如果将这个点删除后,剩余各个连通块中点数的最大值最小,那么这个节点被称为树的重心。</p><h4 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h4><p>dfs(i)指的是以i为根节点的子树的节点数之和</p><h4 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 100010, M = N * 2;int n;int h[N], e[M], ne[M], idx;int ans = N;bool st[N];void add(int a, int b){ e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;}int dfs(int u){ st[u] = true; int size = 0, sum = 0; for (int i = h[u]; i != -1; i = ne[i]) { int j = e[i]; if (st[j]) continue; int s = dfs(j);//S是以j为根结点的子树的节点之和 size = max(size, s); sum += s; }//循环结束后,size是u点的所有子树的最大节点数,sum是所有子树节点之和 size = max(size, n - sum - 1); ans = min(ans, size); return sum + 1;}int main(){ scanf("%d", &n); memset(h, -1, sizeof h); for (int i = 0; i < n - 1; i ++ ) { int a, b; scanf("%d%d", &a, &b); add(a, b), add(b, a); } dfs(1); printf("%d\n", ans); return 0;}</code></pre><h3 id="847-图中点的层次"><a href="#847-图中点的层次" class="headerlink" title="847. 图中点的层次"></a><a href="https://www.acwing.com/problem/content/849/">847. 图中点的层次</a></h3><p>给定一个 n 个点 m 条边的有向图,图中可能存在重边和自环。</p><p>所有边的长度都是 1,点的编号为 1∼n。</p><p>请你求出 1 号点到 n 号点的最短距离,如果从 1 号点无法走到 n 号点,输出 −1。</p><pre><code class="cpp">#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>#include <queue>using namespace std;const int N = 100010; int n, m;int h[N], e[N], ne[N], idx;int d[N];void add(int a, int b){ e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ;}//q,d,里面存储的都是节点的e,只有拉链里面存储的是节点的idxint bfs(){ memset(d, -1, sizeof d); queue<int> q; d[1] = 0; q.push(1); while (q.size()) { int t = q.front(); q.pop(); for (int i = h[t]; i != -1; i = ne[i]) { int j = e[i]; if (d[j] == -1) { d[j] = d[t] + 1; q.push(j); } } } return d[n];}int main(){ scanf("%d%d", &n, &m); memset(h, -1, sizeof h); for (int i = 0; i < m; i ++ ) { int a, b; scanf("%d%d", &a, &b); add(a, b); } cout << bfs() << endl; return 0;}</code></pre><h2 id="Flood-Fill"><a href="#Flood-Fill" class="headerlink" title="Flood Fill"></a>Flood Fill</h2><h3 id="1097-池塘计数"><a href="#1097-池塘计数" class="headerlink" title="1097. 池塘计数"></a><a href="https://www.acwing.com/problem/content/description/1099/">1097. 池塘计数</a></h3><h4 id="题目:-1"><a href="#题目:-1" class="headerlink" title="题目:"></a>题目:</h4><p>八相连,问有几个连通块</p><h4 id="思路:-1"><a href="#思路:-1" class="headerlink" title="思路:"></a>思路:</h4><p>计算调用了几次BFS</p><h4 id="代码:-1"><a href="#代码:-1" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>#define x first#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 1010, M = N * N;int n, m;char g[N][N];PII q[M];bool st[N][N];void bfs(int sx, int sy){ int hh = 0, tt = 0; q[0] = {sx, sy}; st[sx][sy] = true; while (hh <= tt) { PII t = q[hh ++ ]; for (int i = t.x - 1; i <= t.x + 1; i ++ ) for (int j = t.y - 1; j <= t.y + 1; j ++ ) { if (i == t.x && j == t.y) continue; if (i < 0 || i >= n || j < 0 || j >= m) continue; if (g[i][j] == '.' || st[i][j]) continue; q[ ++ tt] = {i, j}; st[i][j] = true; } }}int main(){ scanf("%d%d", &n, &m); for (int i = 0; i < n; i ++ ) scanf("%s", g[i]); int cnt = 0; for (int i = 0; i < n; i ++ ) for (int j = 0; j < m; j ++ ) if (g[i][j] == 'W' && !st[i][j]) { bfs(i, j); cnt ++ ; } printf("%d\n", cnt); return 0;}</code></pre><h3 id="1098-城堡问题"><a href="#1098-城堡问题" class="headerlink" title="1098. 城堡问题"></a><a href="https://www.acwing.com/problem/content/description/1100/">1098. 城堡问题</a></h3><h4 id="题目:-2"><a href="#题目:-2" class="headerlink" title="题目:"></a>题目:</h4><p>问有几个屋,最大的屋的面积是多少</p><h4 id="思路:-2"><a href="#思路:-2" class="headerlink" title="思路:"></a>思路:</h4><h4 id="代码:-2"><a href="#代码:-2" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>#define x first#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 55, M = N * N;int n, m;int g[N][N];PII q[M];bool st[N][N];int bfs(int sx, int sy){ int dx[4] = {0, -1, 0, 1}, dy[4] = {-1, 0, 1, 0}; int hh = 0, tt = 0; int area = 0; q[0] = {sx, sy}; st[sx][sy] = true; while (hh <= tt) { PII t = q[hh ++ ]; area ++ ; for (int i = 0; i < 4; i ++ ) { int a = t.x + dx[i], b = t.y + dy[i]; if (a < 0 || a >= n || b < 0 || b >= m) continue; if (st[a][b]) continue; if (g[t.x][t.y] >> i & 1) continue; q[ ++ tt] = {a, b}; st[a][b] = true; } } return area;}int main(){ cin >> n >> m; for (int i = 0; i < n; i ++ ) for (int j = 0; j < m; j ++ ) cin >> g[i][j]; int cnt = 0, area = 0; for (int i = 0; i < n; i ++ ) for (int j = 0; j < m; j ++ ) if (!st[i][j]) { area = max(area, bfs(i, j)); cnt ++ ; } cout << cnt << endl; cout << area << endl; return 0;}</code></pre><h3 id="1106-山峰和山谷"><a href="#1106-山峰和山谷" class="headerlink" title="1106. 山峰和山谷"></a><a href="https://www.acwing.com/problem/content/description/1108/">1106. 山峰和山谷</a></h3><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>#define x first#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 1010, M = N * N;int n;int h[N][N];PII q[M];bool st[N][N];void bfs(int sx, int sy, bool& has_higher, bool& has_lower){ int hh = 0, tt = 0; q[0] = {sx, sy}; st[sx][sy] = true; while (hh <= tt) { PII t = q[hh ++ ]; for (int i = t.x - 1; i <= t.x + 1; i ++ ) for (int j = t.y - 1; j <= t.y + 1; j ++ ) { if (i == t.x && j == t.y) continue; if (i < 0 || i >= n || j < 0 || j >= n) continue; if (h[i][j] != h[t.x][t.y]) // 山脉的边界 { if (h[i][j] > h[t.x][t.y]) has_higher = true; else has_lower = true; } else if (!st[i][j]) { q[ ++ tt] = {i, j}; st[i][j] = true; } } }}int main(){ scanf("%d", &n); for (int i = 0; i < n; i ++ ) for (int j = 0; j < n; j ++ ) scanf("%d", &h[i][j]); int peak = 0, valley = 0; for (int i = 0; i < n; i ++ ) for (int j = 0; j < n; j ++ ) if (!st[i][j]) { bool has_higher = false, has_lower = false; bfs(i, j, has_higher, has_lower); if (!has_higher) peak ++ ; if (!has_lower) valley ++ ; } printf("%d %d\n", peak, valley); return 0;}</code></pre><h2 id="最短路径问题"><a href="#最短路径问题" class="headerlink" title="最短路径问题"></a>最短路径问题</h2><h3 id="1076-迷宫问题"><a href="#1076-迷宫问题" class="headerlink" title="1076. 迷宫问题"></a><a href="https://www.acwing.com/problem/content/description/1078/">1076. 迷宫问题</a></h3><h4 id="题目:-3"><a href="#题目:-3" class="headerlink" title="题目:"></a>题目:</h4><p>迷宫问题,求最短路径的长度并输出</p><h4 id="思路:-3"><a href="#思路:-3" class="headerlink" title="思路:"></a>思路:</h4><h4 id="代码:-3"><a href="#代码:-3" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>#define x first#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 1010, M = N * N;int n;int g[N][N];PII q[M];PII pre[N][N];void bfs(int sx, int sy){ int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; int hh = 0, tt = 0; q[0] = {sx, sy}; memset(pre, -1, sizeof pre); pre[sx][sy] = {0, 0}; while (hh <= tt) { PII t = q[hh ++ ]; for (int i = 0; i < 4; i ++ ) { int a = t.x + dx[i], b = t.y + dy[i]; if (a < 0 || a >= n || b < 0 || b >= n) continue; if (g[a][b]) continue; if (pre[a][b].x != -1) continue; q[ ++ tt] = {a, b}; pre[a][b] = t; } }}int main(){ scanf("%d", &n); for (int i = 0; i < n; i ++ ) for (int j = 0; j < n; j ++ ) scanf("%d", &g[i][j]); bfs(n - 1, n - 1); PII end(0, 0); while (true) { printf("%d %d\n", end.x, end.y); if (end.x == n - 1 && end.y == n - 1) break; end = pre[end.x][end.y]; } return 0;}</code></pre><h3 id="188-武士风度的牛"><a href="#188-武士风度的牛" class="headerlink" title="188. 武士风度的牛"></a><a href="https://www.acwing.com/problem/content/description/190/">188. 武士风度的牛</a></h3><h4 id="题目:-4"><a href="#题目:-4" class="headerlink" title="题目:"></a>题目:</h4><p>马走日,问从起点走到终点最少走几步</p><h4 id="思路:-4"><a href="#思路:-4" class="headerlink" title="思路:"></a>思路:</h4><p>走呗</p><h4 id="代码:-4"><a href="#代码:-4" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>#define x first#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 155, M = N * N;int n, m;char g[N][N];PII q[M];int dist[N][N];int bfs(){ int dx[] = {-2, -1, 1, 2, 2, 1, -1, -2}; int dy[] = {1, 2, 2, 1, -1, -2, -2, -1}; int sx, sy; for (int i = 0; i < n; i ++ ) for (int j = 0; j < m; j ++ ) if (g[i][j] == 'K') sx = i, sy = j; int hh = 0, tt = 0; q[0] = {sx, sy}; memset(dist, -1, sizeof dist); dist[sx][sy] = 0; while (hh <= tt) { auto t = q[hh ++ ]; for (int i = 0; i < 8; i ++ ) { int a = t.x + dx[i], b = t.y + dy[i]; if (a < 0 || a >= n || b < 0 || b >= m) continue; if (g[a][b] == '*') continue; if (dist[a][b] != -1) continue; if (g[a][b] == 'H') return dist[t.x][t.y] + 1; dist[a][b] = dist[t.x][t.y] + 1; q[ ++ tt] = {a, b}; } } return -1;}int main(){ cin >> m >> n; for (int i = 0; i < n; i ++ ) cin >> g[i]; cout << bfs() << endl; return 0;}</code></pre><h3 id="1100-抓住那头牛"><a href="#1100-抓住那头牛" class="headerlink" title="1100. 抓住那头牛"></a><a href="https://www.acwing.com/problem/content/description/1102/">1100. 抓住那头牛</a></h3><h4 id="题目:-5"><a href="#题目:-5" class="headerlink" title="题目:"></a>题目:</h4><p>农夫有两种走的方式:位置+1或-1,位置*2,问抓住牛最少需要走几步</p><h4 id="思路:-5"><a href="#思路:-5" class="headerlink" title="思路:"></a>思路:</h4><h4 id="代码:-5"><a href="#代码:-5" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;const int N = 100010;int n, k;int q[N];int dist[N];int bfs(){ memset(dist, -1, sizeof dist); int hh = 0, tt = 0; q[0] = n; dist[n] = 0; while(hh <= tt) { int t = q[hh ++]; if(t == k)return dist[k]; if(t + 1 < N && dist[t + 1] == -1)dist[t + 1] = dist[t] + 1, q[++ tt] = t + 1; if(t - 1 >= 0 && dist[t - 1] == -1)dist[t - 1] = dist[t] + 1, q[++ tt] = t - 1; if(2 * t < N && dist[2 * t] == -1)dist[2 * t] = dist[t] + 1, q[++ tt] = t * 2; } return -1;}int main(){ cin >> n >> k; cout << bfs(); return 0;}</code></pre><h2 id="多源BFS"><a href="#多源BFS" class="headerlink" title="多源BFS"></a>多源BFS</h2><h3 id="173-矩阵距离"><a href="#173-矩阵距离" class="headerlink" title="173. 矩阵距离"></a><a href="https://www.acwing.com/problem/content/description/175/">173. 矩阵距离</a></h3><h4 id="题目:-6"><a href="#题目:-6" class="headerlink" title="题目:"></a>题目:</h4><p>输出一个 N 行 M 列的整数矩阵 B,其中:</p><p>$B[i][j]=min_{1≤x≤N,1≤y≤M,A[x][y]=1}dist(A[i][j],A[x][y])$</p><h4 id="思路:-6"><a href="#思路:-6" class="headerlink" title="思路:"></a>思路:</h4><h4 id="代码:-6"><a href="#代码:-6" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>#define x first#define y secondusing namespace std;typedef pair<int, int> PII;const int N = 1010, M = N * N;int n, m;char g[N][N];PII q[M];int dist[N][N];void bfs(){ int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; memset(dist, -1, sizeof dist); int hh = 0, tt = -1; for (int i = 1; i <= n; i ++ ) for (int j = 1; j <= m; j ++ ) if (g[i][j] == '1') { dist[i][j] = 0; q[ ++ tt] = {i, j}; } while (hh <= tt) { auto t = q[hh ++ ]; for (int i = 0; i < 4; i ++ ) { int a = t.x + dx[i], b = t.y + dy[i]; if (a < 1 || a > n || b < 1 || b > m) continue; if (dist[a][b] != -1) continue; dist[a][b] = dist[t.x][t.y] + 1; q[ ++ tt] = {a, b}; } }}int main(){ scanf("%d%d", &n, &m); for (int i = 1; i <= n; i ++ ) scanf("%s", g[i] + 1); bfs(); for (int i = 1; i <= n; i ++ ) { for (int j = 1; j <= m; j ++ ) printf("%d ", dist[i][j]); puts(""); } return 0;}</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> acwing </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> BFS </tag>
</tags>
</entry>
<entry>
<title>DFS</title>
<link href="/2022/03/31/%E7%AE%97%E6%B3%95/acwing/DFS/"/>
<url>/2022/03/31/%E7%AE%97%E6%B3%95/acwing/DFS/</url>
<content type="html"><![CDATA[<p>description: DFS 全称是 Depth First Search,中文名是深度优先搜索,是一种用于遍历或搜索树或图的算法。所谓深度优先,就是说每次都尝试向更深的节点走。</p><p>该算法讲解时常常与 BFS 并列,但两者除了都能遍历图的连通块以外,用途完全不同,很少有能混用两种算法的情况。</p><p>DFS 常常用来指代用递归函数实现的搜索,但实际上两者并不一样。有关该类搜索思想请参阅 DFS(搜索).</p><p>DFS 最显著的特征在于其 递归调用自身。同时与 BFS 类似,DFS 会对其访问过的点打上访问标记,在遍历图时跳过已打过标记的点,以确保 每个点仅访问一次。符合以上两条规则的函数,便是广义上的 DFS。</p><span id="more"></span><p>[TOC]</p><h3 id="843-n-皇后问题"><a href="#843-n-皇后问题" class="headerlink" title="843. n-皇后问题"></a><a href="https://www.acwing.com/problem/content/description/845/">843. n-皇后问题</a></h3><h4 id="代码:"><a href="#代码:" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <iostream>using namespace std;const int N = 20;int n;char g[N][N];bool col[N], dg[N], udg[N];void dfs(int u){ if (u == n) { for (int i = 0; i < n; i ++ ) puts(g[i]); puts(""); return; } for (int i = 0; i < n; i ++ ) if (!col[i] && !dg[u + i] && !udg[n - u + i]) { g[u][i] = 'Q'; col[i] = dg[u + i] = udg[n - u + i] = true; dfs(u + 1); col[i] = dg[u + i] = udg[n - u + i] = false; g[u][i] = '.'; }}int main(){ cin >> n; for (int i = 0; i < n; i ++ ) for (int j = 0; j < n; j ++ ) g[i][j] = '.'; dfs(0); return 0;}sfddfff</code></pre><h2 id="枚举"><a href="#枚举" class="headerlink" title="枚举"></a>枚举</h2><h3 id="3503-数组划分"><a href="#3503-数组划分" class="headerlink" title="3503. 数组划分"></a><a href="https://www.acwing.com/problem/content/3506/">3503. 数组划分</a></h3><p>一个数组中有若干正整数,将此数组划分为两个子数组,使得两个子数组的各元素之和 a,b 的差最小。</p><h4 id="输入格式"><a href="#输入格式" class="headerlink" title="输入格式"></a>输入格式</h4><p>共一行,包含若干个正整数,表示给定数组。</p><h4 id="输出格式"><a href="#输出格式" class="headerlink" title="输出格式"></a>输出格式</h4><p>以降序的顺序,输出两个子数组的各元素之和。</p><h4 id="数据范围"><a href="#数据范围" class="headerlink" title="数据范围"></a>数据范围</h4><p>给定数组的元素个数范围 [2,1000]。<br>给定数组的元素取值范围 [1,1000000]。</p><h4 id="输入样例:"><a href="#输入样例:" class="headerlink" title="输入样例:"></a>输入样例:</h4><pre><code>10 20 30 10 10</code></pre><h4 id="输出样例:"><a href="#输出样例:" class="headerlink" title="输出样例:"></a>输出样例:</h4><pre><code>40 40</code></pre><h4 id="思路:"><a href="#思路:" class="headerlink" title="思路:"></a>思路:</h4><p>暴搜,dfs枚举每个数:选或是不选:</p><p>若是当前总和小于总和的一般,则可以选当前数,也可以不选当前数</p><p>否则一定不选当前数。</p><p>注意,dfs枚举一定要有剪枝</p><pre><code class="cpp">#include <iostream>#include <cstring>#include <algorithm>using namespace std;const int N = 1010;int n = 0;int a[N];int s, s1;bool flag = false;void dfs(int u, int now_sum){ if(flag)return; if(u == n)return; if(now_sum <= s / 2){ s1 = max(s1, now_sum); //dfs一定要有剪枝操作,否则最后一个样例过不了 //最后一个样例大概是:有一个特别大的数,其他数加起来都没他大 //所以当我们遍历到这样的数a[u],答案一定是[a[u], s - a[u]] if(s1 == s / 2)return; else if(a[u] > s / 2){ s1 = s - a[u]; flag = true; } //选当前的数 dfs(u + 1, now_sum + a[u]); } dfs(u + 1, now_sum);}int main(){ while(cin >> a[n], a[n])s += a[n ++]; dfs(0, 0); cout << s - s1 << " " << s1; return 0;}</code></pre><h3 id="1116-马走日"><a href="#1116-马走日" class="headerlink" title="1116. 马走日"></a><a href="https://www.acwing.com/problem/content/description/1118/">1116. 马走日</a></h3><h4 id="题目:"><a href="#题目:" class="headerlink" title="题目:"></a>题目:</h4><p>马在中国象棋以日字形规则移动。</p><p>请编写一段程序,给定 n∗m 大小的棋盘,以及马的初始位置 (x,y),要求不能重复经过棋盘上的同一个点,计算马可以有多少途径遍历棋盘上的所有点。</p><h4 id="思路:-1"><a href="#思路:-1" class="headerlink" title="思路:"></a>思路:</h4><p>每次走的总步数达到n*m的时候,ans++</p><h4 id="代码:-1"><a href="#代码:-1" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 10;int n, m;bool st[N][N];int ans;int dx[8] = {-2, -1, 1, 2, 2, 1, -1, -2};int dy[8] = {1, 2, 2, 1, -1, -2, -2, -1};void dfs(int x, int y, int cnt){ if (cnt == n * m) { ans ++ ; return; } st[x][y] = true; for (int i = 0; i < 8; i ++ ) { int a = x + dx[i], b = y + dy[i]; if (a < 0 || a >= n || b < 0 || b >= m) continue; if (st[a][b]) continue; dfs(a, b, cnt + 1); } st[x][y] = false;}int main(){ int T; scanf("%d", &T); while (T -- ) { int x, y; scanf("%d%d%d%d", &n, &m, &x, &y); memset(st, 0, sizeof st); ans = 0; dfs(x, y, 1); printf("%d\n", ans); } return 0;}</code></pre><h3 id="1117-单词接龙"><a href="#1117-单词接龙" class="headerlink" title="1117. 单词接龙"></a><a href="https://www.acwing.com/problem/content/description/1119/">1117. 单词接龙</a></h3><h4 id="题目:-1"><a href="#题目:-1" class="headerlink" title="题目:"></a>题目:</h4><p>单词接龙是一个与我们经常玩的成语接龙相类似的游戏。</p><p>现在我们已知一组单词,且给定一个开头的字母,要求出以这个字母开头的最长的“龙”,每个单词最多被使用两次。</p><p>在两个单词相连时,其重合部分合为一部分,例如 beast 和 astonish ,如果接成一条龙则变为 beastonish。</p><p>我们可以任意选择重合部分的长度,但其长度必须大于等于1,且严格小于两个串的长度,例如 at 和 atide 间不能相连。</p><h4 id="思路:-2"><a href="#思路:-2" class="headerlink" title="思路:"></a>思路:</h4><h4 id="代码:-2"><a href="#代码:-2" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 21;int n;string word[N];int g[N][N];int used[N];int ans;void dfs(string dragon, int last){ ans = max((int)dragon.size(), ans); used[last] ++ ; for (int i = 0; i < n; i ++ ) if (g[last][i] && used[i] < 2) dfs(dragon + word[i].substr(g[last][i]), i); used[last] -- ;}int main(){ cin >> n; for (int i = 0; i < n; i ++ ) cin >> word[i]; char start; cin >> start; for (int i = 0; i < n; i ++ ) for (int j = 0; j < n; j ++ ) { string a = word[i], b = word[j]; for (int k = 1; k < min(a.size(), b.size()); k ++ ) if (a.substr(a.size() - k, k) == b.substr(0, k)) { g[i][j] = k; break; } } for (int i = 0; i < n; i ++ ) if (word[i][0] == start) dfs(word[i], i); cout << ans << endl; return 0;}</code></pre><h3 id="1118-分成互质组"><a href="#1118-分成互质组" class="headerlink" title="1118. 分成互质组"></a><a href="https://www.acwing.com/problem/content/description/1120/">1118. 分成互质组</a></h3><h4 id="题目:-2"><a href="#题目:-2" class="headerlink" title="题目:"></a>题目:</h4><p>给定 n 个正整数,将它们分组,使得每组中任意两个数互质。</p><p>至少要分成多少个组?</p><h4 id="思路:-3"><a href="#思路:-3" class="headerlink" title="思路:"></a>思路:</h4><h4 id="代码:-3"><a href="#代码:-3" class="headerlink" title="代码:"></a>代码:</h4><pre><code class="cpp">#include <cstdio>#include <iostream>#include <vector>using namespace std;const int N = 10;int gcd(int x, int y) { return y ? gcd(y, x % y) : x;}int n, a[N], ans = N, len;vector<int> g[N];bool inline check(int u, int c) { for (int i = 0; i < g[c].size(); i++) if(gcd(g[c][i], u) > 1) return false; return true;void dfs(int u) { if(u == n) { ans = min(ans, len); return; } for(int i = 0; i < len; i++) { if(check(a[u], i)) { g[i].push_back(a[u]); dfs(u + 1); g[i].pop_back(); } } g[len++].push_back(a[u]); dfs(u + 1); g[--len].pop_back();}int main() { scanf("%d", &n); for (int i = 0; i < n; i++) scanf("%d", a + i); dfs(0); printf("%d\n", ans);}</code></pre><h3 id="生成全排列"><a href="#生成全排列" class="headerlink" title="生成全排列"></a>生成全排列</h3><pre><code class="cpp">#include <bits/stdc++.h>using namespace std;const int N = 15;vector<vector<int>> res;int n;int a[N];bool st[N];void dfs(int u){ if(u == n){ vector<int> line; for(int i = 0; i < n; i ++){ line.push_back(a[i]); } res.push_back(line); } for(int i = 0; i < n;i ++){ if(st[i] == false){ a[u] = i; st[i] = true; dfs(u + 1); st[i] = false; } }}int main(){ cin >> n; for(int i = 0; i < n; i++){ cin >> a[i]; } dfs(0); for(int i = 0; i < res.size(); i ++){ for(int j = 0;j < res[i].size(); j ++){ cout << res[i][j] << " " ; } cout << endl; } return 0;}</code></pre><h3 id="有重复数字的全排列"><a href="#有重复数字的全排列" class="headerlink" title="有重复数字的全排列"></a>有重复数字的全排列</h3><pre><code class="cpp">class Solution { public: vector<vector<int>> res; vector<int> path; vector<vector<int>> permutation(vector<int>& nums) { path.resize(nums.size()); sort(nums.begin(),nums.end()); dfs(nums,0,0,0); return res; } void dfs(vector<int> &nums,int u,int start, int state){ if(u == nums.size()){ res.push_back(path); return; } if (!u || nums[u] != nums[u - 1]) start = 0; for(int i = start;i < nums.size(); i ++){ if(!(state >> i & 1)){ path[i] = nums[u]; dfs(nums,u+1,i+1,state + (1 << i)); } } }};</code></pre>]]></content>
<categories>
<category> 算法 </category>
<category> acwing </category>
</categories>
<tags>
<tag> 算法 </tag>
<tag> DFS </tag>
</tags>
</entry>
<entry>
<title>How to read a paper</title>
<link href="/2022/03/02/%E8%AE%BA%E6%96%87%E7%AC%94%E8%AE%B0/00_How%20to%20Read%20a%20Paper/"/>
<url>/2022/03/02/%E8%AE%BA%E6%96%87%E7%AC%94%E8%AE%B0/00_How%20to%20Read%20a%20Paper/</url>
<content type="html"><![CDATA[<p>从头到尾的读一篇论文是十分低效的,正确做法应该是一篇论文读3遍:第一遍有了这篇论文的总体的思路,第二遍抓住这篇论文的主要内容(不包括细节),第三遍深入的理解论文的内容。</p><h2 id="The-first-pass"><a href="#The-first-pass" class="headerlink" title="The first pass"></a>The first pass</h2><p>第一遍纵观全局,快速扫描这篇论文,大致花费5-10分钟,同时注意以下问题:</p><ol><li>仔细读title, abstract, introduction.</li><li>读section, sub-section的标题,忽略其下的具体内容。</li><li>浏览数学公式(如果有),确定理论基础</li><li>读conclusions</li><li>扫一眼reference, 看有没有你读过的</li></ol><p>第一遍之后,你应该能回答以下问题:</p><ol><li>Categroy:这篇论文是什么类型的?评估类型的?对已有系统的分析?描述一种研究的雏形?</li><li>Context: 这篇论文和什么相关的?用到了哪些理论基础?</li><li>Correctness: 它的假设是有效的吗?</li><li>Contributions: 这篇论文的主要贡献是什么?</li><li>Clarity: 写作水平如何?</li></ol><p>第一遍之后,你能决定要不要继续读,这篇论文是否让你感兴趣?它的假设是否是无效的?</p><p>当你写论文的时候,也应该想到,大部分读者只会读这么第一遍,所以尽量把每个节、子节的标题起的清楚易懂,把摘要写得简洁而全面,如果reviewer不能在第一遍读之后就明白你的文章,你很可能会被拒掉。所以,一个总结一篇论文的“图解摘要”是一个很好的想法,也越来越多地在期刊上见到。</p><h2 id="The-second-pass"><a href="#The-second-pass" class="headerlink" title="The second pass"></a>The second pass</h2><p>第二遍读的时候要更仔细,但是要忽略细节。记下你不懂的词汇,记下你想问的问题,你的评价。如果你是个审稿人,这会帮助你写review。第二遍要注意以下几点:</p><ol><li>重点关注图表、插图。图的坐标轴是否标记正确?结果是否用error bar表示,从而具有统计学意义?像这样的常见错误会把仓促、低劣的论文和真正优质的论文区分开。</li><li>标记相关的未阅读参考文献,以便进一步阅读。</li></ol><p>对于一个有经验的读者来说,第二遍要花一个小时。这遍读完之后,你应该能把握文章的要点,以及有支持性的证据。</p><p>有时第二遍之后你也不理解这篇论文,可能是因为这个主题你以前没接触过,术语、首字母缩写你没见过,你不理解证明或是实验技术,所以论文的大部分内容你是不理解的,可能这篇论文写得就不咋地,等等。你可以选择:</p><ol><li>丢下这篇论文不管,并祈祷不读它对你也没影响。</li><li>读一些背景资料之后再返回来读。</li><li>坚持下去,读第三遍!</li></ol><h2 id="The-third-pass"><a href="#The-third-pass" class="headerlink" title="The third pass"></a>The third pass</h2><p>当你想完全理解这篇论文,或者你是个审稿人,那么读第三遍是很重要的。读第三遍的关键是:你要尝试<strong>虚拟复现这篇论文</strong>,也就是假设你和作者提了一样的假设,重新做作者的工作。通过这种虚拟复现,你可以轻易的发现这篇论文的创新点,发现隐藏的缺陷和假设。</p><p>这遍需要你仔细关注细节部分,你要质疑每一个假设,此外,你要思考你要如何表达一个特定的想法。比较你的虚拟复现和作者实际的工作让你可以对论文的证明有深刻的见解。读这遍的时候,你应该为未来的工作记下想法。</p><p>这遍对新手来说要花很多个小时,对有经验的读者也要花费一两个小时,这遍读了之后,你应该能在脑海里重建这个工作的整个结构,以及识别出它的优点和缺点,此外,你应该能精确的指出隐含的假设,实验或分析技术中的潜在问题。</p><h2 id="如何做文献调查"><a href="#如何做文献调查" class="headerlink" title="如何做文献调查"></a>如何做文献调查</h2><p>你应该读什么样的论文?</p><ol><li>用Google Scholar或其他的学术引擎来搜索最近3-5年这个领域的高引用的论文。先读一遍,然后读related work,你会发现最近工作的简要总结,或者能找到最近的一份调查报告。</li><li>在参考书目中找到一样的引用和重复的作者姓名,这些是这个领域的关键论文和研究者。下载这些论文,然后去这些研究者的网站上看他们最近的工作,这将会帮你识别这个领域的顶会。</li><li>去这些顶会的网站,快速扫描他们最近的论文,识别高质量的论文,结合之前你在第二步下载的论文,构成了你首先要读的论文集,这些论文读2个pass,如果他们都引用了一篇你之前没有找到的关键论文,读它,有必要时迭代。</li></ol><h2 id="相关工作"><a href="#相关工作" class="headerlink" title="相关工作"></a>相关工作</h2><p><a href="https://people.inf.ethz.ch/troscoe/pubs/review-writing.pdf">Writing reviews for systems conferences</a></p>]]></content>
<categories>
<category> 论文 </category>
</categories>
<tags>
<tag> 论文 </tag>
<tag> 论文笔记 </tag>
</tags>
</entry>
<entry>
<title>如何通过文献掌握学术动态</title>
<link href="/2022/03/02/%E8%AE%BA%E6%96%87%E7%AC%94%E8%AE%B0/Else/"/>
<url>/2022/03/02/%E8%AE%BA%E6%96%87%E7%AC%94%E8%AE%B0/Else/</url>
<content type="html"><![CDATA[<p>本文是对<a href="https://github.com/zibuyu/research_tao/blob/master/09_undergraduate_training.md">刘知远老师的文章</a> 的摘抄</p><p>Google Scholar还提供高级检索功能,我比较常见的功能包括:</p><ul><li>按作者搜索:author:”DM Blei”,可以搜索指定作者的相关论文;</li><li>按发表期刊/会议搜索:source:”Nature”,可以搜索发表在指定期刊/会议的相关论文;</li><li>按标题出现关键词搜索:allintitle:”latent dirichlet allocation”,可以搜索在标题出现某些关键词的论文;</li><li>搜索引擎常用的and、or和””均支持,其中””表示按引号中的字符串完整搜索。</li></ul><h1 id="01-如何读论文"><a href="#01-如何读论文" class="headerlink" title="01 如何读论文"></a>01 如何读论文</h1><p>阅读论文也不必需要每篇都从头到尾看完。一篇学术论文通常包括以下结构,我们用序号来标记建议的阅读顺序:</p><ul><li>题目(1)</li><li>摘要(2)</li><li>正文:导论(3)、相关工作(6)、本文工作(5)、实验结果(4)、结论(7)</li><li>参考文献(6)</li><li>附录</li></ul><p>按照这个顺序,基本在读完题目和摘要后,大致可以判断这篇论文与自己研究课题的相关性,然后就可以决定是否要精读导论和实验结果判断学术价值,是否阅读本文工作了解方法细节。此外,如果希望了解相关工作和未来工作,则可以有针对性地阅读“相关工作”和“结论”等部分。</p><h1 id="02-好的研究想法从哪里来"><a href="#02-好的研究想法从哪里来" class="headerlink" title="02 好的研究想法从哪里来"></a>02 好的研究想法从哪里来</h1><p>什么才是好的想法呢?我理解这个”好“字,至少有两个层面的意义。</p><h2 id="什么是好"><a href="#什么是好" class="headerlink" title="什么是好"></a>什么是好</h2><h3 id="学科发展角度的”好“"><a href="#学科发展角度的”好“" class="headerlink" title="学科发展角度的”好“"></a>学科发展角度的”好“</h3><p>学术研究本质是对未知领域的探索,是对开放问题的答案的追寻。所以从推动学科发展的角度,评判什么是好的研究想法的标准,首先就在一个“<strong>新</strong>”字。</p><p>过去有个说法,人工智能学科有个魔咒,凡是人工智能被解决(或者有解决方案)的部分,就不再被认为代表“人类智能”。计算机视觉、自然语言处理、机器学习、机器人之所以还被列为人工智能主要方向,也许正是因为它们尚未被解决,尚能代表“人类智能”的尊严。而我们要开展创新研究,就是要提出新的想法解决这些问题。这其中的”新“字,可以体现在提出新的问题和任务,探索新的解决思路,提出新的算法技术,实现新的工具系统等。</p><p>在保证”新“的基础上,研究想法好不好,那就看它<strong>对推动学科发展的助力有多大</strong>。深度学习之所以拥有如此显赫的影响力,就在于它对于人工智能自然语言处理、语音识别、计算机视觉等各重要方向都产生了革命性的影响,彻底改变了对无结构信号(语音、图像、文本)的语义表示的技术路线。</p><h3 id="研究实践角度的”好“"><a href="#研究实践角度的”好“" class="headerlink" title="研究实践角度的”好“"></a>研究实践角度的”好“</h3><p>那是不是想法只要够”新“就好呢?是不是越新越好呢?我认为应该还不是。因为,只有<strong>能做得出来的想法</strong>才有资格被分析好不好。所以,从研究实践角度,还需要考虑研究想法的<strong>可实现性</strong>和<strong>可验证性。</strong></p><p>可实现性,体现在该想法是否有足够的数学或机器学习工具支持实现。可验证性,体现在该想法是否有合适的数据集合和广泛接受的评价标准。很多民间科学家的想法之所以得不到学术界的认同,就是因为这些想法往往缺乏可实现性和可验证性,只停留在天马行空的纸面,只是些虚无缥缈的理念。</p><h2 id="好的研究想法从哪里来"><a href="#好的研究想法从哪里来" class="headerlink" title="好的研究想法从哪里来"></a>好的研究想法从哪里来</h2><p>想法好还是不好,并不是非黑即白的二分问题,而是像光谱一样呈连续分布,因时而异,因人而宜。计算机科技领域的发展既有积累的过程,也有跃迁的奇点,积累量变才会产生质变,吃第三个馒头饱了,也是因为前面两个馒头打底。</p><p>现在的学术研究已经成为高度专业化的职业,有庞大的研究者群体。”Publish or Perish“,是从事学术职业(如教授、研究员、研究生)的人必须做好平衡的事情,不能要求研究者的每份工作都是“诺贝尔奖”或“图灵奖”级的才值得发表。只要对研究领域的发展有所助力,就值得发表出来,帮助同行前进。鲁迅说:天才并不是自生自长在深林荒野里的怪物,是由可以使天才生长的民众产生,长育出来的,所以没有这种民众,就没有天才。这个庞大研究者群体正是天才成长的群众基础。同时,学术新人也是在开展创新研究训练中,不断磨砺寻找好想法能力,鲁迅也说:即使天才,在生下来的时候的第一声啼哭,也和平常的儿童的一样,决不会就是一首好诗。</p><p>那么,好的研究想法从哪里来呢?我总结,首先要有区分研究想法好与不好的能力,这需要<strong>深入全面了解所在研究方向的历史与现状</strong>,具体就是对学科文献的全面掌握。人是最善于学习的动物,完全可以将既有文献中不同时期研究工作的想法作为学习对象,通过了解它们提出后对学科发展的影响——具体体现在论文引用、学术评价情况等各方面——建立对研究想法好与不好的评价模型。我们很难条分缕析完美地列出区分好与不好想法的所有特征向量,但人脑强大的学习能力,只要给予足够的输入数据,就可以在神经网络中自动学习建立判别的模型,鉴古知今,见微知著,这也许就是常说的学术洞察力。</p><p>做过一些研究的同学会有感受,仅阅读自己研究方向的文献,新想法还是不会特别多。这是因为,读到的都是该研究问题已经完成时的想法,它们本身无法启发新的想法。如何产生新的想法呢?我总结有三种可行的基本途径:</p><p><strong>实践法</strong>。即在研究任务上实现已有最好的算法,通过分析实验结果,例如发现这些算法计算复杂度特别高、训练收敛特别慢,或者发现该算法的错误样例呈现明显的规律,都可以启发你改进已有算法的思路。现在很多自然语言处理任务的Leaderboard上的最新算法,就是通过分析错误样例来有针对性改进算法的 [1]。</p><p><strong>类比法</strong>。即将研究问题与其他任务建立类比联系,调研其他相似任务上最新的有效思想、算法或工具,通过合理的转换迁移,运用到当前的研究问题上来。例如,当初注意力机制在神经网络机器翻译中大获成功,当时主要是在词级别建立注意力,后来我们课题组的林衍凯和沈世奇提出建立句子级别的注意力解决关系抽取的远程监督训练数据的标注噪音问题 [2],这就是一种类比的做法。</p><p><strong>组合法</strong>。即将新的研究问题分解为若干已被较好解决的子问题,通过有机地组合这些子问题上的最好做法,建立对新的研究问题的解决方案。例如,我们提出的融合知识图谱的预训练语言模型,就是将BERT和TransE等已有算法融合起来建立的新模型 [3]。</p><p>正如武侠中的最高境界是无招胜有招,好的研究想法并不拘泥于以上的路径,很多时候是在研究者对研究问题深刻认知的基础上,综合丰富的研究阅历和聪明才智产生”顿悟“的结果。这对初学者而言恐怕还很难一窥门径,需要从基本功做起,经过大量科研实践训练后,才能有登堂入室之感。</p><p>在科研实践过程中,除了通过大量文献阅读了解历史,通过深入思考总结产生洞察力外,还有一项必不可少的工作,那就是主动开放的学术交流和合作意识。不同研究领域思想和成果交流碰撞,既为创新思想提供了新的来源,也为”类比“和”顿悟“提供了机会。了解一下历史就可以知晓,人工智能的提出,就是数学、计算机科学、控制论、信息论、脑科学等学科交叉融合的产物。而当红的深度学习的起源,1980年代的Parallel Distributed Processing (PDP),也是计算机科学、脑认知科学、心理学、生物学等领域研究者通力合作的产物。</p>]]></content>
<categories>
<category> 论文 </category>
</categories>
<tags>
<tag> 论文 </tag>
<tag> 论文笔记 </tag>
</tags>
</entry>
</search>