学了编译原理能否用 Java 写一个编译器或解释器?
16 个回答
能。我一开始学编译原理的时候就是用Java写了好多小编译器和解释器。其实用什么语言来实现编译器并不是最重要的部分(虽然Java也不是实现编译器最方便的语言),最初用啥语言都可以。
我在大学的时候,我们的软件工程和计算机科学的编译原理课的作业好像都是可以用Java来写的。反正我印象中我给这两门课写的作业都是用的Java。
================================================
关于书
用Java写编译器/解释器的话题主可以试试从ANTLR的作者Terence Parr所写的《Language Implementation Patterns》开始读,跟着它做实验。如果是一开始对编译原理还没啥头绪、而又已经对Java的使用比较熟悉的话,跟着这本书做实验会能学习到不少知识面。不过要把这些知识点都学习扎实了的话还是得进一步读别的书。
我推荐的书单里《自制编译器》那本书的类C语言编译器也是用Java实现的:学习编程语言与编译优化的一个书单。以这本为第一本书也行。侧重点跟上面那本不太一样。
其它用Java作为范例实现语言的编译原理书还有若干,例如正统系编译原理教材:
- 龙书第二版:《Compilers: Principles, Techniques, and Tools》 (2nd Edition)。这本书的附录A有一个完整而简单的编译器前端的范例,用Java语言实现。这个前端包括词法分析、语法分析和三地址线性中间代码的生成。
- 虎书的Java版:《Modern Compiler Implementation in Java》。不过这书配套的范例Java代码写得很不Java…书本身还不错,范例代码风格我不爱。
- 《Introduction to Compiler Construction in a Java World》。这本我没仔细读过,不知道好不好。
- 《Compiler Construction Using Java, JavaCC, and Yacc》。同上,这本我也没读过,不过这两本据说都还挺适合入门阅读。
不过比起上面那些,我还是更推荐EAC2作为学习编译原理的“第二本书”:《
Engineering a Compiler》Second Edition
================================================
关于现有的实现
其实现实生活中大家用得多的、用Java写的编译器还真不少。也举几个例子吧。
教学用编译器
- 斯坦福大学编译器课程CS143的Cool语言。请跳传送门:斯坦福大学编译原理课程质量怎么样? - RednaxelaFX 的回答
偏重学术研究的用Java实现的编译器 / 编译器框架:
- COINS: a COmpiler INfraStructure project
- joeq [IBM/Stanford]
- Jikes RVM [IBM]
- Maxine VM / T1X / C1X / Graal [Oracle/Sun]
- Soot: A framework for analyzing and transforming Java and Android Applications
- WALA [IBM] <- 这个自身不是编译器而是做程序分析的库,但其内容跟编译器有许多关联。
- …还有很多,不列举了
在实际产品中用Java实现的编译器 / 解释器:
- javac [Oracle/Sun]
- ECJ: Eclipse Compiler for Java [Eclipse]
- Groovy
- JRuby
- Jython
- Nashorn [Oracle]
- Rhino [Mozilla]
- DynJS
- Graal [Oracle]
- RoboVM
- asc: ActionScript 3 Compiler [Adobe]
- GWT [Google]
- Google Closure Compiler [Google]
- Quercus [Caucho]
- …同样,还有很多,暂时列举这么多
比较简易/玩具性质的:
正则表达式库:
- java.util.regex.*
- joni: Java port of Oniguruma regexp library
- …
词法/语法分析器生成器(parser generator):
- ANTLR
- JavaCC
- Jay
- CUP
- JLex
- JMeta
- …
汇编器库:
收藏感谢收起
2016.2.12更新:
其实仅仅是一个玩具级的编译器前端,并没有太高的技术含量,本人菜鸟一枚,并非大神^_^
这两天整理了一下源码,已发布到Github,有兴趣的同学欢迎关注^_^
源码链接:GitHub - kasonyang/kalang: A toy compiler front-end
-----------------以下原回答-----------------------
大赞
的回答。
最近用java折腾了一个类java的编译器前端,说说本人的思路:
词法语法分析->构建抽象语法树(AST)->类型检查->生成目标代码
1. 首先,第一步是词法语法分析,这一块我采用的是antlr。antlr项目里有很多语言的文法实现可供参考:GitHub - antlr/grammars-v4: Grammars written for ANTLR v4; expectation that the grammars are free of actions.
2. 语法分析后,接下来就是构建抽象语法树。这一步就是将parser(由antlr生成)的输出(ParseTree)进行进一步的处理,得到AST,以便更好的对语法进行分析,同时可以对语法树就行标志或者转换等操作。在构建语法树的过程中,可以对ParseTree和AST进行映射,这样对AST进行分析时如果有任何问题可以由AST反推回去ParseTree,以便定位错误的位置。
3. 得到抽象语法树后,如果你的编译器是静态类型安全的,那么还需要对语法树进行类型检查,在做类型检查的时候可以顺便把语法糖处理了,比如说Integer i=3,这个赋值是类型不匹配的,你需要将其转换为:Integer i=new Integer(3),这样就类型匹配了。
4. 类型检查完后,就可以生成目标代码了。目标代码可以生成本地的二进制代码,也可以生成java的字节码,甚至可以生成另一种高级语言的表示。刚开始时我是直接生成java的class二进制文件的,后来发现因为自己在这方面没什么经验,生成的代码老是有各种问题,每次出问题调试起来非常费劲,有好几次找bug找半天没找出来,信心大受打击,直接想放弃了。后来痛定思痛,把直接生成class文件的功能暂时先砍掉了,而是生成另一种高级语言java的表示,这样有什么bug,找起来就方便多了。
编译写完后,总觉得还缺点什么,然后我又给自己的语言写了个IDE插件。
收藏感谢收起
建议你也可以试试写一下PL/0语言的编译器,原因是比较简单容易入手而且自顶向下的PL/0编译器的实现网上有很多资料
下面是题目要求:
Pl/0语言文法的BNF表示:
<字母> → a|b|c…x|y|z
<数字> → 0|1|2…7|8|9
<无符号整数> → <数字>{<数字>}
<标识符> → <字母>{<字母>|<数字>}
1.A→B.
〈程序〉→〈分程序>.
2.B→CEFH|H|CH|EH|FH|CFH|CEH|EFH
〈分程序〉→ [<常量说明部分>][<变量说明部分>][<过程说明部分>]〈语句〉 3.C→CD;|c
<常量说明部分> → CONST<常量定义>{ ,<常量定义>};
4.D→b=a
<常量定义> → <标识符>=<无符号整数>
5.E→Eb;|d
<变量说明部分> → VAR<标识符>{ ,<标识符>};
6.F→GB;F
<过程说明部分> → <过程首部><分程序>;{<过程说明部分>}
7.G→eb;
<过程首部> → procedure<标识符>;
8.H→I|R|T|S|U|V|J|ε
<语句> → <赋值语句>|<条件语句>|<当型循环语句>|<过程调用语句>|<读语句>|<写语句>|<复合语句>|<空>
9.I→b:=L
<赋值语句> → <标识符>:=<表达式>
10.J→fWg
<复合语句> → begin<一个或多个语句><end>
11.K→LQL|hL
<条件> → <表达式><关系运算符><表达式>|ood<表达式>
12.L→LOM|M|-M|+M
<表达式> → [+|-]<项>{<加减运算符><项>}
13.M→MPN|N
<项> → <因子>{<乘除运算符><因子>}
14.N→b|a|(L)
<因子> → <标识符>|<无符号整数>|(<表达式>)
15.O→+|-
<加减运算符> → +|-
16.P→*|/
<乘除运算符> → *|/
17.Q→=|#|<|<=|>|>=
<关系运算符> → =|#|<|<=|>|>=
18.R→pKqH
<条件语句> → if<条件>then<语句> 19.S→mb
<过程调用语句> → call<标识符>
20.T→nKoH
<当型循环语句> → while<条件>do<语句>
21.U→i(X)
<读语句> → read(<一个或多个标识符>)
22.V→j(X)
<写语句> → write(<一个或多个标识符>)
23.W→W;H|H
<一个或多个语句>→<语句>{ ;<语句>}
24.X→X,b|b
<一个或多个标识符>→<标识符>{,<标识符>}
一. 为PL/0语言建立一个词法分程序GETSYM(函数)
把关键字、算符、界符称为语言固有的单词,标识符、常量称为用户自定义的单词。为此设置三个全程量:SYM,ID,NUM 。
SYM:存放每个单词的类别,为内部编码的表示形式。
ID:存放用户所定义的标识符的值,即标识符字符串的机内表示。
NUM:存放用户定义的数。
GETSYM要完成的任务:
1. 滤掉单词间的空格。
2. 识别关键字,用查关键字表的方法识别。当单词是关键字时,将对应的类别放在SYM中。如IF的类别为IFSYM,THEN的类别为THENSYM。
3. 识别标识符,标识符的类别为IDENT,IDRNT放在SYM中,标识符本身的值放在ID中。关键字或标识符的最大长度是10。
4. 拼数,将数的类别NUMBER放在SYM中,数本身的值放在NUM中。
5. 拼由两个字符组成的运算符,如:>=、<=等等,识别后将类别存放在SYM中。
6. 打印源程序,边读入字符边打印。
由于一个单词是由一个或多个字符组成的,所以在词法分析程序GETSYM中定义一个读字符过程GETCH。
二. 为PL/0语言建立一个语法分析程序BLOCK(函数)
PL/0编译程序采用一遍扫描的方法,所以语法分析和代码生成都有在BLOCK中完成。BLOCK的工作分为两步:
a) 说明部分的处理
说明部分的处理任务就是对每个过程(包括主程序,可以看成是一个主过程)的说明对象造名字表。填写所在层次(主程序是0层,在主程序中定义的过程是1层,随着嵌套的深度增加而层次数增大。PL/0最多允许3层),标识符的属性和分配的相对地址等。标识符的属性不同则填写的信息不同。
所造的表放在全程量一维数组TABLE中,TX为指针,数组元素为结构体类型数据。LEV给出层次,DX给出每层的局部量的相对地址,每说明完一个变量后DX加1。
例如:一个过程的说明部分为:
const a=35,b=49;
var c,d,e;
procedure p;
var g;
对它的常量、变量和过程说明处理后,TABLE表中的信息如下: NAME: a
NAME: b
NAME: c
NAME: d
NAME: e
NAME: p KIND: CONSTANT
KIND: CONSTANT
KIND: VARIABLE
KIND: VARIABLE
KIND: VAEIABLE
KIND: PROCEDURE VAL: 35
VAL: 49
LEVEL: LEV
LEVEL: LEV
LEVEL: LEV
LEVEL: LEV ADR: DX
ADR: DX+1
ADR: DX+2
ADR:
NAME: g
。
。
。 KIND: VARIABLE
。
。
。 LEVEL: LEV+1
。
。
。 ADR: DX
。
。
。
对于过程名的ADR域,是在过程体的目标代码生成后返填过程体的入口地址。
TABLE表的索引TX和层次单元LEV都是以BLOCK的参数形式出现,在主程序调用BLOCK时实参的值为0。每个过程的相对起始位置在BLOCK内置初值DX=3。
2.语句处理和代码生成
对语句逐句分析,语法正确则生目标代码,当遇到标识符的引用则去查TABLE表,看是否有过正确的定义,若有则从表中取出相关的信息,供代码生成用。PL/0语言的代码生成是由过程GEN完成。GEN过程有三个参数,分别代表目标代码的功能码、层差、和位移量。生成的目标代码放在数组CODE中。CODE是一维数组,数组元素是结构体类型数据。
PL/0语言的目标指令是一种假想的栈式计算机的汇编语言,其格式如下: 其中f代表功能码,l代表层次差,a代表位移量。
目标指令有8条:
① LIT:将常数放到运栈顶,a域为常数。
② LOD:将变量放到栈顶。a域为变量在所说明层中的相对位置,l为调用层与说明层的层差值。
③ STO:将栈顶的内容送到某变量单元中。a,l域的含义与LOD的相同。
④ CAL:调用过程的指令。a为被调用过程的目标程序的入中地址,l为层差。
⑤ INT:为被调用的过程(或主程序)在运行栈中开辟数据区。a域为开辟的个数。
⑥ JMP:无条件转移指令,a为转向地址。
⑦ JPC:条件转移指令,当栈顶的布尔值为非真时,转向a域的地址,否则顺序执行。
⑧ OPR:关系和算术运算。具体操作由a域给出。运算对象为栈顶和次顶的内容进行运算,结果存放在次顶。a域为0时是退出数据区。
三. 建立一个解释执行目标程序的函数
编译结束后,记录源程序中标识符的TABLE表已退出内存,内存中只剩下用于存放目标程序的CODE数组和运行时的数据区S。S是由解释程序定义的一维整型数组。解释执行时的数据空间S为栈式计算机的存储空间。遵循后进先出的规则,对每个过程(包括主程序)当被调用时,才分配数据空间,退出过程时,则所分配的数据空间被释放。
为解释程序定义四个寄存器:
1. I:指令寄存器,存放当前正在解释的一条目标指令。
2. P:程序地址寄存器,指向下一条要执行的目标指令(相当于CODE数组的下标)。
3. T:栈顶寄存器,每个过程运行时要为它分配数据区(或称为数据 段),该数据区分为两部分。
静态部分:包括变量存放区和三个联单元。
动态部分:作为临时工作单元和累加器用。需要时临时分配,用完立即释放。栈顶寄存器T指出了当前栈中最新分配的单元(T也是数组S的下标)。
4. B:基地址寄存器,指出每个过程被调用时,在数据区S中给出它分配的数据段起始地址,也称为基地址。每个过程被调用时,在栈顶分配三个联系单元。这三个单元的内容分别是:
SL:静态链,它是指向定义该过程的直接外过程运行时数据段的基地址。
DL:动态链,它是指向调用该过程前正在运行过程的数据段的基地址。
RA:返回地址,记录调用该过程时目标程序的断点,即当时的程序地址寄存器P的值。
具体的过程调用和结束,对上述寄存器及三个联系单元的填写和恢复由下列目标指令完成。
1. INT 0 a
a:为局部量个数加3
2. OPR 0 0
恢复调用该过程前正在运行过程(或主程序)的数据段的基地址寄存器的值,恢复栈顶寄存器T的值,并将返回地址送到指令寄存器P中。
3. CAL l a
a为被调用过程的目标程序的入口,送入指令地址寄存器P中。
CAL指令还完成填写静态链,动态链,返回地址,给出被调用过程的基地址值,送入基址寄存器B中。 例:一个Pl/0源程序及生成的目标代码:
const a=10;
var b,c;
procedure p;
begin
c:=b+a
end;
2 int 0 3
3 lod 1 3
4 lit 0 10
5 opr 0 2
6 sto 1 4
7 opr 0 0
begin
read(b);
while b#0 do
begin
call p;
write(2*c);
read(b)
end
end .
8 int 0 5
9 opr 0 16
10 sto 0 3
11 lod 0 3
12 lit 0 0
13 opr 0 9
14 jpc 0 24
15 cal 0 2
16 lit 0 2
17 lod 0 4
18 opr 0 4
19 opr 0 14
20 opr 0 15
21 opr 0 16
22 sto 0 3
23 jmp 0 11
24 opr 0 0
下面是一些资料
Java版的自顶向下实现:
JAVA课程设计PL0编译器
C语言版的自顶向下实现:
pl/0编译器源码及文档
最后加上我自己实现的,这个是有别于网上的一般实现,采用的SLR(这个具体可以参考编译原理类的书籍)的方法,但是由于时间关系,在代码生成的时候并没有采用标准的语法制导翻译,所以还有待改进,不过整体也算是实现了一个SLR分析方法,而且我实现了读入文法由程序自动的构造SLR分析表,所以文法可以随时修改,相当于实现了一个Yacc的部分功能,也算有一定参考价值,也把代码放在这里吧:
https://github.com/wangzhaode/PL0-compiler-SLR
收藏感谢收起
也可以参考用flex+bison做词法分析和语法分析,用c写语义部分和中间代码生成。
最近用上面的方法写了个类java语言的编译器,有兴趣可以交流。
建议开始入门编译原理不要看龙书,略难,容易在细枝末节处徘徊太长时间。
收藏感谢
收藏感谢
实现一个编译器并不难,因为我们在学Java之初,就是用记事本进行编译的。当然,如果制作复杂的编译器需要编译器框架,但原理是一样的。
作者:tyler_download
链接:编译原理动手实操,用java实现一个简易编译器1-词法解析入门 - CSDN博客
来源:CSDN博客
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
技术的发展可谓是日新月异,层出不穷,但无论是炙手可热的大数据,还是火热的人工智能,所有这些高大上的尖端科技无不建立在基础技术的根基之上。编译原理,计算机网络,操作系统,便是所有软件技术的基石。在这三根支柱中,维编译原理最为难懂,特别是大学课本那种晦涩难通,不讲人话的言语,更是让人觉得这门基础技术就像九十多岁的老妪,皮肤干巴,老态龙钟,让人提不起一点欲望。除了国内教材,就算是被广为称赞的一千多页的”龙书“,也是满篇理论,让人望而生畏。
味道怎样,咬一口就知道,手感如何,摸一把就晓得。编译原理缺的不是理论概念,而是能够动手实践的流程,代码,很多原理用话语怎么讲都难以明了,但跑一遍代码,基本就水落石出。本文本着动手实操(念第一声)的原则,用java实现一个简单的编译器,让读者朋友能一感编译原理的实质,我秉持一个原则,没有代码可实践的计算机理论,都是耍流氓。
编译器作用就是将一种计算机无法理解的文本,转译成计算机能执行的语句,我们要做的编译器如下,将带有加法和乘法的算术式子,转译成机器能执行的汇编语句,t0, t1 是对寄存器的模拟,上述语句基本上就类似计算机能执行的汇编语句了。本章首先专注于词法解析的探讨。
编译原理由两部分组成,一是词法分析,一是语义分析。先说词法分析,词法分析就是将一个语句分割成若干个有意义的字符串的组合,然后给分割的字符串打标签。例如语句:1+2*3+4; 可以分割成 1+, 2*, 3+, 4; 但这些子字符串没有实质意义,有意义的分割是1, +, 2, * , 3, +, 4, ;. 接着就是给这些分割后的字符串打标签,例如给1, 2, 3, 4 打上的标签是NUM_OR_ID, + 打的标签是PLUS, *的标签是TIMES, ;的标签是SEMI, 好了,看看词法分析的代码,代码中2到6行是对标签的定义,其中LP 代表左括号(, RP代表右括号), EOI 表示语句末尾, 第10行的lookAhead 变量用于表明当前分割的字符串指向的标签值,yytext用于存储当前正在分析的字符串,yyleng是当前分析的字符串的长度,yylineno是当前分析的字符串所在的行号。input_buffer 用于存储要分析的语句例如: 1+2*3+4; isAlNum 用于判断输入的字符是否是数字或字母。lex() 函数开始了词法分析的流程,31到40行从控制台读入语句,语句以"end"表明结束,例如在控制台输入:
1+2*3+4;
end回车后,从52行开始执行词法解析流程。以上面的输入为例,input_buffer 存储语句 1+2*3+4, 由于第一个字符是 1, 在for 循环中,落入switch 的default 部分,isAlNum 返回为真,yyleng 自加后值为1, yytext 存储的字符串就是 "1", current前进一个字符变为+2*3+4, 再次执行lex(), 则解析的字符是+, 在for 循环中,落入switch的case '+' 分支,于是yytext为"+", 返回的标签就是PLUS依次类推, advance 调用一次, lex()就执行一次词法分析,当lex执行若干次后,语句1+2*3+4;会被分解成1, +, 2, *, 3, +, 4, ; 。字符串1, 2, 3, 4具有的标签是NUM_OR_ID, + 具有的标签是PLUS, *的标签是TIMES, ;的标签是SEMI。
runLexer() 将驱动词法解析器,执行解析流程,如果解析到的当前字符串,其标签不是EOI(end of input), 也就是没有达到输入末尾,那么就打印出当前分割的字符串和它所属的标签,接着调用advance() 进行下一次解析。
收藏感谢收起
PS,你学了编译原理不是应该知道高级语言都图灵等价了吗?什么语言都能撸的,玩的6了你就会想元编程、FP、C++了。。。。
收藏感谢
Google: Classrooom Object Oriented Language.
https://theory.stanford.edu/~aiken/software/cool/cool-manual.pdf
学了编译原理能否用 Java 写一个编译器或解释器?的更多相关文章
- 用JAVA写一个函数,功能例如以下: 随意给定一组数, 找出随意数相加之后的结果为35(随意设定)的情况
用JAVA写一个函数.功能例如以下:随意给定一组数,比如{12,60,-8,99,15,35,17,18},找出随意数相加之后的结果为35(随意设定)的情况. 能够递归算法来解: package te ...
- 五:用JAVA写一个阿里云VPC Open API调用程序
用JAVA写一个阿里云VPC Open API调用程序 摘要:用JAVA拼出来Open API的URL 引言 VPC提供了丰富的API接口,让网络工程是可以通过API调用的方式管理网络资源.用程序和软 ...
- 用java写一个servlet,可以将放在tomcat项目根目录下的文件进行下载
用java写一个servlet,可以将放在tomcat项目根目录下的文件进行下载,将一个完整的项目进行展示,主要有以下几个部分: 1.servlet部分 Export 2.工具类:TxtFileU ...
- 用JAVA写一个多线程程序,写四个线程,其中二个对一个变量加1,另外二个对一个变量减1
package com.ljn.base; /** * @author lijinnan * @date:2013-9-12 上午9:55:32 */ public class IncDecThrea ...
- 使用JAVA写一个简单的日历
JAVA写一个简单的日历import java.text.DateFormat;import java.text.ParseException;import java.text.SimpleDateF ...
- 用java写一个用户登陆界面
一.课堂测试源代码及其结果截图 用java的swing写一个用户登录界面,采用网格布局.源代码如下: /** * */package LiuLijia; import java.awt.CardLay ...
- 《用Java写一个通用的服务器程序》01 综述
最近一两年用C++写了好几个基于TCP通信类型程序,都是写一个小型的服务器,监听请求,解析自定义的协议,处理请求,返回结果.每次写新程序时都把老代码拿来,修改一下协议解析部分和业务处理部分,然后就一个 ...
- java 写一个 map reduce 矩阵相乘的案例
1.写一个工具类用来生成 map reduce 实验 所需 input 文件 下面两个是原始文件 matrix1.txt 1 2 -2 0 3 3 4 -3 -2 0 2 3 5 3 -1 2 -4 ...
- 自己动手用java写一个hashMap
入坑java很多年了,现在总结一下自己学到的东西. 1.首先我们先来聊聊什么是HashMap? 什么是hash?hash用中文的说法就叫做“散列”,通俗的讲就是把任意长度的字符串输入,经过hash计算 ...
随机推荐
- React、Redux 和 Bootstrap
使用 React.Redux 和 Bootstrap 实现 Alert 今天,我们来学习使用 React.Redux 和 Bootstrap 实现Alert. 例子 这个例子实现了弹出不同类型信息的功 ...
- js变量和函数声明的提升(转)
原文:http://zha-zi.iteye.com/blog/2037026 下面的程序是什么结果? var foo = 1; function bar() { if (!foo) { var fo ...
- 008.Zabbix多图展示
一 Screen介绍 Screen能将某个主机多个图形,或者多个主机的同一种信息放在一起展示. 二 创建多主机监控图形 依次添加VMware-Win7和VMware-CentOS7两台主机的监控图形. ...
- 前端页面重构技巧总结TIP【持续更新...】
本文均为项目实战经验,要求兼容至IE8,所以以下内容均为兼容代码,欢迎各位小伙伴批评指教.其实重构页面是一门学问,看似简单,却暗藏很多学问.实际项目中页面的重构有以下几点最基本需求: 1.需要使用合理 ...
- 电子邮件-TCP
参考下图,由于电子邮件是最早应用于网络上的服务,而早期网络的不可靠性,才设计了TCP协议,而邮件必须要保证正确传输,而非高速,所以早期的电子邮件协议 全是基于TCP的,一直持续到现在.
- iOS开发安全 架构
网络通讯.本地文件和数据.源代码三方面 网络通讯 安全的传输用户密码 客户端在登录时,使用公钥将用户的密码加密后,将密文传输到服务器.服务器使用私钥将密码解密,然后加盐 (Salt:在密码学中,是指通 ...
- 隧道接口工具airtun-ng
隧道接口工具airtun-ng airtun-ng是aircrack-ng套件提供的一个工具.该工具可以为无线网卡建立隧道接口.通过该接口,渗透测试人员可以建立wIDS,分析指定的AP的数据.借助 ...
- python语法(五)—函数
前面几天学习了python的基础语法,判断,循环,以及文件操作等等内容,对python也是有了一个认识.今天开始学习python的函数和模块. 函数 函数是什么?我的理解就是,他和java中的方法是一 ...
- hdu 4407 容斥原理
题意: 1 //一组数据 3 3 //数字为1-3,3次运算 2 2 3 //将2号位变成3 1 1 3 4 //计算1-3号位上与4互质的数的和 1 2 3 6 好题,需要重复练习 #include ...
- hdu 3289 最大独立集
题意:一个动物园里有N只猫和K只狗,一些小朋友来参观,他们如果喜欢狗就不喜欢猫,喜欢猫就不喜欢狗,园长想要移走一些动物,如果,移走的是某个小朋友不喜欢的,而喜欢的没被移走,该小朋友就会高兴,求移动的数 ...