<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Codes, Notes &#38; Scribbles &#187; 新长征路上的代码</title>
	<atom:link href="http://blog.tomtung.com/category/%e6%96%b0%e9%95%bf%e5%be%81%e8%b7%af%e4%b8%8a%e7%9a%84%e4%bb%a3%e7%a0%81/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.tomtung.com</link>
	<description>about programming, music and my life</description>
	<lastBuildDate>Sun, 15 Jan 2012 23:59:24 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Surprises in Scala&#8217;s &#8220;Uniform Return Type&#8221; Magic</title>
		<link>http://blog.tomtung.com/2012/01/surprises-in-scalas-uniform-return-type-magic/</link>
		<comments>http://blog.tomtung.com/2012/01/surprises-in-scalas-uniform-return-type-magic/#comments</comments>
		<pubDate>Sun, 15 Jan 2012 23:59:24 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[LINQ]]></category>
		<category><![CDATA[Scala]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/?p=1266</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2012/01/surprises-in-scalas-uniform-return-type-magic/" title="Surprises in Scala&#039;s &quot;Uniform Return Type&quot; Magic"></a>There&#8217;s an increasingly heated debate about Scala&#8217;s complexity. A recent post talks about Scala&#8217;s &#8220;true complexity&#8220;: the terrifying intricacy you will encounter, if you try to extend Scala&#8217;s Collections Library with a method that achieves the &#8220;perfect&#8221; behavior. Yet it &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2012/01/surprises-in-scalas-uniform-return-type-magic/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2010/02/sinablog2wordpress/' rel='bookmark' title='sinablog2wordpress：从新浪博客搬家到WordPress'>sinablog2wordpress：从新浪博客搬家到WordPress</a></li>
<li><a href='http://blog.tomtung.com/2009/08/rfc822-date-time-parser/' rel='bookmark' title='读取RFC822格式日期时间的类[C#]'>读取RFC822格式日期时间的类[C#]</a></li>
<li><a href='http://blog.tomtung.com/2009/07/agile-development-reading-notes/' rel='bookmark' title='敏捷开发学习笔记（思维导图）'>敏捷开发学习笔记（思维导图）</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2012/01/surprises-in-scalas-uniform-return-type-magic/" title="Surprises in Scala&#039;s &quot;Uniform Return Type&quot; Magic"></a><p>There&#8217;s an increasingly heated debate about Scala&#8217;s complexity. A recent post talks about Scala&#8217;s &#8220;<a href="http://yz.mit.edu/wp/true-scala-complexity/" target="_blank">true complexity</a>&#8220;: the terrifying intricacy you will encounter, if you try to extend Scala&#8217;s Collections Library with a method that achieves the &#8220;perfect&#8221; behavior. </p>
<p>Yet it also might be worth asking: what are the motivations of these &#8220;perfect&#8221; behaviors? Are they always desirable?</p>
<p>One important aspect of the &#8220;perfect&#8221; collection behavior is the magical <em>uniform return type principle</em>. Sloppily speaking, this means that transforming a collection should give you another collection of the same kind. For instance:</p>
<pre class="brush:scala">
// Filtering a List gives you a filtered List
scala&gt; List(1,2,3,4).filter(_%2==0)
res: List[Int] = List(2, 4)

// Mapping a Set gives you a mapped Set
scala&gt; Set(1,2,3,4).map(1+)
res: scala.collection.immutable.Set[Int] = Set(2, 3, 4, 5)

// Uniform return type at both compile time and runtime
scala&gt; val iterable : Iterable[Int] = Set(1,2,3,4)
iterable: Iterable[Int] = Set(1, 2, 3, 4)

scala&gt; iterable.map(1+)
res: Iterable[Int] = Set(2, 3, 4, 5)

// Works for Strings, too
scala&gt; &quot;E is the most frequent letter in English&quot;.map(
     |    c =&gt; if(c=='e'|c=='E') '3' else c)
res: String = 3 is th3 most fr3qu3nt l3tt3r in 3nglish
</pre>
<p>Clean and consistent, can&#8217;t be more intuitive. Of course filtering a List should return a List, and mapping a Set should give you a Set. Both compile time and runtime. Works even for non-collections like Strings.</p>
<p>When I first saw this, I thought &#8220;yeah, cool&#8221;, and then forgot about it. I took it for granted as just another Scala magic that makes my life easier, without realizing its intricate implications. Not until recently have I realized that Scala&#8217;s collections were much smarter, or trickier, than I had expected.</p>
<p>The uniform return type principle is fairly straightforward for operations like filter, take, drop and slice, but situations get complicated in the cases of some other operations like map and flatMap. Consider the following:</p>
<pre class="brush:scala">
scala&gt; Set(1,2,3,4).map(_ % 2 == 0)
res: scala.collection.immutable.Set[Boolean] = Set(false, true)
</pre>
<p>It starts getting interesting. When mapping a Set, you should get a Set, which by definition doesn&#8217;t contain repeated values. So you get a Set[Boolean] which consists of only 2 values. You might get surprised here if you are familiar with LINQ in C#. In C#</p>
<pre class="brush:csharp">new HashSet(new[] {1, 2, 3, 4}).Select(x =&gt; x%2 == 0)</pre>
<p>gives you an IEnumerable that consists of &#8220;false, true, false, true&#8221; in order, because the HashSet is simply iterated through as a Normal IEnumerable. Some say C#/LINQ sucks, because the type information of original collection is &#8220;lost&#8221;.</p>
<p>In Scala, Map[K, V] is a subtype of Iterable[(K, V)], thus can be seen as a collection of key-value pairs. Now stop for a moment and think about it: what happens if you map a Map?</p>
<pre class="brush:scala">
scala&gt; Map('a'-&gt;1,'b'-&gt;2,'c'-&gt;3,'d'-&gt;4).map(t =&gt; t._2 -&gt; t._1)
res: scala.collection.immutable.Map[Int,Char] = Map(1 -&gt; a, 2 -&gt; b, 3 -&gt; c, 4 -&gt; d)

scala&gt; Map('a'-&gt;0,'b'-&gt;1,'c'-&gt;0,'d'-&gt;1).map(t =&gt; t._1)
res: scala.collection.immutable.Iterable[Char] = List(a, b, c, d)
</pre>
<p>Ideally, according to the uniform return type principle, mapping a map should give you another Map. But this is only possible when the target element type is key-value pair. Otherwise, the best we can do is to treat the Map as just an Iterable of key-value pairs and return an List of the target elements.</p>
<p>However, there is still the problem of inconsistency. Here&#8217;s an example:</p>
<pre class="brush:scala">
scala&gt; Map(1-&gt;'a', 2-&gt;'b', 3-&gt;'c').map(_._2)
res: scala.collection.immutable.Iterable[Char] = List(a, b, c)

scala&gt; Map(1-&gt;('a','b'), 2-&gt;('a','c'), 3-&gt;('b','c')).map(_._2)
res: scala.collection.immutable.Map[Char,Char] = Map(a -&gt; c, b -&gt; c)
</pre>
<p>Same operation on two Sets, but the behaviors are totally different, simply because these two Sets have different types of values? This is weird. The problem is that 2-tuples are selected to represent the key-value pairs, but are not exclusively used for this purpose. So when user map the elements to 2-tuples, there&#8217;s this unavoidable ambiguity. </p>
<p>It becomes even more error-prone if the actual class type of the collection is not known at compile time. We know that Map has a property &#8220;keys&#8221; that gives you an Iterable of its keys. Consider the following example:</p>
<pre class="brush:scala">
scala&gt; val m = Map(1 -&gt; 'a', 2 -&gt; 'b', 3 -&gt; 'c', 4 -&gt; 'd')
m: scala.collection.immutable.Map[Int,Char] = Map(1 -&gt; a, 2 -&gt; b, 3 -&gt; c, 4 -&gt; d)

scala&gt; m.keys.map(_%2==0)
res: Iterable[Boolean] = Set(false, true)
</pre>
<p>Ah&#8230;the runtime type of &#8220;keys&#8221; is Set[Int], so repeated values in the returned collection are also ignored. The behavior should surprise you if you haven&#8217;t think of this. In fact, when given a Iterable without knowing its actual type,  in any case the behaviors of collection operations are simply unpredictable. Scala guys can argue that this is just how it works, but this doesn&#8217;t seems to be a good thing, given that Iterables have been widely used between interfaces to pass data collections around without specifying their internal structures.</p>
<p>To sum up, the behavior of the map operation depends on not only the <strong>type of the collection on which it invokes</strong>, both static and dynamic, but also on the <strong>type of the mapped elements</strong>.</p>
<p>How is this magic implemented? If you take a look at the source code, you will probably see the most complicated collection library you&#8217;ve ever seen in your life. I&#8217;ll try to give a brief and simplified explanation. Here is the actual signature of the map method:</p>
<pre class="brush:scala">
def map[MappedElem, That](p: Elem =&gt; MappedElem)
    (implicit bf: CanBuildFrom[This, MappedElem, That]): That
</pre>
<p>As you can see there&#8217;s an extra <a href="http://docs.scala-lang.org/tutorials/tour/implicit-parameters.html">implicit parameter</a> &#8220;bf&#8221; of type &#8220;CanBuildFrom[This, MappedElem, That]&#8220;, which will give you a &#8220;Builder[MappedElem, That]&#8221; that can build a collection (of type &#8220;That&#8221;) from the mapped elements (of type &#8220;MappedElem&#8221;). In short, &#8220;CanBuildFrom[This, MappedElem, That]&#8221; is a factory for &#8220;Builder[MappedElem, That]&#8220;, which itself is a factory for &#8220;That&#8221;. When both type parameters &#8220;This&#8221; and &#8220;MappedElem&#8221; are given, the compiler can find the best eligible for &#8220;bf&#8221; (according to <a href="http://docs.scala-lang.org/tutorials/FAQ/finding-implicits.html">some</a> <a href="http://eed3si9n.com/revisiting-implicits-without-import-tax">perplexing</a> <a href="http://eed3si9n.com/implicit-parameter-precedence-again">rules</a>) and consequently determines the static type of &#8220;That&#8221;. For example:</p>
<pre class="brush:scala">
// bf : CanBuildFrom[Map[_,_], (Char, Int), Map[Char, Int]]
Map(1 -&gt; &#x2018;a&#x2019;, 2 -&gt; &#x2018;b&#x2019;).map(t =&gt; t._2 -&gt; t._1)

// bf: CanBuildFrom[Iterable[_], Int, Iterable[Int]]
Map(1 -&gt; &#x2018;a&#x2019;, 2 -&gt; &#x2018;b&#x2019;).map(_._1)
</pre>
<p>The &#8220;CanBuildFrom&#8221; can forward the call to the &#8220;genericBuilder[MappedElem]&#8221; method of the collection inferred in compile time, so that the &#8220;right&#8221; runtime type can be selected via virtual dispatch.</p>
<p>Now you should have noticed the conceptual and implementation complexity brought by the seemingly simple &#8220;uniform return type principle&#8221;. But why do we need this at the first place?</p>
<p>Let&#8217;s digress to talk about LINQ for a moment. The argument &#8220;LINQ sucks, because the type information of original collection is lost&#8221; doesn&#8217;t really stand, because I think this is exactly what it was designed to ignore. For most of the times, we often don&#8217;t care about whether the Iterable is a Seq or a Set or a Map or whatever. What we do with a collection is often just querying and consuming its content. LINQ provides a uniform interface with consistent, lazy behavior to facilitate this. When you need a collection of certain type, you explicitly convert the query result to it. When you need special behaviors of a Set or Map (Dictionary), you use their own interfaces. But queries are queries, different collections are treated equally. Such simplicity is not a weakness, but a feature.</p>
<p>In my point of view, LINQ and Scala&#8217;s collection operation are cosmetically similar but semantically different. LINQ <strong>queries</strong> a collection; Scala&#8217;s collection operation <strong>transforms</strong> a collection. (This may give you the clue why LINQ doesn&#8217;t have a ForEach extension method.) Scala&#8217;s functional nature necessitates the collection transformation semantic: you need to be able to deal with the value of a collection and compute a new value from it. And since Scala is also statically and strongly typed, the &#8220;uniform return type principle&#8221; is required to make the system consistent. Thus the behaviors of collection operations should of course depend on the actual collection type and the type of the target element. The operations are non-lazy (strict) by default to avoid unintended delay of the side-affects within the operations (there&#8217;s a more detailed explanation about the decision on laziness at the bottom of <a href="http://docs.scala-lang.org/overviews/collections/views.html">this page</a>). </p>
<p>What if we only want to query and consume the content of the collection, like we do in LINQ? I suggest using Iterator directly or convert the collection to Stream:</p>
<pre class="brush:scala">
scala&gt; Set(1,2,3,4).iterator.map(_%2).foreach(print)
1010
scala&gt; Set(1,2,3,4).toStream.map(_%2).foreach(print)
1010
</pre>
<p>What comes with the complexity is great flexibility and power. Scala is expressive and seems quite easy, but the paradox is that you need to spend much time learning to avoid misusing it. Scala is not easy, even in the case of these basic collection operations; it&#8217;s important to recognize that.</p>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2010/02/sinablog2wordpress/' rel='bookmark' title='sinablog2wordpress：从新浪博客搬家到WordPress'>sinablog2wordpress：从新浪博客搬家到WordPress</a></li>
<li><a href='http://blog.tomtung.com/2009/08/rfc822-date-time-parser/' rel='bookmark' title='读取RFC822格式日期时间的类[C#]'>读取RFC822格式日期时间的类[C#]</a></li>
<li><a href='http://blog.tomtung.com/2009/07/agile-development-reading-notes/' rel='bookmark' title='敏捷开发学习笔记（思维导图）'>敏捷开发学习笔记（思维导图）</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2012/01/surprises-in-scalas-uniform-return-type-magic/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[学习笔记] Logistic 回归</title>
		<link>http://blog.tomtung.com/2011/10/logistic-regression/</link>
		<comments>http://blog.tomtung.com/2011/10/logistic-regression/#comments</comments>
		<pubDate>Tue, 25 Oct 2011 20:08:20 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[机器学习]]></category>
		<category><![CDATA[概率]]></category>
		<category><![CDATA[笔记]]></category>
		<category><![CDATA[算法]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/?p=1138</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2011/10/logistic-regression/" title="[学习笔记] Logistic 回归"></a>本文为 Stanford 的机器学习公开课第三周的笔记，不过和上课内容的细节有些出入。 Logistic 回归解决的并不是回归问题，而是分类问题，即目标变量（target variable）的值是离散而非连续的。这时目标变量也可称为标签（label）。如果仍用线性回归硬搞，得到的结果会非常不靠谱。 我们先考虑简单的情况：数据点只有 0 和 1 两个标签（binary classification），即 \( y \in \{0,1\} \)，且大致上是线性可分的（linearly separable）。如图： 那么现在问题就是要找到一个 \(\theta\)，使得直线 \(\theta^{\rm T} x = 0\) 能够将上面图中的 positive 和 negative 两类数据点“分开”；这样的一条直线称为决策边界（decision boundary）。——但具体什么叫“分开”？或者说，如果已知 \(\theta\)，对于一个标签未知的数据点 \(x\)，怎么判断它是 positive 还是 negative？ 直观上看，在给定 \(\theta\) 和 \(x\) &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2011/10/logistic-regression/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2011/10/linear-regression-gradient-descent/' rel='bookmark' title='[学习笔记] 线性回归（及梯度下降）'>[学习笔记] 线性回归（及梯度下降）</a></li>
<li><a href='http://blog.tomtung.com/2011/10/em-algorithm/' rel='bookmark' title='[学习笔记] Expectation-Maximization(EM) 算法'>[学习笔记] Expectation-Maximization(EM) 算法</a></li>
<li><a href='http://blog.tomtung.com/2011/10/plsa/' rel='bookmark' title='[学习笔记] Probabilistic latent semantic analysis (pLSA)'>[学习笔记] Probabilistic latent semantic analysis (pLSA)</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2011/10/logistic-regression/" title="[学习笔记] Logistic 回归"></a><p>本文为 <a href="http://www.ml-class.org/" target="_blank">Stanford 的机器学习公开课</a>第三周的笔记，不过和上课内容的细节有些出入。</p>
<p>Logistic 回归解决的并不是回归问题，而是分类问题，即目标变量（target variable）的值是离散而非连续的。这时目标变量也可称为标签（label）。如果仍用线性回归硬搞，得到的结果会非常不靠谱。</p>
<p>我们先考虑简单的情况：数据点只有 0 和 1 两个标签（binary classification），即 \( y \in \{0,1\} \)，且大致上是线性可分的（linearly separable）。如图：<br />
<img class="alignnone" src="http://upload.tomtung.com/img/binary-classification.png" alt="" width="561" height="420" /></p>
<p>那么现在问题就是要找到一个 \(\theta\)，使得直线 \(\theta^{\rm T} x = 0\) <sup>[<a href="#logistic-regression-n-1" class="footnoted" id="to-logistic-regression-n-1">1</a>]</sup> 能够将上面图中的 positive 和 negative 两类数据点“分开”；这样的一条直线称为决策边界（decision boundary）。——但具体什么叫“分开”？或者说，如果已知 \(\theta\)，对于一个标签未知的数据点 \(x\)，怎么判断它是 positive 还是 negative？</p>
<p>直观上看，在给定 \(\theta\) 和 \(x\) 的情况下，\(y\) 应该满足一个<a href="http://en.wikipedia.org/wiki/Bernoulli_distribution" target="_blank"> 0-1 分布</a>，即<br />
$$ y|x;\theta \sim Bernoulli\big(\phi(\theta^{\rm T} x)\big) $$<br />
其中 \( \phi(\theta^{\rm T} x) = P(y = 1|x;\theta) \) 应该满足：</p>
<ol>
<li> \( 0 \leq \phi(\theta^{\rm T} x) \leq 1 \)</li>
<li>若 \(\theta^{\rm T} x &gt; 0\)，我们可以认为 \( y=1 \) 的可能性相对更大，即 \( \phi(\theta^{\rm T} x) &gt; 0.5 \)，且如果数据点离边界越远，即 \(\theta^{\rm T} x\) 越大， \(\phi(\theta^{\rm T} x)\) 也应该越大。</li>
<li>反之，若 \(\theta^{\rm T} x &lt; 0\)，则 \( \phi(\theta^{\rm T} x) &lt; 0.5 \)，且随 \(\theta^{\rm T} x\) 减小而减小。</li>
<li>当 \(\theta^{\rm T} x = 0\) 时，点落在决策边界上，我们无从判断它的标签 \(y\)，因此 \( \phi(\theta^{\rm T} x) = 0.5 \)。</li>
</ol>
<p>那 \(\phi\) 应该取什么样的函数呢？我们知道 <a href="http://en.wikipedia.org/wiki/Logistic_function" target="_blank">logistic 函数</a>恰巧满足上述条件，不妨就取它（“logistic 回归”也因此得名）<sup>[<a href="#logistic-regression-n-2" class="footnoted" id="to-logistic-regression-n-2">2</a>]</sup>。即：<br />
$$ \phi(z) = \frac{1}{1+\exp(-z)} $$<br />
logistic 函数的图像是：<br />
<img src="http://upload.tomtung.com/img/logistic-curve.png" width="320" height="240" /><br />
可以直观地看到它确实是满足我们要求的。这样我们就得到了：<br />
$$ P(y = 1|x;\theta) = \phi(\theta^{\rm T} x) = \frac{1}{1+\exp(-\theta^{\rm T} x)} $$<br />
我们记 \( h_\theta(x) = P(y = 1|x;\theta) \)，表示这是我们希望预测的量，也就是模型的假设（hypothesis） <sup>[<a href="#logistic-regression-n-3" class="footnoted" id="to-logistic-regression-n-3">3</a>]</sup>。在实际分类应用中，\(h_\theta(x) > 0.5\) 时我们可以给出判断 \(y = 1\) ，\(h_\theta(x) < 0.5\) 时 \(y = 0\) ，\(h_\theta(x) = 0.5\) 的话就蒙一个吧。</p>
<p>接下来 \(\theta\) 该怎么求呢？根据log极大似然法，可知 \(\theta\) 的最优值就是 \(\arg\max_\theta L(\theta)\)，其中<br />
$$<br />
\begin{eqnarray}<br />
L(\theta) &amp;=&amp; \log \prod_i P(y^{(i)}|x^{(i)};\theta) \\<br />
&amp;=&amp; \log \prod_i h_\theta(x^{(i)})^{y^{(i)}} \big(1-h_\theta(x^{(i)})\big)^{1-y^{(i)}} \\<br />
&amp;=&amp; \sum_i y^{(i)} \log h_\theta(x^{(i)}) + (1-y^{(i)}) \log h_\theta(1-x^{(i)})<br />
\end{eqnarray}<br />
$$<br />
其中第二步有点小 tricky。注意到 \( h_\theta(x)^y \big(1-h_\theta(x)\big)^{1-y} \) 在 \(y=0\) 时值为 \(h_\theta(x)^y\)，在 \(y=1\) 时值为 \(\big(1-h_\theta(x)\big)^{1-y}\)。这样就把 \(y\) 两种取值的情况合并写在一个式子里了。</p>
<p>接下来我们可以求出梯度 \(\nabla_\theta L(\theta)\)。如果我们像<a href="http://blog.tomtung.com/2011/10/linear-regression-gradient-descent/" target="_blank">上篇笔记</a>中一样定义：<br />
$$<br />
X := \begin{pmatrix} (x^{(1)})^{\rm T} \\ (x^{(2)})^{\rm T} \\ \vdots \\ (x^{(m)})^{\rm T} \end{pmatrix} \quad<br />
\vec{y} := \begin{pmatrix} y^{(1)} \\ y^{(2)} \\ \vdots \\ y^{(m)} \end{pmatrix} \quad<br />
\vec{h_\theta} := \begin{pmatrix} h_\theta(x^{(1)}) \\ h_\theta(x^{(2)}) \\ \vdots \\ h_\theta(x^{(m)}) \end{pmatrix}<br />
$$<br />
可以推导得到：<br />
$$ \nabla_\theta L(\theta) = \frac{1}{m} X^{\rm T} (\vec{y} &#8211; \vec{h_\theta}) $$<br />
接下来，根据梯度上升算法 <sup>[<a href="#logistic-regression-n-4" class="footnoted" id="to-logistic-regression-n-4">4</a>]</sup>，我们就可以迭代计算下式来逼近 \(\arg\max_\theta L(\theta)\)：<br />
$$<br />
\begin{eqnarray}<br />
\theta_{t+1} &amp;:=&amp; \theta_t + \alpha \nabla_\theta L(\theta) \\<br />
&amp;=&amp; \theta_t – \frac{\alpha}{m} X^{\rm T} (\vec{h_\theta} – \vec{y})<br />
\end{eqnarray}<br />
$$<br />
注意到这个更新式的形式和之前线性规划+最小二乘法+梯度下降得到是一样的，只是 \(\vec{h_\theta}\) 变了。</p>
<p>以上就是用梯度上升做 Logistic 回归的算法了。课上还谈到了另外 3 个问题：</p>
<ol>
<li>如果要把 Logistic 回归推广到多个分类的情况，可以利用一种叫“1 vs all”的方法。具体地说，即每次取一个分类为 positive，其它分类都为 negative。这样针对每一个分类都学习得到一个 Logistic 回归模型。要判断一个新数据点的标签，用每一个模型都测一下，取对应 \( h_\theta(x) \) 最大的分类为预测结果。</li>
<li>如果数据点不是线性可分的，类似线性回归到多项式回归（polynomial regression）的推广，可以将同一属性次数不同的项看成是彼此独立的，再和普通 Logistic 回归问题一样处理。</li>
<li>属性过多可能会造成模型过分复杂，导致过拟合问题。关于 overfit 和 underfit 的问题可以参见<a href="http://blog.pluskid.org/?p=39" target="_blank">这篇文章</a>的 3~6 段，这位学长讲得非常深入浅出了。课上提到的一种解决方法叫 regularization，即对 \(\theta\) 中希望加以限制的各项 \(\theta_i\)，在 \(L(\theta)\) 后加上 \(-\lambda \theta_i^2 \)，使这些值过大时“惩罚”目标函数。这里 \(\lambda > 0\) 的取值也要注意，如果取得太小解决不了过拟合问题，但取得太大又会造成 underfit。</li>
</ol>
<p>p.s. 今天早睡的目标又达成失败了……………………</p>

<hr/> 脚注： <br/> <ol class="footnotes">
	<li class="footnote" id="logistic-regression-n-1"><strong><sup>[1]</sup></strong> 和上节线性规划中一样，每个 \(x^{(i)}\) 是一个 \(n+1\) 维向量，为书写简便我们在 \(n\) 个属性前再加一维 \(x^{(i)}_0=1\)。 <a class="note-return" href="#to-logistic-regression-n-1">&#x21A9;</a></li>
	<li class="footnote" id="logistic-regression-n-2"><strong><sup>[2]</sup></strong> 这里说“不妨”似乎有点太随意了。事实上，如果假设在给定 \(x\) 的情况下 \(y\) 服从 0-1 分布，那么\(\phi\) 取 logistic 函数实际上可以由<a href="http://en.wikipedia.org/wiki/Generalized_linear_model" target="_blank">广义线性模式</a>（Generalized linear model）的假设推导得出。 <a class="note-return" href="#to-logistic-regression-n-2">&#x21A9;</a></li>
	<li class="footnote" id="logistic-regression-n-3"><strong><sup>[3]</sup></strong> 注意到和<a href="http://blog.tomtung.com/2011/10/linear-regression-gradient-descent/" target="_blank">上节课</a>讲的线性回归一样，我们要预测的 \(h_\theta(x)\) 其实都是期望 \(E_{y|x;\theta}\)。 <a class="note-return" href="#to-logistic-regression-n-3">&#x21A9;</a></li>
	<li class="footnote" id="logistic-regression-n-4"><strong><sup>[4]</sup></strong> 由于这里要求的是 \(L(\theta)\) 的<strong>最大值</strong>，因此是梯度上升而非梯度下降。课程视频中取要最小化的目标函数\(J(\theta)=-L(\theta)\)，因此是梯度下降。实际是一回事。 <a class="note-return" href="#to-logistic-regression-n-4">&#x21A9;</a></li></ol>
<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2011/10/linear-regression-gradient-descent/' rel='bookmark' title='[学习笔记] 线性回归（及梯度下降）'>[学习笔记] 线性回归（及梯度下降）</a></li>
<li><a href='http://blog.tomtung.com/2011/10/em-algorithm/' rel='bookmark' title='[学习笔记] Expectation-Maximization(EM) 算法'>[学习笔记] Expectation-Maximization(EM) 算法</a></li>
<li><a href='http://blog.tomtung.com/2011/10/plsa/' rel='bookmark' title='[学习笔记] Probabilistic latent semantic analysis (pLSA)'>[学习笔记] Probabilistic latent semantic analysis (pLSA)</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2011/10/logistic-regression/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>[学习笔记] 线性回归（及梯度下降）</title>
		<link>http://blog.tomtung.com/2011/10/linear-regression-gradient-descent/</link>
		<comments>http://blog.tomtung.com/2011/10/linear-regression-gradient-descent/#comments</comments>
		<pubDate>Sat, 22 Oct 2011 15:59:38 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[机器学习]]></category>
		<category><![CDATA[笔记]]></category>
		<category><![CDATA[算法]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/?p=1038</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2011/10/linear-regression-gradient-descent/" title="[学习笔记] 线性回归（及梯度下降）"></a>这个做的是 Stanford 的机器学习公开课的笔记。前两课很简单，吴大牛讲得又极清楚，我很多地方就简单记了。 线性回归解决的是一个回归问题（怎么像废话），即要预测的目标变量（target variable）是连续值。我们有一个大小为 \(m\) 的训练集，其中数据点 \( x^{(i)} \) 分别对应目标变量 \( y^{(i)} \) \( (1 \leq i \leq m) \)；每个 \(x^{(i)}\) 是一个 \(n+1\) 维向量，\(n\) 为一个数据点的属性（feature）数。（为后面书写简便我们在 \(n\) 个属性前再加一维 \( x_0^{(i)} = 1 \)，即 \( x^{(i)} = (1, x_1^{(i)}, x_2^{(i)}, &#8230;, &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2011/10/linear-regression-gradient-descent/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2011/10/em-algorithm/' rel='bookmark' title='[学习笔记] Expectation-Maximization(EM) 算法'>[学习笔记] Expectation-Maximization(EM) 算法</a></li>
<li><a href='http://blog.tomtung.com/2011/10/plsa/' rel='bookmark' title='[学习笔记] Probabilistic latent semantic analysis (pLSA)'>[学习笔记] Probabilistic latent semantic analysis (pLSA)</a></li>
<li><a href='http://blog.tomtung.com/2009/07/agile-development-reading-notes/' rel='bookmark' title='敏捷开发学习笔记（思维导图）'>敏捷开发学习笔记（思维导图）</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2011/10/linear-regression-gradient-descent/" title="[学习笔记] 线性回归（及梯度下降）"></a><p>这个做的是 <a href="http://www.ml-class.org/" target="_blank">Stanford 的机器学习公开课</a>的笔记。前两课很简单，吴大牛讲得又极清楚，我很多地方就简单记了。</p>
<p>线性回归解决的是一个回归问题（怎么像废话），即要预测的目标变量（target variable）是连续值。我们有一个大小为 \(m\) 的训练集，其中数据点 \( x^{(i)} \) 分别对应目标变量 \( y^{(i)} \) \( (1 \leq i \leq m) \)；每个 \(x^{(i)}\) 是一个 \(n+1\) 维向量，\(n\) 为一个数据点的属性（feature）数。（为后面书写简便我们在 \(n\) 个属性前再加一维 \( x_0^{(i)} = 1 \)，即 \( x^{(i)} = (1, x_1^{(i)}, x_2^{(i)}, &#8230;, x_n^{(i)})^{\rm T} \)。）给定这样一个训练集，我们希望知道对于某个训练集外的 \( x \)，它对应的 \( y \) 是什么。</p>
<p>线性回归的假设（hypothesis）是，数据点的属性 \(x\) 和对应的目标变量 \(y\) 是线性关系，可以用一条直线 \(  h_\theta(x) = \theta^{\rm T} x \) 来拟合 <sup>[<a href="#linear-regression-gradient-descent-n-1" class="footnoted" id="to-linear-regression-gradient-descent-n-1">1</a>]</sup>，就像下面这样（图片盗自维基）：</p>
<p><img class="alignnone" src="http://upload.tomtung.com/img/linear-regression.png" alt="" width="494" height="406" /></p>
<p>其中 \( \theta \) 也是一个 \(n+1\) 维向量，就是我们想拟合的参数，因为假如能求出 \( \theta \)，对于一个训练集外的点 \( x \) 我们就能预测它对应的 \( y = \theta^{\rm T} x \) 了。</p>
<p>为了评价我们估计的 \( \theta \) 的优劣，我们需要引入一个目标函数/费用函数（cost function）。根据<a href="http://en.wikipedia.org/wiki/Least_squares" target="_blank">最小二乘法</a>，我们定义下面的 \( J(\theta) \) 为我们需要最小化的目标函数：<br />
$$<br />
J(\theta) = \frac{1}{2m} \sum_i^m (h_\theta(x) &#8211; y^{(i)})^2<br />
$$<br />
其中 \( 1/2m \) 这个常系数并非必须，只是为了计算便利加上去的。我们要求的就是 \( \arg\min_\theta J(\theta) \)。</p>
<p>不过在继续之前我们先把 \( J(\theta) \) 的记法再进一步 vectorize 一下。我们定义一个 \( m \times n \) 的<a href="http://en.wikipedia.org/wiki/Design_matrix" target="_blank">设计矩阵</a> \( X \)，其第 \(i\) 行为 \( (x^{(i)})^{\rm T} \)，即<br />
$$<br />
X := \begin{pmatrix} (x^{(1)})^{\rm T} \\ (x^{(2)})^{\rm T} \\ \vdots \\ (x^{(m)})^{\rm T} \end{pmatrix}<br />
= \begin{pmatrix} 1 &amp; x_1^{(1)} &amp; x_2^{(1)} &amp; &#8230; &amp; x_n^{(1)} \\<br />
1 &amp; x_1^{(2)} &amp; x_2^{(2)} &amp; &#8230; &amp; x_n^{(2)} \\<br />
\vdots &amp; \vdots &amp; \vdots &amp; \ddots &amp; \vdots \\<br />
1 &amp; x_1^{(m)} &amp; x_2^{(m)} &amp; &#8230; &amp; x_n^{(m)} \end{pmatrix}<br />
$$<br />
我们再定义<br />
$$<br />
\begin{eqnarray}<br />
&#038;&#038;\vec{y} := (y^{(1)},y^{(2)},&#8230;,y^{(m)})^{\rm T} \\<br />
&#038;&#038;\vec{h_\theta} := \big( h_\theta(x^{(1)}),h_\theta(x^{(2)}),&#8230;,h_\theta(x^{(m)}) \big)^{\rm T} = X\theta<br />
\end{eqnarray}<br />
$$<br />
不难推出 \( J(\theta) \) 可以写成下面的形式：<br />
$$<br />
J(\theta) = \frac{1}{2m} (\vec{h_\theta} &#8211; \vec{y})^{\rm T} (\vec{h_\theta} &#8211; \vec{y})<br />
$$<br />
我们还可以接着求出 \( J(\theta) \) 的<a href="http://en.wikipedia.org/wiki/Gradient" target="_blank">梯度</a> \( \nabla_\theta J(\theta) \) <sup>[<a href="#linear-regression-gradient-descent-n-2" class="footnoted" id="to-linear-regression-gradient-descent-n-2">2</a>]</sup>：<br />
$$<br />
\nabla_\theta J(\theta) = \frac{1}{m} X^{\rm T} (\vec{h_\theta} &#8211; \vec{y}) = \frac{1}{m} X^{\rm T} (X\theta &#8211; \vec{y})<br />
$$</p>
<p>我们知道函数在极值处梯度为零，因此<strong>第一种解法</strong>就是直接求 \( \nabla_\theta J(\theta) = 0 \)。这样可以得到一个解析解：<br />
$$<br />
\theta = (X^{\rm T} X)^{-1} X^{\rm T} \vec{y}<br />
$$<br />
注意其中 \( X^{\rm T} X \) 是一个 \( n \times n \) 的矩阵，而矩阵求逆的的复杂度一般是 \( O(n^3) \)。所以这种解法在 \(n\) 比较小的时候非常高效，但在 \(n\) 较大时就瞎了。</p>
<p>我们还知道 \( J(\theta) \) 上某一点的梯度向量指向 \( J(\theta) \) 增长最快的方向，长度为这个增长的变化率。因此<strong>第二种解法</strong>就是让 \( \theta \) 的值向着 \( -\nabla_\theta J(\theta) \) 的方向，即 \( J(\theta) \) 减小最快的方向迭代变化，这样就可以逼近 \( J(\theta) \) 的极小值了。因此我们可以得到下面被称为<a href="http://en.wikipedia.org/wiki/Gradient_descent" target="_blank">梯度下降</a>的算法：</p>
<blockquote><p>Repeat until converge {$$<br />
\begin{eqnarray}<br />
\theta_{t+1} &#038;:=&#038; \theta_t &#8211; \alpha \nabla_\theta J(\theta) \\<br />
&#038;=&#038; \theta_t &#8211; \frac{\alpha}{m} X^{\rm T} (\vec{h_\theta} &#8211; \vec{y})<br />
\end{eqnarray}<br />
$$}</p></blockquote>
<p><sup>[<a href="#linear-regression-gradient-descent-n-3" class="footnoted" id="to-linear-regression-gradient-descent-n-3">3</a>]</sup> 其中 \(\alpha\) 称为学习速率（learning rate），意思就是……学习的速率= = 这个参数控制着每次迭代 \(\theta\) 值变化的保守或激进程度；如果太大，一次迭代对 \(\theta\) 改变过大，导致“冲得太猛”越过了极值点没法收敛；如果太小，一次迭代对 \(\theta\) 改变太少，算法收敛太慢。决定 \( \alpha \) 的方法是……呃，就是试，“0.01 太小，0.1 太大，0.03 有点小，咦 0.06 正好”这样。</p>
<p>对于线性回归和梯度下降，课上还谈到了另外两个话题。一是属性缩放（feature scaling）。如果不同属性数量级差得太大，会严重影响梯度下降的性能。这时可以对分别对每项属性做类似“减去均值，除以标准差”这样的缩放。二是<a href="http://en.wikipedia.org/wiki/Polynomial_regression" target="_blank">多项式回归</a>（polynomial regression）。属性值和目标变量值之间的关系可能不是线性的，而是多项式的关系，比如 \( y = \theta_0 + \theta_1 x + \theta_2 x^2 \) 这样。但由于这个式子对于 \( \theta \) 仍然是线性的，所以用最小二乘法时可以直接把 \(x\)、\(x^2\) 看成是彼此独立的不同的属性，然后按上面的方法如法炮制。至于多项式回归时属性的次数怎么取，应该会在讲 bias-variance trade-off 的时候讲吧，我到时候再写好了。</p>

<hr/> 脚注： <br/> <ol class="footnotes">
	<li class="footnote" id="linear-regression-gradient-descent-n-1"><strong><sup>[1]</sup></strong> 这里讲得有点 sloppy 了（不过你能明白我的意思就行=.=）。更严格一点说，应该是训练集中的数据满足：<br />
$$<br />
y^{(i)} = \theta^{\rm T} x^{(i)} + \epsilon^{(i)}<br />
$$<br />
其中 \( \epsilon^{(i)} \) 表示误差；各 \( \epsilon^{(i)} \) 满足均值为零，方差相同且互不相关（参见<a href="http://en.wikipedia.org/wiki/Gauss%E2%80%93Markov_theorem" target="_blank">高斯-马尔科夫定理</a>）。</p>
<p>顺带一提，我们如果进一步假设 \( \epsilon^{(i)} \sim N(0,\sigma^2) \)，有 \( y^{(i)}|x^{(i)};\theta \sim N(\theta^{\rm T} x^{(i)},\sigma^2) \)，则这个模型成为<a href="http://en.wikipedia.org/wiki/Generalized_linear_model" target="_blank">广义线性模式</a>（Generalized linear model）的一个特例。我们用极大log似然法<br />
$$ \arg\max_\theta L(\theta) = \arg\max_\theta \sum_i \log(P(y^{(i)}|x^{(i)};\theta)) $$<br />
可以推出和下面提到的最小二乘法一样的形式。——如果你不知道我上面在说什么，忽略吧，不碍得的。 <a class="note-return" href="#to-linear-regression-gradient-descent-n-1">&#x21A9;</a></li>
	<li class="footnote" id="linear-regression-gradient-descent-n-2"><strong><sup>[2]</sup></strong> 推导其实挺麻烦的。教授在课上没讲，我也懒得敲了=.= <a class="note-return" href="#to-linear-regression-gradient-descent-n-2">&#x21A9;</a></li>
	<li class="footnote" id="linear-regression-gradient-descent-n-3"><strong><sup>[3]</sup></strong> 注意这里每次迭代的运算都会考虑整个训练集，这称为批量梯度下降（batch gradient descent），当训练集很大时这可能导致很大的开销。我们也可以每次迭代只依次选取训练集中的一个实例 \(x^{(i)}\)，更新<br />
$$<br />
\theta_{t+1} := \theta_{t} &#8211; \alpha (\theta^{\rm T} x^{(i)} – y^{(i)}) x^{(i)}<br />
$$<br />
这个叫增量梯度下降（incremental gradient descent）。好处在于每次迭代开销小，因而收敛速度较快，在处理大数据集时可能很有用。但坏处在于这算法可能永远都收敛不到极小值，而是一直围着极小值转——不过在实际应用中这个也能接受了。 <a class="note-return" href="#to-linear-regression-gradient-descent-n-3">&#x21A9;</a></li></ol>
<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2011/10/em-algorithm/' rel='bookmark' title='[学习笔记] Expectation-Maximization(EM) 算法'>[学习笔记] Expectation-Maximization(EM) 算法</a></li>
<li><a href='http://blog.tomtung.com/2011/10/plsa/' rel='bookmark' title='[学习笔记] Probabilistic latent semantic analysis (pLSA)'>[学习笔记] Probabilistic latent semantic analysis (pLSA)</a></li>
<li><a href='http://blog.tomtung.com/2009/07/agile-development-reading-notes/' rel='bookmark' title='敏捷开发学习笔记（思维导图）'>敏捷开发学习笔记（思维导图）</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2011/10/linear-regression-gradient-descent/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>[学习笔记] Probabilistic latent semantic analysis (pLSA)</title>
		<link>http://blog.tomtung.com/2011/10/plsa/</link>
		<comments>http://blog.tomtung.com/2011/10/plsa/#comments</comments>
		<pubDate>Wed, 19 Oct 2011 04:21:21 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[EM 算法]]></category>
		<category><![CDATA[Topic Model]]></category>
		<category><![CDATA[机器学习]]></category>
		<category><![CDATA[笔记]]></category>
		<category><![CDATA[算法]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/?p=923</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2011/10/plsa/" title="[学习笔记] Probabilistic latent semantic analysis (pLSA)"></a>Probabilistic latent semantic analysis (概率潜在语义分析，pLSA) 是一种 Topic model，在99年被 Thomas Hofmann 提出。它和随后提出的 LDA 使得 Topic Model 成为了研究热点，其后的模型大都是建立在二者的基础上的。 我们有时会希望在数量庞大的文档库中自动地发现某些结构。比如我们希望在文档库发现若干个“主题”，并将每个主题用关键词的形式表现出来。我们还希望知道每篇文章中各个主题占得比重如何，并据此判断两篇文章的相关程度。而 pLSA 就能完成这样的任务。 我之前取了 Wikinews 中的 1000 篇新闻，试着用 pLSA 在其中发现 K=15 个主题。比如一篇关于 Wikileaks 的阿萨奇被保释消息的新闻，算法以 100% 的概率把它分给了主题 9，其关键词为： media phone hacking wikileaks assange australian stated &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2011/10/plsa/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2011/10/em-algorithm/' rel='bookmark' title='[学习笔记] Expectation-Maximization(EM) 算法'>[学习笔记] Expectation-Maximization(EM) 算法</a></li>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1014/' rel='bookmark' title='[VIJOS 1014]旅行商简化版'>[VIJOS 1014]旅行商简化版</a></li>
<li><a href='http://blog.tomtung.com/2007/07/noi05-cckk/' rel='bookmark' title='[NOI 05]聪聪与可可'>[NOI 05]聪聪与可可</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2011/10/plsa/" title="[学习笔记] Probabilistic latent semantic analysis (pLSA)"></a>
<p>Probabilistic latent semantic analysis (概率潜在语义分析，pLSA) 是一种 <a href="http://en.wikipedia.org/wiki/Topic_model" target="_blank">Topic model</a>，在99年被 Thomas Hofmann 提出。它和随后提出的 <a href="http://en.wikipedia.org/wiki/Latent_Dirichlet_allocation" target="_blank">LDA</a> 使得 Topic Model 成为了研究热点，其后的模型大都是建立在二者的基础上的。</p>
<p>我们有时会希望在数量庞大的文档库中自动地发现某些结构。比如我们希望在文档库发现若干个“主题”，并将每个主题用关键词的形式表现出来。我们还希望知道每篇文章中各个主题占得比重如何，并据此判断两篇文章的相关程度。而 pLSA 就能完成这样的任务。</p>
<p>我之前取了 <a href="http://en.wikinews.org/" target="_blank">Wikinews</a> 中的 1000 篇新闻，试着用 pLSA 在其中发现 K=15 个主题。比如一篇关于 <a href="http://en.wikinews.org/wiki/Wikileaks_founder_Julian_Assange_granted_bail,_set_free" target="_blank">Wikileaks 的阿萨奇被保释消息</a>的新闻，算法以 100% 的概率把它分给了主题 9，其关键词为：</p>
<blockquote><p>media phone hacking wikileaks assange<br />
australian stated information investigation murdoch</p></blockquote>
<p>可以看到这个主题的发现还是非常靠谱的。又比如这条<a href="http://en.wikinews.org/wiki/North_Korea_warns_of_'self-defensive_blows,'_nuclear_war,_if_military_exercises_take_place" target="_blank">中国人民的老朋友威胁要大打打核战争</a>的新闻。算法把它以 97.7% 的概率分给了主题 3，2.3% 的概率分给了主题 7。主题 3 的关键词是：</p>
<blockquote><p>south north court china military<br />
death tornado service million storm</p></blockquote>
<p>主题 7 的关键词是：</p>
<blockquote><p>nuclear plant power japan million<br />
carbon radiation china water minister</p></blockquote>
<p>可以看到这条新闻和主题 3 中的“南北”、“军事”、“中国”、“死亡”这些信息联系在一起，和主题 7 中的“核”、“中国”联系在一起。应该是因为我的数据集中与北朝鲜核问题相关的新闻只有不到 10 条，而 10 个主题的划分并不够细致，所以关于“朝核问题”或者“核武器”的这样的主题并没能被分离出来。但可以看到即使是这样结果也是很 make sense 的。</p>
<p>那我们就来看看 pLSA 模型是怎么回事吧。和很多模型一样，pLSA 遵从 bag-of-words 假设，即只考虑一篇文档中单词出现的次数，忽略单词的先后次序关系，且每个单词的出现都是彼此独立的。这样一来，我们观察到的其实就是每个单词 \(w \in W\) 在每篇文档 \(d \in D\) 中出现的次数 \(n(w,d)\)。pLSA 还假设对于每对出现的 \((d,w)\) 都对应着一个表示“主题”的隐藏变量 \(z \in Z\)。pLSA 是一个<a href="http://en.wikipedia.org/wiki/Generative_model" target="_blank">生成模型</a>，它假设 \(d\)、\(w\) 和 \(z\) 之间的关系用<a href="http://en.wikipedia.org/wiki/Bayesian_network" target="_blank">贝叶斯网络</a>表示是这样的（从 (Blei et al., 2003) 偷的图）：</p>
<p><img src="http://upload.tomtung.com/img/plsa_graph.png" width="414" height="154" alt="" /></p>
<p>实心的节点 \(d\) 和 \(w\) 表示我们能观察到的文档和单词，空心的 \(z\) 表示我们观察不到的隐藏变量，用来表示隐含的主题。图中用了所谓的“<a href="http://en.wikipedia.org/wiki/Plate_notation" target="_blank">盘子记法</a>”，即用方框表示随机变量的重复。这里方框右下角的字母 \(M\) 和 \(N\) 分别表示有 \(M\) 篇文档，第 \(j\) 篇文档有 \(N_j\) 个单词。每条有向边表示随机变量间的依赖关系。也就是说，pLSA 假设每对 \((d,w)\) 都是由下面的过程产生的：</p>
<ol>
<li>以 \(P(d)\) 的先验概率选择一篇文档 \(d\)</li>
<li>选定 \(d\) 后，以 \(P(z|d)\) 的概率选中主题 \(z\)</li>
<li>选中主题 \(z\) 后，以 \(P(w|z)\) 的概率选中单词 \(w\)</li>
</ol>
<p>而我们感兴趣的正是其中的 \(P(z|d)\) 和 \(P(w|z)\)：利用前者我们可以知道每篇文章中各主题所占的比重，利用后者我们则能知道各单词在各主题中出现的概率，从而进一步找出各主题的“关键词”。记 \(\theta = (P(z|d), P(w|z))\)，表示我们希望估计的模型参数。当然 \(\theta\) 不仅仅代表两个数，而是对于每对 \( (w^{(j)}, z^{(k)}) \) 和 \( (d^{(i)}, z^{(k)}) \) ，我们都要希望知道 \( P(z^{(k)}|d^{(i)}) \) 和 \( P(w^{(j)}|z^{(k)}) \) 的值。也就是说，模型中共有 \( |Z| \cdot |D| + |W| \cdot |Z| \) 个参数。我们还知道：<br />
$$<br />
\begin{eqnarray}<br />
P(d,w) &amp;=&amp; P(d)P(w|d) \\<br />
P(w|d) &amp;=&amp; \sum_z P(w|z)P(z|d)<br />
\end{eqnarray}<br />
$$<br />
根据最大log似然估计法，我们要求的就是<br />
$$<br />
\begin{eqnarray}<br />
\arg\max_\theta L(\theta)<br />
&amp;=&amp; \arg\max_\theta \sum_{d,w} n(d,w)\log P(d,w;\theta) \\<br />
&amp;=&amp; \arg\max_\theta \sum_{d,w} n(d,w)\log P(w|d;\theta)P(d) \\<br />
&amp;=&amp; \arg\max_\theta \left\{ \sum_{d,w} n(d,w)\log P(w|d;\theta) + \sum_{d,w} n(d,w)\log P(d) \right\}<br />
\end{eqnarray}<br />
$$<br />
这里由于 \( \sum_{d,w} n(d,w)\log P(d) \) 这一项与 \(\theta\) 无关，在 \(\arg\max_\theta\) 中可以被直接扔掉。<sup>[<a href="#plsa-n-1" class="footnoted" id="to-plsa-n-1">1</a>]</sup> 因此<br />
$$<br />
\begin{eqnarray}<br />
\arg\max_\theta L(\theta)<br />
&amp;=&amp; \arg\max_\theta \sum_{d,w} n(d,w)\log P(w|d;\theta) \\<br />
&amp;=&amp; \arg\max_\theta \sum_{d,w} n(d,w)\log \sum_z P(w|z)P(z|d)<br />
\end{eqnarray}<br />
$$</p>
<p>这里出现了 \( \log \) 套 \( \sum \) 的形式，导致很难直接拿它做最大似然。但假如能观察到 \(z\)，问题就很简单了。于是我们想到根据 EM 算法（参见我的<a href="http://blog.tomtung.com/2011/10/em-algorithm/" target="_blank">上篇笔记</a>），可以用下式迭代逼近 \( \arg\max_\theta L(\theta) \)：<br />
$$ \arg\max_\theta Q_t(\theta) = \arg\max_\theta \sum_{d,w} n(d,w) E_{z|d,w;\theta_t}[\log P(w|d;\theta)] $$<br />
其中<br />
$$<br />
\begin{eqnarray}<br />
E_{z|d,w;\theta_t}[\log P(w|d;\theta)]<br />
&amp;=&amp; \sum_z P(z|d,w;\theta_t) \log P(w|d;\theta) \\<br />
&amp;=&amp; \sum_z P(z|d,w;\theta_t) [\log P(w|z) + \log P(z|d)] \\<br />
\end{eqnarray}<br />
$$<br />
在 E-step 中，我们需要求出 \( Q_t(\theta) \) 中除 \(\theta\) 外的其它未知量，也就是说对于每组 \( (d^{(i)}, w^{(j)}, z^{(k)}) \) 我们都需要求出 \( P(z^{(k)}|d^{(i)},w^{(j)};\theta_t) \)。根据<a href="http://en.wikipedia.org/wiki/Bayes%27_theorem" target="_blank">贝叶斯定理</a>，我们知道：<br />
$$<br />
P(z^{(k)}|d^{(i)},w^{(j)};\theta_t) = \frac{P_t(z^{(k)}|d^{(i)})P_t(w^{(j)}|z^{(k)})} {\sum_z P_t(z|d^{(i)})P_t(w^{(j)}|z)}<br />
$$<br />
而 \( P_t(z|d) \) 和 \( P_t(w|z) \) 就是上轮迭代求出的 \( \theta_t \)。这样就完成了 E-step。</p>
<p>接下来 M-step 就是要求 \( \arg\max_\theta Q_t(\theta) \) 了。利用基本的微积分工具 <sup>[<a href="#plsa-n-2" class="footnoted" id="to-plsa-n-2">2</a>]</sup>，可以分别对每对 \( (w^{(j)}, z^{(k)}) \) 和 \( (d^{(i)}, z^{(k)}) \) 求出：<br />
$$<br />
\begin{eqnarray}<br />
P_{t+1}(w^{(j)}|z^{(k)}) = \frac {\sum_d n(d,w^{(j)})P(z^{(k)}|d,w^{(j)};\theta_t)} {\sum_{d,w} n(d,w)P(z^{(k)}|d,w;\theta_t)} \\<br />
P_{t+1}(z^{(k)}|d^{(i)}) = \frac {\sum_w n(d^{(i)},w)P(z^{(k)}|d^{(i)},w;\theta_t)} {\sum_{w,z} n(d,w)P(z|d^{(i)},w;\theta_t)}<br />
\end{eqnarray}<br />
$$<br />
以上就是 pLSA 算法了。最后贴个我用 MATLAB 写的实现 <sup>[<a href="#plsa-n-3" class="footnoted" id="to-plsa-n-3">3</a>]</sup>：</p>
<pre class="brush:matlab">function [p_w_z, p_z_d, Lt] = pLSA(n_dw, n_z, iter_num)
% PLSA	Fit a pLSA model on given data
%       in which n_dw(d,w) is the number of occurrence of word w
%		in document d, d, n_z is the number of topics to be discovered
%		

% pre-allocate space
[n_d, n_w] = size(n_dw); % max indices of d and w
p_z_d = rand(n_z, n_d); % p(z|d)
p_w_z = rand(n_w, n_z); % p(w|z)
n_p_z_dw = cell(n_z, 1); % n(d,w) * p(z|d,w)
for z = 1:n_z
    n_p_z_dw{z} = sprand(n_dw);
end

p_dw = sprand(n_dw); % p(d,w)
Lt = []; % log-likelihood
for i = 1:iter_num
    %disp('E-step');
    for d = 1:n_d
        for w = find(n_dw(d,:))
            for z = 1:n_z
                n_p_z_dw{z}(d,w) = p_z_d(z,d) * p_w_z(w,z) * ...
					n_dw(d,w) / p_dw(d, w);
            end
        end
    end

    %disp('M-step');
    %disp('update p(z|d)')
    concat = cat(2, n_p_z_dw{:}); % make n_p_z_dw{:}(d,:)) possible
    for d = 1:n_d
        for z = 1:n_z
            p_z_d(z,d) = sum(n_p_z_dw{z}(d,:));
        end
        p_z_d(:,d) = p_z_d(:,d) / sum(concat(d,:));
    end

    %disp('update p(w|z)')
    for z = 1:n_z
        for w = 1:n_w
            p_w_z(w,z) = sum(n_p_z_dw{z}(:,w));
        end
        p_w_z(:,z) = p_w_z(:,z) / sum(n_p_z_dw{z}(:));
    end

    % update p(d,w) and calculate likelihood
    L = 0;
    for d = 1:n_d
        for w = find(n_dw(d,:))
            p_dw(d,w) = 0;
            for z = 1:n_z
                p_dw(d,w) = p_dw(d,w) + p_w_z(w,z) * p_z_d(z,d);
            end
            L = L + n_dw(d,w) * log(p_dw(d, w));
        end
    end

    Lt = [Lt; L];
    %plot(Lt); ylim([2*median(Lt)-L-0.1 L+(L-median(Lt))/2+0.1]);
    %drawnow; pause(0.1)
end

end</pre>
<p>第一次拿 Mablab 写程序，比较丑……<sup>[<a href="#plsa-n-4" class="footnoted" id="to-plsa-n-4">4</a>]</sup> 下图是 Log 似然度随迭代收敛的情况。可以看到收敛速度还是相对较快的。而且由于是 EM 算法的缘故，Log 似然度确实是单调上升的.</p>
<p><img src="http://upload.tomtung.com/img/plsa-converge.png" width="561" height="420" alt="" /></p>
<p>最后，pLSA 的问题是在文档的层面上没有一个概率模型，每篇文档的 P(d|z) 都是需要拟合的模型参数。这就导致参数的数目会随文档数目线性增长、不能处理训练集外的文档这样的问题。所以02年 David Blei、Andrew Ng（就是正在 <a href="http://www.ml-class.org/" target="_blank">ml-class.org</a> 里上公开课的那位） 和 Michael Jordan 又提出了一个更为简洁的模型：LDA。有时间的话下次再写了。</p>
<h2>参考文献</h2>
<p>(Hofmann, 1999) Hofmann, T. 1999. Probabilistic latent semantic indexing. Proceedings of the 22nd annual international ACM SIGIR conference on Research and development in information retrieval SIGIR 99. pages, (1999), 50-57.<br />
(Gildea &amp; Hofmann, 1999) Gildea, D. and Hofmann, T. 1999. Topic-based language models using EM. Proceedings of the 6th European Conference on Speech (1999), 2167-2170.<br />
(Brants, 2005) Brants, T. 2005. Test Data Likelihood for PLSA Models. Information Retrieval. (2005), 181-196.<br />
(Blei et al., 2003) Blei, D.M. et al. 2003. Latent Dirichlet Allocation. Journal of Machine Learning Research. 3, 4-5 (2003), 993-1022.</p>

<hr/> 脚注： <br/> <ol class="footnotes">
	<li class="footnote" id="plsa-n-1"><strong><sup>[1]</sup></strong> 这里 Hofmann 自己在 (Hofmann, 1999) 和 (Gildea &amp; Hofmann, 1999) 中使用了不同的形式。本文和 (Gildea &amp; Hofmann, 1999)、(Brants, 2005) 一样选择不去理会 \(P(d)\)。因为正如 (Brants, 2005) 中指出、(Blei et al., 2003) 及很多其它文献吐槽的那样，(Hofmann, 1999) 中的模型算出的 \(P(d)\) 实在坑爹，当 \(d\) 不在训练集中时 \(P(d)\) 就一律为0，没什么意义，还不如别估计它呢。另外，(Hofmann, 1999) 中额外引入了一个参数 \(\beta\) 来“解决”过度拟合问题，但 (Brants, 2005) 中指出这一问题实际并不存在，因此本文也对此忽略不提。 <a class="note-return" href="#to-plsa-n-1">&#x21A9;</a></li>
	<li class="footnote" id="plsa-n-2"><strong><sup>[2]</sup></strong> 具体而言，这里要求的是 \( Q_t(\theta) \) 在 \( \sum_w P(w|z) = 1 \) 和 \( \sum_z P(z|d) = 1 \) 约束条件下的极值。根据<a href="http://en.wikipedia.org/wiki/Lagrange_multiplier" target="_blank">拉格朗日乘数法</a>，解：</p>
<p>$$<br />
\nabla_\theta \left( Q(\theta) + \sum_z \alpha_z (\sum_w P(w|z) -1) + \sum_d \beta_d (\sum_z P(z|d) -1) \right) = \mathbf{0}<br />
$$</p>
<p> <a class="note-return" href="#to-plsa-n-2">&#x21A9;</a></li>
	<li class="footnote" id="plsa-n-3"><strong><sup>[3]</sup></strong> 完整的程序和数据在<a href="https://github.com/tomtung/Learning-Machine-Learning/tree/master/MATLAB/pLSA" target="_blank">这里</a>。 <a class="note-return" href="#to-plsa-n-3">&#x21A9;</a></li>
	<li class="footnote" id="plsa-n-4"><strong><sup>[4]</sup></strong> 吐槽：用 Matlab 做简单字符串处理怎么都那么恶心！长度不同的字符串竟然算是不同类型的！Cell array 怎么那么难用！ <a class="note-return" href="#to-plsa-n-4">&#x21A9;</a></li></ol>
<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2011/10/em-algorithm/' rel='bookmark' title='[学习笔记] Expectation-Maximization(EM) 算法'>[学习笔记] Expectation-Maximization(EM) 算法</a></li>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1014/' rel='bookmark' title='[VIJOS 1014]旅行商简化版'>[VIJOS 1014]旅行商简化版</a></li>
<li><a href='http://blog.tomtung.com/2007/07/noi05-cckk/' rel='bookmark' title='[NOI 05]聪聪与可可'>[NOI 05]聪聪与可可</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2011/10/plsa/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>[学习笔记] Expectation-Maximization(EM) 算法</title>
		<link>http://blog.tomtung.com/2011/10/em-algorithm/</link>
		<comments>http://blog.tomtung.com/2011/10/em-algorithm/#comments</comments>
		<pubDate>Wed, 12 Oct 2011 16:54:04 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[EM 算法]]></category>
		<category><![CDATA[机器学习]]></category>
		<category><![CDATA[概率]]></category>
		<category><![CDATA[笔记]]></category>
		<category><![CDATA[算法]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/?p=591</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2011/10/em-algorithm/" title="[学习笔记] Expectation-Maximization(EM) 算法"></a>果然还是有必要保持记笔记的习惯呐。前两天实验室讲 pLSA 的推导，用到 EM 竟然完全记不清了，竟然还把 Jensen 不等式和 SVM 的 minmax=maxmin 什么的记混= = （这么一说 SVM 具体怎么回事也记不清了。。。）赶紧补一篇复习笔记。 Expectation-Maximization 算法是统计学中用来给带隐含变量的模型做最大似然（和最大后验概率）的一种方法。EM 的应用特别广泛，经典的比如做概率密度估计用的 Gaussian Mixture Model。这两天我或许还会写 pLSA 的笔记放上来，也是 EM 应用的例子。 下面我会先解释 EM 算法要解决的问题，然后直接给出算法的过程，最后再说明算法的正确性。 问题 首先我们定义要解决的问题。给定一个训练集 \(X=\{x^{(1)},&#8230;,x^{(m)}\}\)，我们希望拟合包含隐含变量 \(z\) 的模型 \(P(x,z;\theta)\) 中的参数 \(\theta\)。根据模型的假设，每个我们观察到的 \(x^{(i)}\) 还对应着一个我们观察不到的隐含变量 \(z^{(i)}\)，我们记 \(Z=\{z^{(1)},&#8230;,z^{(m)}\}\)。做最大对数似然就是要求 &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2011/10/em-algorithm/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 索引'>C++ Primer 读书笔记 &#8211; 索引</a></li>
<li><a href='http://blog.tomtung.com/2009/07/agile-development-reading-notes/' rel='bookmark' title='敏捷开发学习笔记（思维导图）'>敏捷开发学习笔记（思维导图）</a></li>
<li><a href='http://blog.tomtung.com/2008/09/cpp-primer-reding-notes-chapter8/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第八章'>C++ Primer 读书笔记 &#8211; 第八章</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2011/10/em-algorithm/" title="[学习笔记] Expectation-Maximization(EM) 算法"></a><p><br />
果然还是有必要保持记笔记的习惯呐。前两天实验室讲 <a href="http://en.wikipedia.org/wiki/PLSA" target="_blank">pLSA</a> 的推导，用到 EM 竟然完全记不清了，竟然还把 <a href="http://en.wikipedia.org/wiki/Jensen's_inequality" target="_blank">Jensen 不等式</a>和 SVM 的 minmax=maxmin 什么的记混= = （这么一说 SVM 具体怎么回事也记不清了。。。）赶紧补一篇复习笔记。</p>
<p>Expectation-Maximization 算法是统计学中用来给带<a href="http://en.wikipedia.org/wiki/Latent_variable" target="_blank">隐含变量</a>的模型做最大似然（和最大后验概率）的一种方法。EM 的应用特别广泛，经典的比如做概率密度估计用的 <a href="http://en.wikipedia.org/wiki/Gaussian_mixture_model" target="_blank">Gaussian Mixture Model</a>。这两天我或许还会写 <a href="http://en.wikipedia.org/wiki/PLSA" target="_blank">pLSA</a> 的笔记放上来，也是 EM 应用的例子。</p>
<p>下面我会先解释 EM 算法要解决的问题，然后直接给出算法的过程，最后再说明算法的正确性。</p>
<h1>问题</h1>
<p>首先我们定义要解决的问题。给定一个训练集 \(X=\{x^{(1)},&#8230;,x^{(m)}\}\)，我们希望拟合包含隐含变量 \(z\) 的模型 \(P(x,z;\theta)\) 中的参数 \(\theta\)。根据模型的假设，每个我们观察到的 \(x^{(i)}\) 还对应着一个我们观察不到的隐含变量 \(z^{(i)}\)，我们记 \(Z=\{z^{(1)},&#8230;,z^{(m)}\}\)。做最大对数似然就是要求 \( \theta \) 的“最优值”：$$\theta=\arg\max_\theta{L(\theta;X)}$$ 其中 $$L(\theta;X)=log{P(X;\theta)}=\log{\sum_Z P(X,Z;\theta)}$$</p>
<p>想用这个\(\log\)套\(\sum\)的形式直接求解 \(\theta\) 往往非常困难。而如果能观察到隐含变量 \(z\) ，求下面的似然函数的极大值会容易许多：$$L(\theta;X,Z)=\log{P(X, Z;\theta)}$$</p>
<p>问题是实际上我们没法观察到 \(z\) 的值，只能在给定 \(\theta\) 时求 \(z\) 的后验概率 \(P(z|x;\theta)\) <sup>[<a href="#em-algorithm-n-1" class="footnoted" id="to-em-algorithm-n-1">1</a>]</sup>。EM 算法就可以帮我们打破这样的困境。</p>
<h1>算法</h1>
<p>下面给出 EM 算法的过程。其中\(\theta_t\) 是第 t-1 次迭代时算出的 \(\theta\) 值；\(\theta_0\) 为任意初值。</p>
<blockquote><p>Repeat until converge {</p>
<ol>
<li>(E-step) Calculate \( P(Z|X;\theta_t) \), in order to get:<br />
$$<br />
\begin{eqnarray}<br />
E_{Z|X;\theta_t}[L(\theta;X,Z)] &amp;:=&amp; E_{Z|X;\theta_t}[\log{P(X,Z;\theta)}] \\<br />
&amp;=&amp; \sum_Z P(Z|X;\theta_t) \log{P(X,Z;\theta)}<br />
\end{eqnarray}<br />
$$
</li>
<li>(M-step) $$\theta_{t+1} := \arg\max_\theta E_{Z|X;\theta_t}[\log{P(X,Z;\theta)}]$$</li>
</ol>
<p>}</p></blockquote>
<p>对，就这么短。所以我总觉得称之为 algorithm 不如称之为 method 更恰当。上面的过程在收敛后就得到了我们需要的 \( \theta=\arg\max_\theta{L(\theta;X)} \) <sup>[<a href="#em-algorithm-n-2" class="footnoted" id="to-em-algorithm-n-2">2</a>]</sup>。</p>
<p>先简单说说这短短两步都做了些啥。EM 算法每次迭代都建立在上轮迭代对 \(\theta\) 的最优值的估计 \(\theta_t\) 上，利用它可以求出 \(Z\) 的后验概率 \(P(Z|X;\theta_t)\)，进而求出 \(L(\theta;X,Z)\) 在分布 \(Z \sim P(Z|X;\theta)\) 上的期望 \(E_{Z|X;\theta_t}[L(\theta;X,Z)]\)。在第一节中我们提到 \( \arg\max_\theta L(\theta;X,Z) \) 在未知 \(Z\) 的情况下难以直接计算，于是 EM 算法就转而通过最大化它的期望 \(E_{Z|X;\theta_t}[L(\theta;X,Z)]\) 来逼近 \(\theta\) 的最优值，得到 \(\theta_{t+1}\) 。注意由于 \(L(\theta;X,Z)\) 的这个期望是在 \(Z\) 的一个分布上求的，这样得到的表达式就只剩下 \(\theta\) 一个未知量，因而绕过了 \(z\) 未知的问题。而 \(\theta_{t+1}\) 又可以作为下轮迭代的基础，继续向最优逼近。算法中 E-step 就是在利用 \(\theta_t\) 求期望 \(E_{Z|X;\theta_t}[L(\theta;X,Z)]\)，这就是所谓“Expectation”；M-step 就是通过寻找 \(\theta_{t+1}\) 最大化这个期望来逼近 \( \theta \) 的最优值，这就叫“Maximization”。EM 算法因此得名。</p>
<p>另外，如果数据满足独立同分布的条件，分别枚举 \(z^{(i)}\) 的值可能要比枚举整个 \(Z\) 方便些，可把 \(E_{Z|X;\theta_t}[L(\theta;X,Z)]\) 替换成：<br />
$$<br />
\begin{eqnarray}<br />
\sum_i E_{z^{(i)}|x^{(i)};\theta_t}[L(\theta;x^{(i)},z^{(i)}]<br />
&amp;:=&amp; \sum_i E_{z^{(i)}|x^{(i)};\theta_t}[\log{P(x^{(i)},z^{(i)};\theta)}] \\<br />
&amp;=&amp; \sum_i \sum_{z^{(i)}} P(z^{(i)}|x^{(i)};\theta_t) \log{P(x^{(i)},z^{(i)};\theta)}<br />
\end{eqnarray}<br />
$$</p>
<h1>原理</h1>
<p>为什么这样 E一步，M一步，一步E，一步M，就能逼近极大似然？具体而言，为什么通过迭代计算 \(\arg\max_\theta E_{Z|X;\theta_t}[L(\theta;X,Z)]\) 可以逼近 \(\theta\) 的最优值 \(\arg\max_\theta L(\theta;X,Z)\)？我们稍后会看到，这是因为每次迭代得到的 \(\theta_{t+1}\) 一定比 \(\theta_t\) 更优，即算法是在对 \(\theta\) 的最优值做单调逼近。</p>
<p>不过首先让我们先抛开最大似然，考虑一个更一般的问题。假设有一个凹函数 \( F(\theta) \)，我们想求 \( \arg\max_\theta F(\theta) \)，但直接求很困难。不过对于任意给定的 \( \theta_t \)，假设我们都能找到 \( F(\theta) \) 的一个下界函数 \( G_{\theta_t}(\theta) \)，满足 \( F(\theta) \geq G_{\theta_t}(\theta) \) 且 \( F(\theta_t) = G_{\theta_t}(\theta_t) \) ——我管 \( G_{\theta_t}(\theta) \) 这样的函数叫 \( F \) 的“在 \( \theta_t \) 处相等的下界函数”。现在考虑 \( \theta_{t+1} := \arg\max_\theta G_{\theta_t}(\theta) \)，它一定会满足：<br />
$$ F(\theta_{t+1}) \geq G_{\theta_t}(\theta_{t+1}) \geq G_{\theta_t}(\theta_t) = F(\theta_t) $$<br />
也就是说，\( \theta_{t+1} \) 一定比 \( \theta_t \) 更优。而接下来我们又可以用 \( \theta_{t+1} \) 找到一个 \( G_{\theta_{t+1}}(\theta) \)，再据此算出比 \( \theta_{t+1} \) 还优的 \( \theta_{t+2} := \arg\max_\theta G_{\theta_{t+1}}(\theta) \) 。如此不断迭代，就能不断步步逼近  \( \theta \) 的最优值了。由此可见，如果对任意 \( \theta_t \) 都能找到 \(F\) 的“在 \( \theta_t \) 处相等的下界函数”\( G_{\theta_t}(\theta) \)，我们就得到了一个能单调逼近 \(\theta\) 的最优值的算法：</p>
<blockquote><p>Repeat until converge {</p>
<ol>
<li>找到函数 \( F(\theta) \) 的“在 \( \theta_t \) 处相等的下界函数” \( G_{\theta_t}(\theta) \)</li>
<li>\( \theta_{t+1} := \arg\max_\theta G_{\theta_t}(\theta) \)</li>
</ol>
<p>}</p></blockquote>
<p>上面的算法看起来和 EM 算法的每步都分别对应——事实上也正如此。下面是从 <a href="http://research.microsoft.com/en-us/um/people/cmbishop/prml/" target="_blank">PRML</a> 中偷的一张图改的，展示了上述逼近的过程：<br />
<img class="alignnone" title="EM Algorithm" src="http://upload.tomtung.com/img/em-algo.png" alt="" width="494" height="359" /></p>
<p>现在我们回到最大似然问题 \(\theta=\arg\max_\theta{L(\theta;X)}\) 。如果我们想套用上面的算法来逼近 \( \theta \) 的这个最优解，就需要找到对于每个 \( \theta_t \)，函数 \( L(\theta;X) \) 的“在 \( \theta_t \) 处相等的下界函数”。该怎么找呢？让我们从 \( L(\theta) \) 的初始形式开始推导：</p>
<p>$$<br />
\begin{eqnarray}<br />
L(\theta) &amp;=&amp; \log{P(X;\theta)} \\<br />
&amp;=&amp; \log{\sum_Z P(X,Z;\theta)}<br />
\end{eqnarray}<br />
$$</p>
<p>又卡在这个\(\log\)套\(\sum\)的形式上了……我们说过麻烦在于观察不到 \(Z\) 的值，那不妨给它任意引入一个概率分布 \(Q(Z)\) <sup>[<a href="#em-algorithm-n-3" class="footnoted" id="to-em-algorithm-n-3">3</a>]</sup>，利用分子分母同乘 \(Q(Z)\) 的小 trick，得到：</p>
<p>$$<br />
\begin{eqnarray}<br />
L(\theta) &amp;=&amp; \log{\sum_Z P(X,Z;\theta)} \\<br />
&amp;=&amp; \log{\sum_Z Q(Z) \frac{P(X,Z;\theta)}{Q(Z)}} \\<br />
&amp;=&amp; \log E_{Z \sim Q}\left[ \frac{P(X,Z;\theta)}{Q(Z)} \right]<br />
\end{eqnarray}<br />
$$</p>
<p>根据 Jensen 不等式 <sup>[<a href="#em-algorithm-n-4" class="footnoted" id="to-em-algorithm-n-4">4</a>]</sup>，对于任意分布 \(Q\) 都有：</p>
<p>$$<br />
L(\theta) = \log E_{Z \sim Q}\left[ \frac{P(X,Z;\theta)}{Q(Z)} \right] \geq E_{Z \sim Q}\left[ \log\frac{P(X,Z;\theta)}{Q(Z)} \right]<br />
$$</p>
<p>且上面的不等式在 \( \frac {P(X,Z;\theta)} {Q(Z)}\) 为常数时取等号。</p>
<p>于是我们就得到了 \( L(\theta;X) \) 的一个下界函数。我们要想套用上面的算法，还要让这个不等式在 \( \theta_t \) 处取等号，这就这要求在 \( \theta = \theta_t \) 时 \( \frac {P(X,Z;\theta)} {Q(Z)}\) 为常数，即 \(Q(Z) \propto P(X,Z;\theta_t)\)。由于 \(Q(Z)\) 是一个概率分布，必须满足 \(\sum_z Q_i(z) = 1\)，所以这样的 \(Q(Z)\) 只能是 \(Q(Z) = \frac {P(X,Z;\theta_t)} {\sum_Z P(X,Z;\theta_t)} = P(Z|X;\theta_t)\)。那我们就把 \( Q(Z) = P(Z|X;\theta_t) \) 代入上式，得到：</p>
<p>$$<br />
L(\theta) \geq E_{Z|X;\theta_t}\left[ \log\frac{P(X,Z;\theta)}{P(Z|X;\theta_t)} \right]<br />
$$</p>
<p>且该不等式在 \( \theta = \theta_t \) 时取到等号。那么……\( E_{Z|X;\theta_t}\left[ \log\frac{P(X,Z;\theta)}{P(Z|X;\theta_t)} \right] \) 就是 \( L(\theta;X) \) 的“在 \( \theta_t \) 处相等的下界函数”——这不就是我们要找的么！于是把它塞进本节开始得到的算法里替换“\( G_{\theta_t}(\theta) \)”就可以用啦。也就是说，迭代计算 \( \arg\max_\theta E_{Z|X;\theta_t}\left[ \log\frac{P(X,Z;\theta)}{P(Z|X;\theta_t)} \right] \) 就可以逼近 \( \theta \) 的最优值了。而由于利用 Jensen 不等式的那一步搞掉了\(\log\)套\(\sum\)的形式，它算起来往往要比直接算 \( \arg\max_\theta{L(\theta;X)} \) 容易不少。</p>
<p>我们还可以再做几步推导，得到一个更简单的形式：</p>
<p>$$\begin{eqnarray}<br />
\theta_{t+1}<br />
&amp;:=&amp; \arg\max_\theta E_{Z|X;\theta_t}\left[ \log\frac{P(X,Z;\theta)}{P(Z|X;\theta_t)} \right] \\<br />
&amp;=&amp; \arg\max_\theta \sum_{Z} P(Z|X;\theta_t) \log\frac{P(X,Z;\theta)}{P(Z|X;\theta_t)} \\<br />
&amp;=&amp; \arg\max_\theta \sum_{Z} [P(Z|X;\theta_t)\log P(X,Z;\theta) - P(Z|X;\theta_t) \log P(Z|X;\theta_t)] \\<br />
&amp;=&amp; \arg\max_\theta \sum_{Z} P(Z|X;\theta_t)\log P(X,Z;\theta) \\<br />
&amp;=&amp; \arg\max_\theta E_{Z|X;\theta_t}[\log{P(X,Z;\theta)}]<br />
\end{eqnarray}$$</p>
<p>其中倒数第二步是因为 \(- P(Z|X;\theta_t) \log P(Z|X;\theta_t)]\) 这一项与 \(\theta\) 无关，所以就直接扔掉了。这样就得到了本文第二节 EM 算法中的形式——它就是这么来的。</p>
<p>以上就是 EM 了。至于独立同分布的情况推导也类似。</p>
<p>顺带一提，\( \arg\max_\theta E_{Z|X;\theta_t}[\log{P(X,Z;\theta)}] \) 有时也比较难算。这时我们其实可以退而求其次，不要求这个期望最大化了，只要它在 \( \theta_{t+1} \) 处的值大于在 \( \theta_t \) 处的值就行了。根据上面的推导，这样也能逼近 \( \theta \) 的最优值，只是收敛速度较慢。这就是所谓 GEM (Generalized EM) 算法了。</p>
<p>p.s. <a href="http://www.mathjax.org/" target="_blank">MathJax</a> 很神嘛。<br />
p.p.s. 这篇笔记竟然断断续续写写改改了两天多，我对 EM 的认识也越来越清晰。“<a href="http://mindhacks.cn/2009/02/15/why-you-should-start-blogging-now/" target="_blank">‘教’是最好的‘学’</a>”真是一点没错。</p>
<hr />
更新历史:<br />
2011.10.12 重写了“原理”部分，把利用函数的“在 \(\theta_t\) 处相等的下界函数”逼近 \(\theta\) 的最优值的算法单独提到前面说，这样似乎清楚很多。<br />
2011.10.13 修正了对在利用 Jensen 不等式的那一步要取 \( Q(Z) = P(Z|X;\theta_t) \) 的解释</p>

<hr/> 脚注： <br/> <ol class="footnotes">
	<li class="footnote" id="em-algorithm-n-1"><strong><sup>[1]</sup></strong> 一般可以利用<a href="http://en.wikipedia.org/wiki/Bayes%27_theorem" target="_blank">贝叶斯定理</a>：<br />
$$P(z|x;\theta) = \frac{P(x|z;\theta)P(z;\theta)}{\sum_z{P(x|z;\theta)P(z;\theta)}}$$ 而 \(P(x|z;\theta)\) 和 \(P(z;\theta)\) 往往是模型假设的一部分。 <a class="note-return" href="#to-em-algorithm-n-1">&#x21A9;</a></li>
	<li class="footnote" id="em-algorithm-n-2"><strong><sup>[2]</sup></strong> 实际上在某些特殊情况下，\(\theta\) 还可能收敛在局部最优点或鞍点上。这时可以多跑几次算法，每次随机不同的 \(\theta_0\)，最后取最好的结果。为简明起见，本文忽略这种情况。 <a class="note-return" href="#to-em-algorithm-n-2">&#x21A9;</a></li>
	<li class="footnote" id="em-algorithm-n-3"><strong><sup>[3]</sup></strong> \(Q(Z)\) 为概率分布，意即需满足 \(\sum_Z Q(Z) = 1\) 且 \(Q(Z) \geq 0\) <a class="note-return" href="#to-em-algorithm-n-3">&#x21A9;</a></li>
	<li class="footnote" id="em-algorithm-n-4"><strong><sup>[4]</sup></strong> Jensen 不等式：<br />
\(f\) 为凸函数，\(X\) 为随机变量。则 $$ E[f(X)] \geq f(EX) $$ 若 \(f\) 是严格凸的，则上式取等号当前仅当 \(X\) 为常数。</p>
<p>在这里 \(\log\) 函数是严格凹的，所以要把上面的不等号方向调转。 <a class="note-return" href="#to-em-algorithm-n-4">&#x21A9;</a></li></ol>
<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 索引'>C++ Primer 读书笔记 &#8211; 索引</a></li>
<li><a href='http://blog.tomtung.com/2009/07/agile-development-reading-notes/' rel='bookmark' title='敏捷开发学习笔记（思维导图）'>敏捷开发学习笔记（思维导图）</a></li>
<li><a href='http://blog.tomtung.com/2008/09/cpp-primer-reding-notes-chapter8/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第八章'>C++ Primer 读书笔记 &#8211; 第八章</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2011/10/em-algorithm/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>sinablog2wordpress：从新浪博客搬家到WordPress</title>
		<link>http://blog.tomtung.com/2010/02/sinablog2wordpress/</link>
		<comments>http://blog.tomtung.com/2010/02/sinablog2wordpress/#comments</comments>
		<pubDate>Sat, 06 Feb 2010 13:23:16 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Scala]]></category>
		<category><![CDATA[WordPress]]></category>
		<category><![CDATA[博客搬家]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/?p=475</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2010/02/sinablog2wordpress/" title="sinablog2wordpress：从新浪博客搬家到Wordpress"></a>要从新浪搬到Wordpres，网上广为流传的方法是利用blogbus的博客搬家服务获得blogbus格式的xml，然后再用一个Python写的脚本把它转换成WordPress认识的格式。但是这种方法在最近新浪博客升级以后就失效了。于是自己用现学的scala写了一个小程序，搬家时能保留标签、目录、评论及评论回复这些信息。 猛击[这里]下载。注意，此程序仅支持2010年初的新版新浪博客，之前或之后的版本都不支持。 要运行程序，你需要确保已经安装过JRE。双击运行后显示如图界面，填入自己的博客地址（不要省略“ http:// ”），然后点击“Start”即可。这时“Start”按钮变为灰色，标题栏显示“Extracting”。等待几分钟，当标题栏显示为“Done”、“Start”按钮重新变为可用时，程序所在目录下会出现一个blog.xml文件。把这个文件直接导入WordPress就可以了。 代码也打在jar包里了，MIT协议。欢迎报告bug。 下面是废话。 恩由于新浪用了ajax，评论信息是通过xhr异步读取的，用一般的方法没法抓到。我纠结许久，最后是用了非常ad hoc的方法解决的，不知道有没有什么什么不太麻烦的通用解决方案呢。 再扯两句scala。我都想不起来当初具体是怎么想到要学scala的，也许是为了了解下函数式编程，也许只是想在jvm上有一个喜欢的语言吧——Java写起来太不爽了；Java社区的低效和保守也已经开始显出C++的影子。 scala确实是非常强大和灵活；我在见到一些颇富技巧性的hack之后都有些怀疑scala社区的风气会不会慢慢变得像C++社区一样过分热衷技巧的炫耀。不过scala的设计目标就是以较简单的语法规则获得最大的scalability，不需要通过挖掘语言规范里的犄角旮旯来实现一些必要功能，所以不会像C++一样成为一门本身已相当复杂，却还需要别人反过来教语言发明者如何使用的语言。 scala毕竟表现力比Java强太多，代码也简洁太多。比如这次我需要实现一个抛出异常后重试若干次的逻辑，只需定义一个函数： def tryFor[T](times: Int)(op: =&#62; T): T = { if (times &#60;= 0) throw new RuntimeException("Operation failed.")  try { return op } catch {  case e: Throwable =&#62; e.printStackTrace &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2010/02/sinablog2wordpress/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2009/03/try-template-meta-programming/' rel='bookmark' title='小试模板元编程'>小试模板元编程</a></li>
<li><a href='http://blog.tomtung.com/2008/10/first-month-in-buaa/' rel='bookmark' title='初来北航一个多月的零零碎碎'>初来北航一个多月的零零碎碎</a></li>
<li><a href='http://blog.tomtung.com/2008/09/cpp-primer-reding-notes-chapter8/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第八章'>C++ Primer 读书笔记 &#8211; 第八章</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2010/02/sinablog2wordpress/" title="sinablog2wordpress：从新浪博客搬家到Wordpress"></a><div class="wp-caption alignnone" style="width: 430px"><a href="http://code.google.com/p/sinablog2wordpress/"><img title="http://upload.tomtung.com/img/sina2wordpress-screenshot.png" src="http://upload.tomtung.com/img/sina2wordpress-screenshot.png" alt="" width="420" height="80" /></a><p class="wp-caption-text">Screenshot</p></div>
<p>要从新浪搬到Wordpres，网上广为流传的方法是利用blogbus的博客搬家服务获得blogbus格式的xml，然后再用一个Python写的脚本把它转换成WordPress认识的格式。但是这种方法在最近新浪博客升级以后就失效了。于是自己用现学的scala写了一个小程序，搬家时能保留<strong>标签</strong>、<strong>目录</strong>、<strong>评论</strong>及<strong>评论回复</strong>这些信息。</p>
<p>猛击<strong><a href="http://sinablog2wordpress.googlecode.com/files/sina2wordpress.jar" target="_self">[这里]</a></strong>下载。注意，此程序仅支持2010年初的新版新浪博客，之前或之后的版本都不支持。</p>
<p>要运行程序，你需要确保已经安装过<a href="http://java.com/zh_CN/download/">JRE</a>。双击运行后显示如图界面，填入自己的博客地址（不要省略“ http:// ”），然后点击“Start”即可。这时“Start”按钮变为灰色，标题栏显示“Extracting”。等待几分钟，当标题栏显示为“Done”、“Start”按钮重新变为可用时，程序所在目录下会出现一个blog.xml文件。把这个文件直接导入WordPress就可以了。</p>
<p>代码也打在jar包里了，MIT协议。欢迎报告bug。</p>
<p>下面是废话。</p>
<p>恩由于新浪用了ajax，评论信息是通过xhr异步读取的，用一般的方法没法抓到。我纠结许久，最后是用了非常ad hoc的方法解决的，不知道有没有什么什么不太麻烦的通用解决方案呢。</p>
<p>再扯两句scala。我都想不起来当初具体是怎么想到要学scala的，也许是为了了解下函数式编程，也许只是想在jvm上有一个喜欢的语言吧——Java写起来太不爽了；Java社区的低效和保守也已经开始显出C++的影子。</p>
<p>scala确实是非常强大和灵活；我在见到一些颇富技巧性的hack之后都有些怀疑scala社区的风气会不会慢慢变得像C++社区一样过分热衷技巧的炫耀。不过scala的设计目标就是以较简单的语法规则获得最大的scalability，不需要通过挖掘语言规范里的犄角旮旯来实现一些必要功能，所以不会像C++一样成为一门本身已相当复杂，却还需要别人反过来教语言发明者如何使用的语言。</p>
<p>scala毕竟表现力比Java强太多，代码也简洁太多。比如这次我需要实现一个抛出异常后重试若干次的逻辑，只需定义一个函数：</p>
<pre class="brush:scala">def tryFor[T](times: Int)(op: =&gt; T): T = {
  if (times &lt;= 0) throw new RuntimeException("Operation failed.")
  try { return op } catch {
    case e: Throwable =&gt; e.printStackTrace
    tryFor(times - 1)(op)
  }
}</pre>
<p>然后这样使用：</p>
<pre class="brush:scala">val source = tryFor(5) {new Source(url)}</pre>
<p>程序就会不断获得网页源代码，并在5次失败后抛出异常。Java实现同样的东西可不会如此优雅了。又如下面这段代码返回一篇博文xml：</p>
<pre class="brush:scala">private def generateEntryXml(entry: BlogEntry) = {
  &lt;item&gt;
    &lt;title&gt;
      {entry.title}
    &lt;/title&gt;
    &lt;wp:post_date&gt;
      {dateFormat.format(entry.postDate)}
    &lt;/wp:post_date&gt;
    &lt;category&gt;
      {entry.category}
    &lt;/category&gt;
    {for (tag &lt;- entry.tags) yield &lt;category domain="tag"&gt;{tag}&lt;/category&gt;}
    &lt;content:encoded&gt;
      {xml.Unparsed(handleNewLines(entry.content))}
    &lt;/content:encoded&gt;
    &lt;wp:status&gt;publish&lt;/wp:status&gt;
    {for (comment &lt;- entry.comments) yield generateCommentXml(comment)}
  &lt;/item&gt;
}</pre>
<p>注意，xml标签直接作为scala的源代码的一部分在代码中出现！虽然我觉得这样会使scala语言多出一种“特殊情况”，增加语言的复杂性，但不得不承认这样的设计确实非常优美简洁。</p>
<p>我比较看好scala，以后自己做跑在jvm上的东西scala应该是首选语言。推荐有兴趣的童鞋也了解一下。</p>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2009/03/try-template-meta-programming/' rel='bookmark' title='小试模板元编程'>小试模板元编程</a></li>
<li><a href='http://blog.tomtung.com/2008/10/first-month-in-buaa/' rel='bookmark' title='初来北航一个多月的零零碎碎'>初来北航一个多月的零零碎碎</a></li>
<li><a href='http://blog.tomtung.com/2008/09/cpp-primer-reding-notes-chapter8/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第八章'>C++ Primer 读书笔记 &#8211; 第八章</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2010/02/sinablog2wordpress/feed/</wfw:commentRss>
		<slash:comments>29</slash:comments>
		</item>
		<item>
		<title>有爱的小脚本：启动终端时显示一句箴言</title>
		<link>http://blog.tomtung.com/2009/11/cowsay-fortune/</link>
		<comments>http://blog.tomtung.com/2009/11/cowsay-fortune/#comments</comments>
		<pubDate>Sun, 22 Nov 2009 10:46:17 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[linux]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2009/11/%e6%9c%89%e7%88%b1%e7%9a%84%e5%b0%8f%e8%84%9a%e6%9c%ac%ef%bc%9a%e5%90%af%e5%8a%a8%e7%bb%88%e7%ab%af%e6%97%b6%e6%98%be%e7%a4%ba%e4%b8%80%e5%8f%a5%e7%ae%b4%e8%a8%80/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2009/11/cowsay-fortune/" title="有爱的小脚本：启动终端时显示一句箴言"></a>效果就是每次启动终端时都有一个小动物什么的讲一句有意思的话：doubanclaim469c1764e4db1ecf 说话的东西和说的话都随机出现。这个效果是我在Linux Mint里面看到的，感觉很有爱。下周考Unix环境编程，周末恶补一下，顺便写这个小脚本练手。 以我在用的 ubuntu 为例。首先确保安装 fortunes 和 cowsay 两个包。前者用于显示各种各样的趣味短句，后者则提供了一头会说话的奶牛（和其它各种诡异的东西）。关于fortunes还有一些有趣的包你可能也想一起安装，比如fortune-zh里有唐诗宋词，fortunes-ubuntu-server则有关于使用Ubuntu Server的贴士，等等。 新建一个文件cowsay-fortune，把以下代码复制进去： #!/bin/bash # Cow randomly says a hopefully interesting adage # Get a short message from fortune, both offensive and not. # Remove -a if you don't want to &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2009/11/cowsay-fortune/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2008/10/first-month-in-buaa/' rel='bookmark' title='初来北航一个多月的零零碎碎'>初来北航一个多月的零零碎碎</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2009/11/cowsay-fortune/" title="有爱的小脚本：启动终端时显示一句箴言"></a><p>效果就是每次启动终端时都有一个小动物什么的讲一句有意思的话：<span style="color: #ffffff;">doubanclaim469c1764e4db1ecf</span></p>
<p><a href="http://upload.tomtung.com/img/cowsay-fortune-1.png"><img class="alignnone" title="cowsay-fortune" src="http://upload.tomtung.com/img/cowsay-fortune-1.png" alt="cowsay-fortune" width="657" height="435" /></a></p>
<p>说话的东西和说的话都随机出现。这个效果是我在<a href="http://www.linuxmint.com/" target="_blank">Linux Mint</a>里面看到的，感觉很有爱。下周考Unix环境编程，周末恶补一下，顺便写这个小脚本练手。</p>
<p>以我在用的 ubuntu 为例。首先确保安装 <a href="http://en.wikipedia.org/wiki/Fortune_%28Unix%29" target="_blank">fortunes</a> 和 <a href="http://en.wikipedia.org/wiki/Cowsay" target="_blank">cowsay</a> 两个包。前者用于显示各种各样的趣味短句，后者则提供了一头会说话的奶牛（和其它各种诡异的东西）。关于fortunes还有一些有趣的包你可能也想一起安装，比如fortune-zh里有唐诗宋词，fortunes-ubuntu-server则有关于使用Ubuntu Server的贴士，等等。</p>
<p>新建一个文件cowsay-fortune，把以下代码复制进去：</p>
<pre class="brush:bash">
#!/bin/bash
# Cow randomly says a hopefully interesting adage

# Get a short message from fortune, both offensive and not.
# Remove -a if you don't want to see offensive ones.
# Remove -s if you don't mind reading the long messages.
msg=`fortune -a -s`

# Randomly pick a mode of the cow
modes=("" -b -d -g -p -s -t -w -y ); mode=${modes[$(($RANDOM % 9))]}

# cowsay or cowthink?
cowdos=(cowsay cowthink); cowdo=${cowdos[$(($RANDOM % 2))]}

# Radomly pick a cow picture file
speaker=`cowsay -l | sed '1d;s/ /\n/g'| sort -R | head -1`

# That's it ^^
echo "$msg" | $cowdo -n -f $speaker $mode
</pre>
<p>保存后加上执行权限：</p>
<p><code>chmod +x cowsay-fortune</code></p>
<p>然后把这个文件复制到/usr/bin下</p>
<p><code>sudo cp cowsay-fortune /usr/bin</code></p>
<p>最后打开/etc/bash.bashrc</p>
<p><code>sudo gedit /etc/bash.bashrc</code></p>
<p>并在最后加上一行：</p>
<p><code>cowsay-fortune</code></p>
<p>保存后打开终端，应该就是这个效果了：</p>
<p><a href="http://upload.tomtung.com/img/cowsay-fortune-2.png"><img class="alignnone" title="cowsay-fortune" src="http://upload.tomtung.com/img/cowsay-fortune-2.png" alt="cowsay-fortune" width="675" height="470" /></a></p>
<p>顺便抱怨一下，shell编程时对空格的要求也太诡异了吧。。一会儿要求空，一会儿要求不空，一会儿又不限制。。。</p>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2008/10/first-month-in-buaa/' rel='bookmark' title='初来北航一个多月的零零碎碎'>初来北航一个多月的零零碎碎</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2009/11/cowsay-fortune/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>[POJ1771] Elevator Stopping Plan</title>
		<link>http://blog.tomtung.com/2009/11/poj1771-elevator-stopping-plan/</link>
		<comments>http://blog.tomtung.com/2009/11/poj1771-elevator-stopping-plan/#comments</comments>
		<pubDate>Fri, 20 Nov 2009 16:33:17 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[动态规划]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2009/11/poj1771-elevator-stopping-plan/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2009/11/poj1771-elevator-stopping-plan/" title="[POJ1771] Elevator Stopping Plan"></a>新浪的 spamer 越来越多，很早就想搬到独立的 wordpress 上去，但是一直没顾上。只好先在sina这里凑合着了。 Elevator Stopping Plan Time Limit: 1000MS  Memory Limit: 30000K  Special Judge Description ZSoft Corp. is a software company in GaoKe Hall. And the workers in the hall are very hard-working. But the elevator in that &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2009/11/poj1771-elevator-stopping-plan/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/05/noi97-game/' rel='bookmark' title='[NOI97]积木游戏(Game)'>[NOI97]积木游戏(Game)</a></li>
<li><a href='http://blog.tomtung.com/2007/05/noi99-chess/' rel='bookmark' title='[NOI99]棋盘分割(Chessboard Division)'>[NOI99]棋盘分割(Chessboard Division)</a></li>
<li><a href='http://blog.tomtung.com/2007/09/noip05-river/' rel='bookmark' title='[NOIP05]过河'>[NOIP05]过河</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2009/11/poj1771-elevator-stopping-plan/" title="[POJ1771] Elevator Stopping Plan"></a><p>新浪的 spamer 越来越多，很早就想搬到独立的 wordpress 上去，但是一直没顾上。只好先在sina这里凑合着了。</p>
<div style="text-align: center; font-weight: bold; color: #0000ff;" lang="en-US" xml:lang="en-US"><span style="font-size: 18px;">Elevator Stopping Plan</span></div>
<div style="text-align: center;"><strong>Time Limit:</strong> 1000MS  <strong>Memory Limit:</strong> 30000K  <span style="font-weight: bold; color: #ff0000;">Special Judge</span></div>
<p style="font-weight: bold; color: #0000ff;">Description</p>
<div lang="en-US" xml:lang="en-US">
<p>ZSoft Corp. is a software company in GaoKe Hall. And the workers in the hall are very hard-working. But the elevator in that hall always drives them crazy. Why? Because there is only one elevator in GaoKe Hall, while there are hundreds of companies in it. Every morning, people must waste a lot of time waiting for the elevator.</p>
<p>Hal, a smart guy in ZSoft, wants to change this situation. He wants to find a way to make the elevator work more effectively. But it’s not an easy job.</p>
<p>There are 31 floors in GaoKe Hall. It takes 4 seconds for the elevator to raise one floor. It means:</p>
<p>It costs (31-1)*4=120 seconds if the elevator goes from the 1st floor to the 31st floor without stop. And the elevator stops 10 second once. So, if the elevator stops at each floor, it will cost 30*4+29*10 = 410 seconds (It is not necessary to calculate the stopping time at 31st floor). In another way, it takes 20 seconds for the workers to go up or down one floor. It takes 30*20 = 600 seconds for them to walk from the 1st floor to the 31st floor. Obviously, it is not a good idea. So some people choose to use the elevator to get a floor which is the nearest to their office.</p>
<p>After thinking over for a long time, Hal finally found a way to improve this situation. He told the elevator man his idea: First, the elevator man asks the people which floors they want to go. He will then design a stopping plan which minimize the time the last person need to arrive the floor where his office locates. For example, if the elevator is required to stop at the 4th, 5th and 10th floor,the stopping plan would be: the elevator stops at 4th and 10th floor. Because the elevator will arrive 4th floor at 3*4 = 12 second, then it will stop 10 seconds, then it will arrive 10th floor at 3*4+10+6*4 = 46 second. People who want to go 4th floor will reach their office at 12 second, people who want to go to 5th floor will reach at 12+20 = 32 second and people who want to go to 10th floor will reach at 46 second. Therefore it takes 46 seconds for the last person to reach his office. It is a good deal for all people.<br />
Now, you are supposed to write a program to help the elevator man to design the stopping plan,which minimize the time the last person needs to arrive at his floor.</p>
</div>
<p style="color: #0000ff; font-weight: bold;">Input</p>
<div lang="en-US" xml:lang="en-US">The input consists of several testcases. Each testcase is in a single line as the following:<br />
n f1 f2 &#8230; fn<br />
It means, there are totally n floors at which the elevator need to stop, and n = 0 means no testcases any more. f1 f2 &#8230; fn are the floors at which the elevator is to be stopped (n &lt;= 30, 2 &lt;= f1 &lt; f2 &#8230; fn &lt;= 31). Every number is separated by a single space.</div>
<p style="color: #0000ff; font-weight: bold;">Output</p>
<div lang="en-US" xml:lang="en-US">For each testcase, output the time the last reading person needs in the first line and the stopping floors in the second line. Please note that there is a summary of the floors at the head of the second line. There may be several solutions, any appropriate one is accepted. No extra spaces are allowed.</div>
<p style="font-weight: bold; color: #0000ff;">Sample Input</p>
<pre> 3 4 5 10
 1 2
 0</pre>
<p style="font-weight: bold; color: #0000ff;">Sample Output</p>
<pre> 46
 2 4 10
 4
 1 2</pre>
<p style="color: #0000ff; font-weight: bold;">Source</p>
<p>Asia Guangzhou 2003</p>
<p style="font-weight: bold; color: #0000ff;">Solution</p>
<p>很久不更新了，写个水水的解题报告充数。。。这是最近做的算法习题，网上看到这题的题解都是二分+贪心的，这里提供一个dp解法。</p>
<p>理解题意的时候有一点需要特别注意：题目所描述的整个过程是“并行”的。所以所有人都到达各自楼层的用时只与最晚到达的人有关。</p>
<p>首先，由于去各楼层乘客的具体数目对结果没有影响，为表述方便，我们假设每个目标楼层只有一个人要去，并且把各个目标楼层与要去该楼层的那个乘客对应。下面在说“电梯里还剩i个人”的时候，就是在说“电梯里的乘客还要去i个楼层”。</p>
<p>粗略的阶段划分和状态表示还是很简单的。</p>
<p>电梯从第1层开始层层上升，每层都看做一个阶段，任意时刻的状态都可以由“电梯在几楼”和“电梯上都有谁”这两个参数唯一确定。初始状态就是“电梯在1楼”和“所有人都在电梯上”。</p>
<p>在每个阶段需要做出决策，选择让电梯上的哪些人下来自己走（如果没人下来就表示电梯在这层不停）。每个决策发生后，原来电梯里的人被分成两拨：一拨留在电梯上继续上升，另一拨离开电梯开始爬楼锻炼身体。要求某个状态下电梯里的所有人到达各自楼层所需的最短时间，只需要找到一个最优的决策，使得上述两拨人中最晚到达的人尽早到达。</p>
<p>即，若设决策后留在电梯里的人全部到达各自楼层需要时间 T1，离开电梯的人全部到达需要时间 T2，则要求的就是 min{ max(T1, T2) }。其中 T1 可由“当前层数+1”和“决策后剩下的人”确定的状态得到；T2 则是下电梯的人中走的最远的那位所花的时间。</p>
<p>按照上述想法很容易列出转移方程。但是“电梯上都有谁”这一参数有 2^n 种取值，整个算法的复杂度因此能到达令人发指的O(m*2^2n)，对于m=31，n=30的数据规模这是不可接受的。</p>
<p>我们需要设法减少需要考虑的状态数目。下面给出两个引理：</p>
<p>1. 电梯决定停在第k层时：要去 1..k 层的人应选择在这时下电梯，这样一定可以得到当前决策下的一个最优解。如图：<br />
<a href="http://upload.tomtung.com/img/poj1771_1.png"><img class="aligncenter" title="http://upload.tomtung.com/img/poj1771_1.png" src="http://upload.tomtung.com/img/poj1771_1.png" alt="" width="357" height="284" /></a><br />
2. 电梯在第k层时，若要去 k+r 层的人选择在这时下电梯，则：要去k+1..k+r-1层的人也应选择在此时下电梯，这样一定可以得到当前决策下的一个最优解。如图：<br />
<img class="aligncenter" title="http://upload.tomtung.com/img/poj1771_2.png" src="http://upload.tomtung.com/img/poj1771_2.png" alt="http://upload.tomtung.com/img/poj1771_2.png" width="426" height="324" /><br />
以上两点很容易证明。由这两点可以得到一个很简单但足以解决问题的结论：</p>
<p style="font-weight: bold;">无论电梯停在哪一层，若要去第 k 层的人选择在这时下电梯，则：所有要去低于k层（第1..k-1层）的人也应选择在此时下电梯，这样一定可以得到当前决策下的一个最优解。<span style="font-weight: normal;">如图：</span></p>
<p><a href="http://upload.tomtung.com/img/poj1771_3.png"><img class="aligncenter" title="http://upload.tomtung.com/img/poj1771_3.png" src="http://upload.tomtung.com/img/poj1771_3.png" alt="http://upload.tomtung.com/img/poj1771_3.png" width="422" height="322" /></a></p>
<p>也就是说，如果把初始时的 n 名乘客按照各自要去的层数从<span style="font-weight: bold;">高</span>到<span style="font-weight: bold;">低</span>（注意此顺序与输入相反）排列，并依此编号为第 1、2、3&#8230;n 个人，第 i 个人要去第 f[i] 层（f[1]&gt;f[2]&gt;&#8230;&gt;f[n]），那么可以认为<span style="font-weight: bold;">任意时刻电梯里乘客的编号都是 1, 2,..,x 这样一个连续序列</span>。也就是说，对于电梯里的人我们只需要考虑编号为 1, 2, 3 或 1, 2, 3, 4, 5 这样连续排列的情况，而无需考虑 1, 2, 4（缺3）或2, 3, 4（缺1）这样的情况。<br />
<a href="http://upload.tomtung.com/img/poj1771_4.png"><img class="aligncenter" title="http://upload.tomtung.com/img/poj1771_4.png" src="http://upload.tomtung.com/img/poj1771_4.png" alt="http://upload.tomtung.com/img/poj1771_4.png" width="610" height="116" /></a><br />
这样一来，每个状态都能由两个数[i,j]来表示：电梯在第i层，电梯里有j个人，即要去楼层最高的第1,2,..,j个人。</p>
<p>下面给出转移方程：</p>
<p>f[i,j]表示电梯在第i层，电梯上有要去楼层最高的j个人时，电梯上的人全部到达各自楼层所需的最短时间</p>
<p>f[i,j] = min{ max(t1, t2) } (0&lt;=k&lt;=j)</p>
<p>t1 = f[i+1, k] + 电梯停留时间 + 电梯上升一层所用时间</p>
<p>t2 = max{ |d[l] &#8211; i| * 人爬一层楼所用时间 } ( k+1&lt;=l&lt;=j )</p>
<p>边界条件、最优解的构造方法以及其它细节问题不再赘述，详见代码。复杂度O(m*n^2)。代码中其实还有优化的空间，但已经是0ms过的，没必要了。</p>
<pre class="brush:c++">
#include &lt;iostream&gt;
using std::cin;
using std::cout;
using std::endl;

#include &lt;cstring&gt;
using std::memset;

#include &lt;algorithm&gt;
using std::max;

#include &lt;cmath&gt;
using std::abs;

#include &lt;limits&gt;
using std::numeric_limits;

#include &lt;vector&gt;
using std::vector;

const int maxN = 30, maxF = 31;
const int ve = 4, st = 10, vw = 20; // 电梯上一层所需时间；电梯停一层所需时间；人走一层所需时间

int n, f[maxN + 1];

bool input()
{
    cin &gt;&gt; n;
    if (n==0) return false;

    // 注意：f[1..n]中楼层数从高到底排列
    for (int i = n; i&gt;=1; --i)
        cin &gt;&gt; f[i];

    return true;
}

int dp[maxF + 1][maxN + 1], nextJ[maxF + 1][maxN + 1];

// 现在电梯在第currF层，第L到第R人离开电梯
// 函数返回这些离开电梯的人中最晚到达目的楼层所需的时间
int tLeave(int currF, int l, int r)
{
    if (l&gt;r)  return 0;
    // 仅需考虑两端
    return max(abs(currF-f[l]), abs(currF-f[r])) * vw;
}

// 现在电梯在第i层，电梯里本来有j个人，在要下电梯的人离开后还剩jj个人
// 函数返回这些留在电梯里的人中最晚到达目的楼层所需的时间
int tStay(int i, int j, int jj)
{
    // 没人下电梯
    if (j==jj)
        return dp[i+1][jj] + ve;
    // 所有人都离开电梯
    else if (jj==0)
        return 0;
    // 第1层不计算电梯停留时间
    else if (i==1)
        return dp[i+1][jj] + ve;
    //普通情况
    else
        return dp[i+1][jj] + ve + st;
}

void calculate()
{
    // 边界：电梯在顶楼时所有人都必须下电梯
    int topFloor = f[1];
    for (int j = 1; j&lt;=n; ++j)
        dp[topFloor][j] = tLeave(topFloor,1,j);

    for (int i = topFloor - 1; i&gt;=1; --i)
        for (int j = 1; j&lt;=n; ++j)
        {
            dp[i][j] = numeric_limits&lt;int&gt;::max();
            for (int jj = 0; jj &lt;= j; ++jj)
            {
                // 取离开电梯的人和留下的人中的最晚到达者
                int tmp = max(tStay(i,j,jj),tLeave(i,jj+1,j));
                if (dp[i][j] &gt; tmp)
                {
                    dp[i][j] = tmp;
                    nextJ[i][j] = jj;
                }
            }
        }
    cout &lt;&lt; dp[1][n] &lt;&lt; endl;
}

void rebuildSolution()
{
    vector&lt;int&gt; stops;
    int j = nextJ[1][n], topFloor = f[1];
    for (int i = 2; i&lt;=topFloor; ++i)
        if (nextJ[i][j]!=j)
        {
            stops.push_back(i);
            j = nextJ[i][j];
            if (j==0) break;
        }

    cout &lt;&lt; stops.size();
    for (int i = 0; i!=stops.size(); ++i)
        cout &lt;&lt; ' ' &lt;&lt; stops[i];
    cout &lt;&lt; endl;
}

void solve()
{
    memset(dp,0,sizeof(dp));
    memset(nextJ,0,sizeof(nextJ));
    calculate();
    rebuildSolution();
}

int main()
{
    while (input())
        solve();
}
</pre>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/05/noi97-game/' rel='bookmark' title='[NOI97]积木游戏(Game)'>[NOI97]积木游戏(Game)</a></li>
<li><a href='http://blog.tomtung.com/2007/05/noi99-chess/' rel='bookmark' title='[NOI99]棋盘分割(Chessboard Division)'>[NOI99]棋盘分割(Chessboard Division)</a></li>
<li><a href='http://blog.tomtung.com/2007/09/noip05-river/' rel='bookmark' title='[NOIP05]过河'>[NOIP05]过河</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2009/11/poj1771-elevator-stopping-plan/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>读取RFC822格式日期时间的类[C#]</title>
		<link>http://blog.tomtung.com/2009/08/rfc822-date-time-parser/</link>
		<comments>http://blog.tomtung.com/2009/08/rfc822-date-time-parser/#comments</comments>
		<pubDate>Sun, 09 Aug 2009 12:06:31 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[C#]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2009/08/%e8%af%bb%e5%8f%96rfc822%e6%a0%bc%e5%bc%8f%e6%97%a5%e6%9c%9f%e6%97%b6%e9%97%b4%e7%9a%84%e7%b1%bbc/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2009/08/rfc822-date-time-parser/" title="读取RFC822格式日期时间的类[C#]"></a>比较无法理解为什么 .NET 类库里面没有提供足够对RFC822格式日期时间的支持。网上有人实现，但是都不太让人满意。比较囧的是，关于单字母表示时区，好几个实现都只考虑了 Z、A、M、N、Y 五个 spec 里直接列出的字母……我在用的 Argotic 也是这样。所以就自己实现了一个 parser，然后修改 Argotic 的代码直接用它。 点这里下载源代码及其单元测试。需要的话就拿去吧，lgpl。 我实现的时候相比严格的 spec 又放宽了一些，比如允许小时、分钟、秒只有一位数字（spec 要求必须两位），允许不指定时区，允许用四位数字表示年份（这个是 rss 的 spec 要求的），允许只指定日期不指定时间。如果你觉着不爽就自己改改吧，看代码就知道非常好改（就在“Set Format Strings”那个 region 里）。 欢迎报告bug。 可能你对下面的文章也感兴趣： 敏捷开发学习笔记（思维导图） 初来北航一个多月的零零碎碎 写在临行前<hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2009/07/agile-development-reading-notes/' rel='bookmark' title='敏捷开发学习笔记（思维导图）'>敏捷开发学习笔记（思维导图）</a></li>
<li><a href='http://blog.tomtung.com/2008/10/first-month-in-buaa/' rel='bookmark' title='初来北航一个多月的零零碎碎'>初来北航一个多月的零零碎碎</a></li>
<li><a href='http://blog.tomtung.com/2008/09/unable-to-stay/' rel='bookmark' title='写在临行前'>写在临行前</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2009/08/rfc822-date-time-parser/" title="读取RFC822格式日期时间的类[C#]"></a><div style="text-align: center;"><a href="http://upload.tomtung.com/img/rfc822_date_time_parser.png"><img class="aligncenter" title="http://upload.tomtung.com/img/rfc822_date_time_parser.png" src="http://upload.tomtung.com/img/rfc822_date_time_parser.png" alt="" width="202" height="133" /></a></div>
<p>比较无法理解为什么 .NET 类库里面没有提供足够对<a href="http://www.w3.org/Protocols/rfc822/#z28" target="_blank">RFC822格式日期时间</a>的支持。网上有人实现，但是都不太让人满意。比较囧的是，关于单字母表示时区，好几个实现都只考虑了 Z、A、M、N、Y 五个 spec 里直接列出的字母……我在用的 Argotic 也是这样。所以就自己实现了一个 parser，然后修改 Argotic 的代码直接用它。</p>
<p>点<a href="http://podcat.sourceforge.net/Rfc822DateTimeParser.zip" target="_blank">这里</a>下载源代码及其单元测试。需要的话就拿去吧，lgpl。</p>
<p>我实现的时候相比严格的 spec 又放宽了一些，比如允许小时、分钟、秒只有一位数字（spec 要求必须两位），允许不指定时区，允许用四位数字表示年份（这个是 rss 的 spec 要求的），允许只指定日期不指定时间。如果你觉着不爽就自己改改吧，看代码就知道非常好改（就在“Set Format Strings”那个 region 里）。</p>
<p>欢迎报告bug。</p>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2009/07/agile-development-reading-notes/' rel='bookmark' title='敏捷开发学习笔记（思维导图）'>敏捷开发学习笔记（思维导图）</a></li>
<li><a href='http://blog.tomtung.com/2008/10/first-month-in-buaa/' rel='bookmark' title='初来北航一个多月的零零碎碎'>初来北航一个多月的零零碎碎</a></li>
<li><a href='http://blog.tomtung.com/2008/09/unable-to-stay/' rel='bookmark' title='写在临行前'>写在临行前</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2009/08/rfc822-date-time-parser/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>敏捷开发学习笔记（思维导图）</title>
		<link>http://blog.tomtung.com/2009/07/agile-development-reading-notes/</link>
		<comments>http://blog.tomtung.com/2009/07/agile-development-reading-notes/#comments</comments>
		<pubDate>Sat, 04 Jul 2009 10:06:18 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[C#]]></category>
		<category><![CDATA[思维导图]]></category>
		<category><![CDATA[敏捷开发]]></category>
		<category><![CDATA[笔记]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2009/07/%e6%95%8f%e6%8d%b7%e5%bc%80%e5%8f%91%e5%ad%a6%e4%b9%a0%e7%ac%94%e8%ae%b0%ef%bc%88%e6%80%9d%e7%bb%b4%e5%af%bc%e5%9b%be%ef%bc%89/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2009/07/agile-development-reading-notes/" title="敏捷开发学习笔记（思维导图）"></a>终于看完了《Agile Principles, Patterns, and Practices in C#》的第一个Section，上学期间读这些书的进度还真不是一般的慢……第一次尝试用MindMap做笔记，感觉不错，不过效率还是不够高。老蒋说我笔记做得太详尽了，像抄书&#8230;反思中。 篇幅所限，书中还是有很多内容不够深入。一些内容需要另一本书的篇幅深入讨论（如重构），甚至需要看一本书才能从纸上谈兵转到实践（如TDD、Acceptance Tests）。。。望不到头的书单啊T_T 做笔记使用的工具是 XMind。 可能你对下面的文章也感兴趣： C++ Primer 读书笔记 &#8211; 索引 C++ Primer 读书笔记 &#8211; 第七章 C++ Primer 读书笔记 &#8211; 第六章<hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 索引'>C++ Primer 读书笔记 &#8211; 索引</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter7/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第七章'>C++ Primer 读书笔记 &#8211; 第七章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter6/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第六章'>C++ Primer 读书笔记 &#8211; 第六章</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2009/07/agile-development-reading-notes/" title="敏捷开发学习笔记（思维导图）"></a><p>终于看完了《<a href="http://www.douban.com/subject/1815898/" target="_blank">Agile Principles, Patterns, and Practices in C#</a>》的第一个Section，上学期间读这些书的进度还真不是一般的慢……第一次尝试用<a href="http://en.wikipedia.org/wiki/Mindmap" target="_blank">MindMap</a>做笔记，感觉不错，不过效率还是不够高。<a href="http://www.jiangtianzheng.com/" target="_blank">老蒋</a>说我笔记做得太详尽了，像抄书&#8230;反思中。</p>
<p>篇幅所限，书中还是有很多内容不够深入。一些内容需要另一本书的篇幅深入讨论（如重构），甚至需要看一本书才能从纸上谈兵转到实践（如TDD、Acceptance Tests）。。。望不到头的书单啊T_T</p>
<p>做笔记使用的工具是 <a href="http://www.xmind.net/" target="_blank">XMind</a>。</p>
<script type='text/javascript'>  
window.onload = document.write("<iframe width='100%' height='250' marginwidth='0' marginheight='0' scrolling='auto' frameborder='0'  src='http://xmind.net/share/_embed/tomtung/xmind-362366/' ></iframe> "); 
 window.onload = document.write("<br /><a href='http://www.xmind.net/share/tomtung/xmind-362366/'>下载查看完整内容</a><br/>"); 
</script>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 索引'>C++ Primer 读书笔记 &#8211; 索引</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter7/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第七章'>C++ Primer 读书笔记 &#8211; 第七章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter6/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第六章'>C++ Primer 读书笔记 &#8211; 第六章</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2009/07/agile-development-reading-notes/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>[BHOJ10235] 窗口取数</title>
		<link>http://blog.tomtung.com/2009/03/bhoj-10235/</link>
		<comments>http://blog.tomtung.com/2009/03/bhoj-10235/#comments</comments>
		<pubDate>Sat, 28 Mar 2009 13:52:48 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2009/03/bhoj10235-%e7%aa%97%e5%8f%a3%e5%8f%96%e6%95%b0/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2009/03/bhoj-10235/" title="[BHOJ10235] 窗口取数"></a>窗口（超级版） 时间限制:5000 ms 内存限制:65535 KB 描述 有一串整数在排队…… 有N个整数，你有一个可以框住M个连续段整数的木框，现在你想知道，对于这个队列中任意的连续M个整数，最大和最小的整数是哪个？ 例：(M大小的窗口向右滑动) 1 2 3 2 1 M=2 1 2 MAX 2 MIN 1 2 3 MAX 3 MIN 2 3 2 MAX 3 MIN 2 2 1 MAX 2 MIN 1 最后需要输出的是两行： &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2009/03/bhoj-10235/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1014/' rel='bookmark' title='[VIJOS 1014]旅行商简化版'>[VIJOS 1014]旅行商简化版</a></li>
<li><a href='http://blog.tomtung.com/2007/05/museum-fire/' rel='bookmark' title='[IOI2000国家队原创题] 艺术馆的火灾'>[IOI2000国家队原创题] 艺术馆的火灾</a></li>
<li><a href='http://blog.tomtung.com/2007/07/noi05-cckk/' rel='bookmark' title='[NOI 05]聪聪与可可'>[NOI 05]聪聪与可可</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2009/03/bhoj-10235/" title="[BHOJ10235] 窗口取数"></a><h1 style="text-align: center;"><img class="aligncenter" title="http://upload.tomtung.com/img/sliding-window.jpg" src="http://upload.tomtung.com/img/sliding-window.jpg" alt="http://upload.tomtung.com/img/sliding-window.jpg" width="300" height="300" /></h1>
<h1 style="text-align: center;">窗口（超级版）</h1>
<p style="text-align: center;">时间限制:5000 ms   内存限制:65535 KB</p>
<h2>描述</h2>
<p>有一串整数在排队……</p>
<p>有N个整数，你有一个可以框住M个连续段整数的木框，现在你想知道，对于这个队列中任意的连续M个整数，最大和最小的整数是哪个？</p>
<p>例：(M大小的窗口向右滑动)</p>
<pre>1 2 3 2 1   M=2
1 2    MAX 2  MIN 1
2 3    MAX 3  MIN 2
3 2    MAX 3  MIN 2
2 1    MAX 2  MIN 1
</pre>
<p>最后需要输出的是两行：</p>
<pre>1 2 2 1
2 3 3 2
</pre>
<h2>输入</h2>
<p>第一行包含一个整数T，表示有T组测试数据；<br />
以下每组测试数据格式：<br />
第一行包含2个整数N,M表示有N个整数在排队，取连续M个整数。<br />
第二行包含N个整数。<br />
其中N不大于1,000,000，M不大于N。</p>
<h2>输出</h2>
<p>按照题目描述格式输出结果，第一行为MIN，第二行为MAX。</p>
<h2>样例输入</h2>
<pre>1
5 2
1 2 3 2 1
</pre>
<h2>样例输出</h2>
<pre>1 2 2 1
2 3 3 2
</pre>
<h2>提示</h2>
<p>注意数据规模！</p>
<h2>问题来源</h2>
<p>软件学院07级数据结构第二次测试</p>
<h2>题解</h2>
<p>很久不考虑算法问题了的说……当然也好久不写解题报告了。上面这题让我体会到久违了的思考的乐趣（i.e.我废掉好久了&gt;_&lt;）</p>
<p>最显然的解法是利用平衡树始终保持木框内M个数的有序状态，大体是O(NlogM)的复杂度。这个不用多说。</p>
<p>问题是：能否找到O(N)的解法？在读下去之前你可以好好想想。</p>
<p>我纠结了好久，终于……也没想出来- &#8211; 尽管想出很多优化，终究还是不能达到要求。网上搜了下，看到 CS大牛csdn 上有人给出了一个解法（你也可以在读完全文后再回头看这一段）：</p>
<blockquote><p>是可以到o(n);</p>
<p>编程之美上有一个类似的问题:&#8221;队列中取最大值操作问题&#8221;;<br />
实际上窗口移动就相当于对队列做了一次出队与入队操作，所以lz这道题可以套用该解法；</p>
<p>书上是使用两个栈来模拟队列，假设为分别A,B;<br />
1)当入队的时候，push A；<br />
2)当出队的时候,<br />
a)若B非空,pop B,<br />
b)若B为空，则先将A中的元素依次pop并push到B,再pop B;<br />
这样,就使用两个栈达到了队列的功能;</p>
<p>同时,对于单个栈，由于pop,push都是在栈顶进行的，所以每个栈都可以方便地维护自己的最<br />
大值与最小值在栈内的索引；<br />
以最大值为例；<br />
用max_idx保存栈内最大值的索引;<br />
另使用一个跟栈的最大长度一样的数组idx,idx[i]表示栈的索引范围在0到i-1的元素中的最<br />
大值的索引为idx[i];<br />
1) 当push的时候，比较栈顶元素与栈的已保存的最大值，<br />
a) 若栈顶元素大于已保存的最大值,那么idx[top]=max_idx,max_idx=top;<br />
b) 若栈顶元素不大于已保存的最大值,那么idx[top]=max_idx(书上是idx[top]=-1);<br />
2) 当pop的时候，max_idx=idx[top](书上是先比较top与max_idx，若top==max_idx,则<br />
max_idx=idx[top]);</p>
<p>具体到这个题目；<br />
可以使用两个长度为m的栈来模拟窗口；<br />
1)将前m个元素依次push到栈A;<br />
2)移动窗口就相当于分别进行pop B　与　push A操作;<br />
3) 窗口的最大值==max[A.items[A.max_idx]),B.items[B.max_idx])]；</p>
<p>由于每个元素最多进入两栈各一次；<br />
所以总的复杂度是o(n)的;</p></blockquote>
<p>这个解法的确可行，但描述仍不直观。为什么两个栈暧昧地眉来眼去一番，就在O(N)时间内把问题解决了呢？褪去实现上的种种细节，从更高的角度观察这个算法，它的思路是怎么样的呢？</p>
<p>和冬冬讨论了半天，似乎找到了一个比较靠近此算法根本动机的理解方向。如下：</p>
<h3>一、简化</h3>
<p>此问题中，框的左端和右端同时在向右移动。这时，要在任意时刻用O(1)的时间获得框内最小值（最大值同理，故仅以最小值为例）并不容易。但是，如果假设这个框是可伸缩的，将其一端固定，仅移动另外一端，任意时刻能否在O(1)的时间内获得最小值呢？</p>
<p>稍加思考，就发现要找到这样的算法是很容易的（事实上在我最初考虑时，就是试图在这样一个算法的基础上进行优化的）。</p>
<p>假如这个框是左端固定，右端不断向右移动的（称之为<em>左定右动框</em>）。对于框内的每个数，有一个对应指针指向框内它左边（包括自身）所有数中最小者。这样，框内最小值就是框内最右端元素对应的指针所指向的元素。如在下图中，最小元素就是右端7对应指针所指向的元素6。</p>
<div class="wp-caption aligncenter" style="width: 499px"><img title="http://upload.tomtung.com/img/bhoj-10235_1.png" src="http://upload.tomtung.com/img/bhoj-10235_1.png" alt="http://upload.tomtung.com/img/bhoj-10235_1.png" width="489" height="127" /><p class="wp-caption-text">min = 6</p></div>
<p>当框的右端向右扩展时，可以通过递推获得新元素对应指针应指向的元素。以上图为例。加入2时，由7对应的指针可知原框中最小值为6，而6&gt;2，所以新元素2对应的指针应指向自己。</p>
<div class="wp-caption aligncenter" style="width: 497px"><img title="http://upload.tomtung.com/img/bhoj-10235_2.png" src="http://upload.tomtung.com/img/bhoj-10235_2.png" alt="http://upload.tomtung.com/img/bhoj-10235_2.png" width="487" height="125" /><p class="wp-caption-text">min = 2</p></div>
<p>加入8时，由2的指针知原框中最小值为2，而2&lt;8，所以新元素8对应指针应指向2的指针所指向的元素2。</p>
<div class="wp-caption aligncenter" style="width: 500px"><img title="http://upload.tomtung.com/img/bhoj-10235_3.png" src="http://upload.tomtung.com/img/bhoj-10235_3.png" alt="http://upload.tomtung.com/img/bhoj-10235_3.png" width="490" height="128" /><p class="wp-caption-text">min = 2</p></div>
<p>其余如法炮制。</p>
<div class="wp-caption aligncenter" style="width: 501px"><img title="http://upload.tomtung.com/img/bhoj-10235_4.png" src="http://upload.tomtung.com/img/bhoj-10235_4.png" alt="http://upload.tomtung.com/img/bhoj-10235_4.png" width="491" height="129" /><p class="wp-caption-text">min = 2</p></div>
<p>可见，在框左端固定、右端不断扩展时，任意时刻都能在常数时间内确定框内元素的最小值。（不仅如此，即使需要让右端向左收缩，也能在常数时间内，通过最右端元素指针获得得到收缩后框内所剩元素的最小值。） 整个过程演示如下（先扩张后收缩）：</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/bhoj-10235_5.gif" src="http://upload.tomtung.com/img/bhoj-10235_5.gif" alt="http://upload.tomtung.com/img/bhoj-10235_5.gif" width="496" height="192" /></p>
<p>演示中的收缩操作在当前问题中并无必要。演示出来是为了说明无论右端如何移动，只要左端不动，就可以在任意时刻立刻得到最小值。同时也是为了与下面一个演示保持一致。</p>
<p>如果一个框右端固定，左端收缩（称之为<em>右定左动框</em>），也可以在任意时刻花费常数时间获得框内的最小元素。过程和上面左右对称。具体地说，每个元素对应一个指针，指向框内它右边（包括自身）所有数中最小者。首先需要从右到左计算出各个元素对应的指针。这个过程类似于将框的左端由右向左扩展。计算完毕后就可以在收缩左端的过程中在常数时间内获得最小元素了。</p>
<p>演示如下（先扩张后收缩，可以看到完全和上面对称）：</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/bhoj-10235_6.gif" src="http://upload.tomtung.com/img/bhoj-10235_6.gif" alt="http://upload.tomtung.com/img/bhoj-10235_6.gif" width="495" height="170" /></p>
<p>对于当前问题，在“扩张”过程中并不需要求最小值，而仅仅需要计算出指针来为收缩过程作准备。这里在全过程中标注出最小值是为了说明任意时刻都有能力得到它，也为了与上一个演示保持一致。</p>
<p>上面拉拉杂杂说了一堆，只为了说明：<strong>如果框的一端固定，仅移动另外一端，则在此过程中可以仅花费常数时间获得框内的最小元素。针对左定右移、右定左移两种情况的算法是彼此对称的。</strong></p>
<p>最后顺带提一点实现细节，希望不会影响你对整体的理解。上面所使用的指针也可以用索引号来代替，这里使用指针是为了显得更形象。事实上，不保存索引或指针而直接保存最小的“值”也是可行的，但是并不推荐。设想，如果每个元素不是数而是四五米长的字符串（&gt;_&lt;），偏序关系使用字符串长度的比较，那么如果不保存指针/索引而保存值，所带来的元素复制开销恐怕不是你想要的。</p>
<h3>二、推广</h3>
<p>怎样把上面只允许一端移动的解法推广到同时允许两端移动呢？</p>
<p>一种想法是，将以上二者合二为一。具体地说，将框内的数看成左右两部分，左边一部分看成右定左动的，右边一部分看成左定右动的。这样，在左边收缩、右边扩张的过程中，左右两部分都可以在常数时间内得到最小元素。取两个最小元素中更小者，即为整个框中的最小元素。</p>
<p>OK，这就是O(N)算法的基本思路了。回到原题目，下面看一个例子。</p>
<p>假设我们要处理的是这么一串数：</p>
<pre>3 1 4 1 5 9 2 6 5 3 5 8 9 7 9 3
</pre>
<p>框的大小为5。</p>
<p>首先，把最先被框住的5个数看成被一个右定左动的框框住，这5个数右边看成是一个空的左定右动框。当然，首先需要计算出开始这5个元素对应的指针：</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/bhoj-10235_7.gif" src="http://upload.tomtung.com/img/bhoj-10235_7.gif" alt="http://upload.tomtung.com/img/bhoj-10235_7.gif" width="477" height="96" /></p>
<p>接下来，左边的右定左动框收缩，右边的左定右动框扩张。在此过程中，框在框中的M个元素的最小者可以在常数时间内获得。</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/bhoj-10235_8.gif" src="http://upload.tomtung.com/img/bhoj-10235_8.gif" alt="http://upload.tomtung.com/img/bhoj-10235_8.gif" width="477" height="96" /></p>
<p>到左边部分收缩至空时就没有办法继续收缩了。怎么继续这个过程呢？</p>
<p>解决方法是，将右端含有M个元素的左定右动框重新处理为右定左动框，并在右边再放上一个空左定右动框：</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/bhoj-10235_9.gif" src="http://upload.tomtung.com/img/bhoj-10235_9.gif" alt="http://upload.tomtung.com/img/bhoj-10235_9.gif" width="477" height="96" /></p>
<p>接下来继续这个过程就行了。下面是全过程的演示：</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/bhoj-10235_X.gif" src="http://upload.tomtung.com/img/bhoj-10235_X.gif" alt="http://upload.tomtung.com/img/bhoj-10235_X.gif" width="477" height="96" /></p>
<p>你可能会对每次将右边的左定右动框重新处理为左边的右定左动框（有点晕- -）时计算指针造成的额外开销存有疑虑。然而，每次重新计算时需要处理M个元素的指针，每隔M个元素才会进行一次这样的处理，N/M*M仍然为N，并不会升高复杂度的阶。换句话说，每个元素至多被重新计算两次指针，所以总体复杂度仍然为O(N)。</p>
<p>以上就是整个解法。</p>
<p>三、关于栈</p>
<p>我们可以把右定左动框看成是一个底在右顶在左的栈，左端向左扩张看成是向栈push元素，左端向右收缩看成是栈在pop元素。左定右动框亦然。前面提到，两种框上的操作是对称的；如果把它们都看成栈，则push和pop时的操作完全一致。这样就将两种框对称的操纵统一在了同一个数据结构上，使得实现起来更为简洁。</p>
<p>这就是对开始所引用那段算法描述的解释。</p>
<p>四、问题的扩展</p>
<p>更进一步，我们还可以使用类似的方法处理此问题的变种。如，对于一个队列，随意进行入队出队操作（相当于木框的宽度不再固定为M，其两端也不同时向右移动），求在任意时刻队列中的最小元素。或者，一个双端队列，在两端随意进行入队出队操作，求任意时刻队列中的最小元素。等等。</p>
<p>最后顺便一提，在搜索此题资料时发现这个问题似乎涉及到数据流的处理算法。这个方面我一无所知，没办法站在那样一个高度阐述，抱歉。</p>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1014/' rel='bookmark' title='[VIJOS 1014]旅行商简化版'>[VIJOS 1014]旅行商简化版</a></li>
<li><a href='http://blog.tomtung.com/2007/05/museum-fire/' rel='bookmark' title='[IOI2000国家队原创题] 艺术馆的火灾'>[IOI2000国家队原创题] 艺术馆的火灾</a></li>
<li><a href='http://blog.tomtung.com/2007/07/noi05-cckk/' rel='bookmark' title='[NOI 05]聪聪与可可'>[NOI 05]聪聪与可可</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2009/03/bhoj-10235/feed/</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>小试模板元编程</title>
		<link>http://blog.tomtung.com/2009/03/try-template-meta-programming/</link>
		<comments>http://blog.tomtung.com/2009/03/try-template-meta-programming/#comments</comments>
		<pubDate>Sun, 01 Mar 2009 06:37:57 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[C++]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2009/03/%e5%b0%8f%e8%af%95%e6%a8%a1%e6%9d%bf%e5%85%83%e7%bc%96%e7%a8%8b/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2009/03/try-template-meta-programming/" title="小试模板元编程"></a>很久不更新了，再水一下吧。 前两天新开的数据结构课布置上机作业，里面又出现了不知道之前出现过多少次的输出质数。每次都交表也会很乏味，所以……这次还是要交表 &#8211; -&#124;&#124;&#124; 不过要玩一点小花样——让编译器在编译期把质数表算出来。 这个当然涉及到一点模板元编程了。之前虽然看了《Effective C++》里关于模板元的简介挺感兴趣，但看到《学习C++：实践者的方法》里告诫不要在这种“20%场景下的复杂性”上白花时间（“这些细节或技术在日常编程中极少用到，尤其是各种语言缺陷衍生出来的workarounds，构成了一个巨大的长尾……绝大多数只在库开发当中需要用到”），我一直对模板元编程敬而远之。这次也只是消遣一下，并无深入学习的打算。 以下是代码： #include &#60;iostream&#62; #include &#60;vector&#62; #include &#60;iterator&#62; #include &#60;algorithm&#62; std::vector&#60;int&#62; Primes; template &#60;int toTest, int factor&#62; // factor should be odd class IsPrime { public: enum { result = ( toTest == 2 &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2009/03/try-template-meta-programming/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter2/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第二章'>C++ Primer 读书笔记 &#8211; 第二章</a></li>
<li><a href='http://blog.tomtung.com/2007/06/c-cpp-io-odd-behavior/' rel='bookmark' title='诡异的事：关于C/C++输入输出'>诡异的事：关于C/C++输入输出</a></li>
<li><a href='http://blog.tomtung.com/2008/09/cpp-primer-reding-notes-chapter8/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第八章'>C++ Primer 读书笔记 &#8211; 第八章</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2009/03/try-template-meta-programming/" title="小试模板元编程"></a><p>很久不更新了，再水一下吧。</p>
<p>前两天新开的数据结构课布置上机作业，里面又出现了不知道之前出现过多少次的<a href="http://acm.buaa.edu.cn/oj/problem_show.php?c=99&amp;p=101330" target="_blank">输出质数</a>。每次都交表也会很乏味，所以……这次还是要交表 &#8211; -||| 不过要玩一点小花样——让编译器在编译期把质数表算出来。</p>
<p>这个当然涉及到一点<a href="http://zh.wikipedia.org/w/index.php?title=%E6%A8%A1%E6%9D%BF%E5%85%83%E7%B7%A8%E7%A8%8B&amp;variant=zh-cn" target="_blank">模板元编程</a>了。之前虽然看了《Effective C++》里关于模板元的简介挺感兴趣，但看到《<a href="http://blog.csdn.net/pongba/archive/2007/12/11/1930150.aspx" target="_blank">学习C++：实践者的方法</a>》里告诫不要在这种“20%场景下的复杂性”上白花时间<span style="color: #666666;">（“这些细节或技术在日常编程中极少用到，尤其是各种语言缺陷衍生出来的workarounds，构成了一个巨大的长尾……绝大多数只在库开发当中需要用到”）</span>，我一直对模板元编程敬而远之。这次也只是消遣一下，并无深入学习的打算。</p>
<p>以下是代码：</p>
<pre class="brush:c++">#include &lt;iostream&gt;
#include &lt;vector&gt;
#include &lt;iterator&gt;
#include &lt;algorithm&gt;

std::vector&lt;int&gt; Primes;

template &lt;int toTest, int factor&gt; // factor should be odd
class IsPrime
{
   public:
      enum {
         result = ( toTest == 2 )
         ||  toTest % factor
          &amp;&amp; IsPrime &lt; toTest , factor - 2 &gt;::result
      };
};

template&lt;int toTest&gt;
class IsPrime&lt;toTest, 1&gt;
{
   public:
      enum {result = ( toTest == 2 )  || ( toTest &amp; 1 ) };
};

template &lt;int upperBound&gt; // upperBound should be odd or 2
class PrimePick : public PrimePick &lt; upperBound - 2 &gt;
{
   public:
      enum {
         isPrime = IsPrime &lt; upperBound, ( upperBound &gt;&gt; 1 ) | 1 &gt;::result
      };
      PrimePick&lt;upperBound&gt;() {
         if ( isPrime )
            Primes.push_back ( upperBound );
      }
};

template&lt;&gt;
class PrimePick&lt;2&gt;
{
   public:
      PrimePick&lt;2&gt;() {
         Primes.push_back ( 2 );
      }
};

template&lt;&gt;
class PrimePick&lt;1&gt; : public PrimePick&lt;2&gt; {};

int main()
{
   PrimePick&lt;999&gt; PrimeInitializer;

   int m;
   std::cin &gt;&gt; m;
   for ( int i = 0; i &lt; m; ++i ) {
      int n;
      std::cin &gt;&gt; n;

      std::vector&lt;int&gt;::iterator end = Primes.begin();
      while ( end != Primes.end() &amp;&amp; *end &lt;= n )
         ++end;

      std::ostream_iterator&lt;int&gt; out ( std::cout, " " );
      std::copy ( Primes.begin(), end, out );
      std::cout &lt;&lt; std::endl;
   }
}
</pre>
<p>原理比较简单，主函数第一行初始化 PrimeInitializer 时，由于继承关系，构造函数会层层递归调用，从里至外利用另一个模板类 IsPrime 判断模板参数是否是质数，并把测试确认的质数放进一个 vector 里，这样就得到了编译期计算出的质数表。IsPrime 则使用最原始的试除方法判断质数。代码里一些写得很纠结的地方，一方面是为了尽量简化计算，减少编译时间，另一方面更主要是因为 g++ 默认最大只能实例化500层模板，以题目的数据规模（1000）不纠结一下编译器会抱怨。</p>
<p>这个程序……很不幸没能 AC。Buaa 的 OJ 在设计时估计就考虑了这种情况，对编译时间做出了限制，在编译20秒左右还没编译成功时会结束编译，直接判 <a href="http://acm.buaa.edu.cn/oj/status.php?problem_id=101330&amp;user_id=38211427&amp;result=-1&amp;c=99&amp;viewall=1" target="_blank">CE</a>……这个程序在我的系统上编译需要20分钟(VC 编译)到半个小时以上( g++ 编译)的时间，冬冬的64位 Ubuntu 上用 g++ 编译也需要将近10分钟时间。如果OJ不做这个限制估计会像当年vijos一样pending很多页吧……</p>
<p>恩，第一次模板元编程经历就以这样悲惨收场了 T_T</p>
<p>更新：</p>
<p>vijos<a href="http://fanfou.com/statuses/RNTyv6omdrE" target="_blank">果然被卡住了</a> &#8211; -</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/vijos-puppy-stuck.jpg" src="http://upload.tomtung.com/img/vijos-puppy-stuck.jpg" alt="http://upload.tomtung.com/img/vijos-puppy-stuck.jpg" width="621" height="505" /></p>
<p>可怜的puppy</p>
<p>已作为bug报告。</p>
<div id="_mcePaste" style="position: absolute; left: -10000px; top: 84px; width: 1px; height: 1px; overflow: hidden;"><span style="font-family: monospace;"><span style="color: #a020f0;">#include</span> <span style="color: #ff00ff;">&lt;iostream&gt;</span><br />
<span style="color: #a020f0;">#include</span> <span style="color: #ff00ff;">&lt;vector&gt;</span><br />
<span style="color: #a020f0;">#include</span> <span style="color: #ff00ff;">&lt;iterator&gt;</span><br />
<span style="color: #a020f0;">#include</span> <span style="color: #ff00ff;">&lt;algorithm&gt;</span></p>
<p>std::vector&lt;<span style="color: #2e8b57;"><strong>int</strong></span>&gt; Primes;</p>
<p><span style="color: #2e8b57;"><strong>template</strong></span> &lt;<span style="color: #2e8b57;"><strong>int</strong></span> toTest, <span style="color: #2e8b57;"><strong>int</strong></span> factor&gt; <span style="color: #0000ff;">// factor should be odd</span><br />
<span style="color: #2e8b57;"><strong>class</strong></span> IsPrime<br />
{<br />
<span style="color: #804040;"><strong>public</strong></span>:<br />
<span style="color: #2e8b57;"><strong>enum</strong></span> {<br />
result = ( toTest == <span style="color: #ff00ff;">2</span> )<br />
||  toTest % factor<br />
&amp;&amp; IsPrime &lt; toTest , factor &#8211; <span style="color: #ff00ff;">2</span> &gt;::result<br />
};<br />
};</p>
<p><span style="color: #2e8b57;"><strong>template</strong></span>&lt;<span style="color: #2e8b57;"><strong>int</strong></span> toTest&gt;<br />
<span style="color: #2e8b57;"><strong>class</strong></span> IsPrime&lt;toTest, <span style="color: #ff00ff;">1</span>&gt;<br />
{<br />
<span style="color: #804040;"><strong>public</strong></span>:<br />
<span style="color: #2e8b57;"><strong>enum</strong></span> {result =  ( toTest == <span style="color: #ff00ff;">2</span> )  || ( toTest &amp; <span style="color: #ff00ff;">1</span> ) };<br />
};</p>
<p><span style="color: #2e8b57;"><strong>template</strong></span> &lt;<span style="color: #2e8b57;"><strong>int</strong></span> upperBound&gt; <span style="color: #0000ff;">// upperBound should be odd or 2</span><br />
<span style="color: #2e8b57;"><strong>class</strong></span> PrimePick : <span style="color: #804040;"><strong>public</strong></span> PrimePick &lt; upperBound &#8211; <span style="color: #ff00ff;">2</span> &gt;<br />
{<br />
<span style="color: #804040;"><strong>public</strong></span>:<br />
<span style="color: #2e8b57;"><strong>enum</strong></span> {<br />
isPrime = IsPrime &lt; upperBound, ( upperBound &gt;&gt; <span style="color: #ff00ff;">1</span> ) | <span style="color: #ff00ff;">1</span> &gt;::result<br />
};<br />
PrimePick&lt;upperBound&gt;() {<br />
<span style="color: #804040;"><strong>if</strong></span> ( isPrime )<br />
Primes.push_back (  upperBound );<br />
}<br />
};</p>
<p><span style="color: #2e8b57;"><strong>template</strong></span>&lt;&gt;<br />
<span style="color: #2e8b57;"><strong>class</strong></span> PrimePick&lt;<span style="color: #ff00ff;">2</span>&gt;<br />
{<br />
<span style="color: #804040;"><strong>public</strong></span>:<br />
PrimePick&lt;<span style="color: #ff00ff;">2</span>&gt;() {<br />
Primes.push_back ( <span style="color: #ff00ff;">2</span> );<br />
}<br />
};</p>
<p><span style="color: #2e8b57;"><strong>template</strong></span>&lt;&gt;<br />
<span style="color: #2e8b57;"><strong>class</strong></span> PrimePick&lt;<span style="color: #ff00ff;">1</span>&gt; : <span style="color: #804040;"><strong>public</strong></span> PrimePick&lt;<span style="color: #ff00ff;">2</span>&gt; {};</p>
<p><span style="color: #2e8b57;"><strong>int</strong></span> main()<br />
{<br />
PrimePick&lt;<span style="color: #ff00ff;">999</span>&gt; PrimeInitializer;</p>
<p><span style="color: #2e8b57;"><strong>int</strong></span> m;<br />
std::cin &gt;&gt; m;<br />
<span style="color: #804040;"><strong>for</strong></span> ( <span style="color: #2e8b57;"><strong>int</strong></span> i = <span style="color: #ff00ff;">0</span>;  i &lt; m; ++i ) {<br />
<span style="color: #2e8b57;"><strong>int</strong></span> n;<br />
std::cin &gt;&gt; n;</p>
<p>std::vector&lt;<span style="color: #2e8b57;"><strong>int</strong></span>&gt;::iterator end  = Primes.begin();<br />
<span style="color: #804040;"><strong>while</strong></span> ( end != Primes.end() &amp;&amp; *end &lt;= n )<br />
++end;</p>
<p>std::ostream_iterator&lt;<span style="color: #2e8b57;"><strong>int</strong></span>&gt; out ( std::cout, <span style="color: #ff00ff;">&#8221; &#8220;</span> );<br />
std::copy ( Primes.begin(), end, out );<br />
std::cout &lt;&lt; std::endl;<br />
}<br />
}</p>
<p></span></div>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter2/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第二章'>C++ Primer 读书笔记 &#8211; 第二章</a></li>
<li><a href='http://blog.tomtung.com/2007/06/c-cpp-io-odd-behavior/' rel='bookmark' title='诡异的事：关于C/C++输入输出'>诡异的事：关于C/C++输入输出</a></li>
<li><a href='http://blog.tomtung.com/2008/09/cpp-primer-reding-notes-chapter8/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第八章'>C++ Primer 读书笔记 &#8211; 第八章</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2009/03/try-template-meta-programming/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>C++ Primer 读书笔记 &#8211; 第八章</title>
		<link>http://blog.tomtung.com/2008/09/cpp-primer-reding-notes-chapter8/</link>
		<comments>http://blog.tomtung.com/2008/09/cpp-primer-reding-notes-chapter8/#comments</comments>
		<pubDate>Tue, 02 Sep 2008 07:12:15 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[笔记]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2008/09/c-primer-%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-%e7%ac%ac%e5%85%ab%e7%ab%a0/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2008/09/cpp-primer-reding-notes-chapter8/" title="C++ Primer 读书笔记 - 第八章"></a>[笔记索引] 第８章 标准IO库 ⒈ IO标准库类型 类名           派生自            头文件       描述 istream        ios               iostream   输入流 ostream        ios               iostream   输出流 iostream       istream和ostream  iostream   输入/输出流 ifstream       istream           fstream    输入文件流 ofstream       ostream           fstream    输出文件流 fstream        iostream          fstream    输入/输出文件流 istringstream  istream           sstream    输入字符串流 ostringstream  ostream           sstream    输出字符串流 stringstream   iostream          sstream    输入/输出字符串流 ⑴ 标准库分别实现了以上继承层次的的两个版本，分别面向 char 类型（如上）和 wchar_t 类型 后者采用和前者一样的命名规则，仅在每个类和对象名前加上字母w以示区别 ⑵ IO对象不可赋值或赋值 ⒉ &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2008/09/cpp-primer-reding-notes-chapter8/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter4/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第四章'>C++ Primer 读书笔记 &#8211; 第四章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter2/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第二章'>C++ Primer 读书笔记 &#8211; 第二章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter5/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第五章'>C++ Primer 读书笔记 &#8211; 第五章</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2008/09/cpp-primer-reding-notes-chapter8/" title="C++ Primer 读书笔记 - 第八章"></a><p style="text-align: right;"><a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/" target="_blank"><span style="font-size: 12px;"> [笔记索引]</span></a></p>
<p><span style="font-size: 12px;">第８章 标准IO库</span></p>
<p><span style="font-size: 12px;"> ⒈ IO标准库类型<br />
类名           派生自            头文件       描述<br />
istream        ios               iostream   输入流<br />
ostream        ios               iostream   输出流<br />
iostream       istream和ostream  iostream   输入/输出流<br />
ifstream       istream           fstream    输入文件流<br />
ofstream       ostream           fstream    输出文件流<br />
fstream        iostream          fstream    输入/输出文件流<br />
istringstream  istream           sstream    输入字符串流<br />
ostringstream  ostream           sstream    输出字符串流<br />
stringstream   iostream          sstream    输入/输出字符串流</span></p>
<p><span style="font-size: 12px;"> ⑴ 标准库分别实现了以上继承层次的的两个版本，分别面向 </span><span style="color: #2e8b57;"><strong>char</strong></span> 类型（如上）和 <span style="color: #2e8b57;"><strong>wchar_t</strong></span> 类型<br />
后者采用和前者一样的命名规则，仅在每个类和对象名前加上字母w以示区别<br />
⑵ IO对象不可赋值或赋值</p>
<p>⒉ 标准输入输出对象<br />
<span style="color: #2e8b57;"><strong>extern</strong></span> istream cin; 标准输入流<br />
<span style="color: #2e8b57;"><strong>extern</strong></span> ostream cout; 标准输出流<br />
<span style="color: #2e8b57;"><strong>extern</strong></span> ostream cerr;  标准错误输出流<br />
<span style="color: #2e8b57;"><strong>extern</strong></span> ostream clog; 标准日志输出流</p>
<p>⒊ 文件输入输出流<br />
⑴ 使用 ifstream, ofstream 和 fstream 型对象可以实现对文件的读写<br />
⑵ 可以调用公共成员函数 open 或在创建时使用构造函数使流对象关联到一个文件<br />
二者都接受两个参数：一个C风格字符串表示文件名，一个可选的 ios_base::openmode 型值表示流打开模式<br />
① openmode 是用以表示流打开模式标识的位掩码类型，支持位操作符<br />
该类型对象的值可以是以下打开模式标识或它们之间进行位操作所得的结果<br />
和以下(openmode 型)模式标志均为 ios_base 的公共成员<br />
Ⅰ app (append)每次执行输出操作前都定位到流的末尾<br />
Ⅱ ate (at end)打开时定位到流末尾<br />
Ⅲ binary 在二进制(而非文本)模式下操作<br />
Ⅳ in (input)允许输入操作<br />
Ⅴ out (output)允许输出操作<br />
Ⅵ trunc (truncate)抛弃流中现存的所有内容，打开时假定长度为<span style="color: #ff00ff;">0</span><br />
(openmode 和上述标识分别是 ios_base 的公共成员类型和公共成员常量)<br />
② fstream 默认使用 in | out 模式<br />
ifstream 自动使用 in 模式<br />
ofstream 自动使用 out 模式(效果上等同于 out | trunc)<br />
⑶ 完成文件操作后可以使用公共成员函数 <span style="color: #2e8b57;"><strong>void</strong></span> close() 关闭<br />
关闭后可以再次 open 新文件，但之前应调用 clear 清除该流的状态<br />
流对象被析构时也会自动调用 close()</p>
<p>⒋ 字符串流<br />
⑴ 常用于特定数据类型和格式化的字符串之间的相互转换<br />
⑵ <span style="color: #ff00ff;">3</span>个类的构造函数各有带或不带 string 形参的两个不同版本<br />
使用带 string 参数的版本，则创建储存该实参副本的字符串流对象<br />
⑶ <span style="color: #ff00ff;">3</span>个类的都有公共成员函数 str，它的两个版本分别返回和设置与该流对象相关的 string 对象<br />
string str() <span style="color: #2e8b57;"><strong>const</strong></span>;<br />
返回与该流对象相关的 string 对象<br />
string str(<span style="color: #2e8b57;"><strong>const</strong></span> string&amp;);<br />
将该流对象相关的 string 对象设置为实参的副本</p>
<p>⒌ 条件状态<br />
⑴ 流对象内都用一个 iostate 型值表示条件状态<br />
· iostate 是用以表示流错误状态标志的位掩码类型，支持位操作符<br />
该类型对象的值可以是以下条件状态标识或它们之间进行位操作所得的结果<br />
Ⅰ eofbit 输入操作中遇到文件结束符(同时会设置failbit)<br />
Ⅱ failbit 输入操作失败(通常可恢复)<br />
Ⅲ badbit 流被损坏(通常不可恢复)<br />
Ⅳ goodbit 没有错误，值为<span style="color: #ff00ff;">0</span><br />
(iostate 和上述标识分别是 ios_base 的公共成员类型和公共成员常量)<br />
⑵ 查询条件状态<br />
<span style="color: #2e8b57;"><strong>bool</strong></span> ios::eof() <span style="color: #2e8b57;"><strong>const</strong></span>;<br />
eofbit位被设置则返回 <span style="color: #ff00ff;">true</span>, 否则返回 <span style="color: #ff00ff;">false</span><br />
<span style="color: #2e8b57;"><strong>bool</strong></span> ios::fail() <span style="color: #2e8b57;"><strong>const</strong></span>;<br />
failbit 或 badbit 位被设置则返回 <span style="color: #ff00ff;">true</span>, 否则返回 <span style="color: #ff00ff;">false</span><br />
<span style="color: #2e8b57;"><strong>bool</strong></span> ios::bad() <span style="color: #2e8b57;"><strong>const</strong></span>;<br />
badbit 位被设置则返回 <span style="color: #ff00ff;">true</span>, 否则返回 <span style="color: #ff00ff;">false</span><br />
<span style="color: #2e8b57;"><strong>bool</strong></span> ios::good() <span style="color: #2e8b57;"><strong>const</strong></span>;<br />
流对象有效则返回 <span style="color: #ff00ff;">true</span>, 否则返回 <span style="color: #ff00ff;">false</span><br />
ios_base::iostate rdstate() <span style="color: #2e8b57;"><strong>const</strong></span>;<br />
返回当前条件状态<br />
⑶ 控制条件状态<br />
<span style="color: #2e8b57;"><strong>void</strong></span> ios::clear( ios_base::iostate = goodbit);<br />
使用参数值替换当前条件状态<br />
<span style="color: #2e8b57;"><strong>void</strong></span> ios::setstate( ios_base::iostate );<br />
将参数状态加入当前状态(如同使用了位或操作符|)<br />
⑷ 可以直接检查流对象的真值来确认是否可用</p>
<p>⒍ 输出缓冲区的管理<br />
每个IO对象都管理一个缓冲区，作为流和写入目标间的媒介<br />
下面几种情况将导致输出流被刷新(即写入到真实的输出设备或者文件)<br />
⑴ 程序正常结束或目标文件被关闭<br />
注: 程序崩溃不会刷新缓冲区<br />
调试崩溃的程序时如果用最后的输出定义错误位置，应确保缓冲区已刷新<br />
因此输出时应多使用endl而非<span style="color: #6a5acd;">&#8216;\n&#8217;</span><br />
⑵ 当缓冲区满时会自动刷新<br />
⑶ 使用操纵符显式刷新缓冲区<br />
(以下均定义在&lt;ostream&gt;中)<br />
① endl 输出换行符并刷新缓冲区<br />
② flush 刷新缓冲区(不添加任何字符)<br />
③ ends 输出空字符<span style="color: #6a5acd;">&#8216;\0&#8242;</span>并刷新缓冲区<br />
注：cplusplus.com,cppreference.com和msdn上都没有表示ends会刷新缓冲区，存疑<br />
⑷ 使用 unitbuf 操纵符可以设置流对象的相关格式状态标志，使缓冲区在每次插入操作后都刷新<br />
使用 nounitbuf 操纵符可以恢复为正常的、系统控制的缓冲区刷新方式<br />
⑸ 可以把流对象绑定到一个输出流对象，前者进行任何I/O操作都会刷新后者<br />
① 可以使用 ios::tie 公共成员函数查询、修改绑定状态<br />
ostream* ios::tie() <span style="color: #2e8b57;"><strong>const</strong></span>;<br />
返回指向所绑定到的输出流对象的指针<br />
ostream* ios::tie( ostream* );<br />
绑定到参数中指针指向的输出流对象(实参为<span style="color: #ff00ff;">0</span>则解除绑定)，并返回指向原绑定对象的指针<br />
② cin, cerr, clog 默认绑定到 cout, 而它们的宽字符版本则默认绑定到 wcout</p>
<p>⒎ 控制格式状态<br />
istream/ostream(及它们的派生类)对象都支持使用提取操作符(&gt;&gt;)/插入操作符(&lt;&lt;)，从流中提取/向流中插入格式化的对象数据<br />
把提取/插入操作符和一些操纵符(manipulator)一起使用，可以改变流对象的特性或格式设置<br />
其中除setbase,setprecision,setw,setfill定义在头文件&lt;iomanip&gt;中外，其余操纵符均定义在&lt;ios&gt;中<br />
⑴ 布尔值格式<br />
从流中提取/向流中插入布尔值时形式使用 <span style="color: #ff00ff;">0</span>, <span style="color: #ff00ff;">1</span>(默认) 还是 <span style="color: #ff00ff;">false</span>, <span style="color: #ff00ff;">true</span><br />
· 使用操纵符 boolalpha noboolalpha<br />
⑵ 整数格式<br />
① 基数设置<br />
从流中提取/向流中插入整数时使用八进制、十进制(默认)还是十六进制<br />
· 使用操纵符 oct dec hex<br />
· 使用操纵符 setbase(<span style="color: #2e8b57;"><strong>int</strong></span>) (实参只能是<span style="color: #ff00ff;">8</span>,<span style="color: #ff00ff;">10</span>或<span style="color: #ff00ff;">16</span>)<br />
② 基数前缀设置<br />
向流中插入整数时是否显示基数前缀(八进制的<span style="color: #ff00ff;">0</span>和十六进制的0x)(默认不显示)<br />
· 使用操纵符 showbase noshowbase<br />
③ 十六进制和科学计数法中字母的大小写设置<br />
向流中插入整数时若使用十六进制或科学计数法，出现的字母使用小写(默认)还是大写<br />
· 使用操纵符 uppercase nouppercase<br />
⑶ 浮点数格式<br />
① 精度设置<br />
向流中插入浮点数精度为多少(默认为<span style="color: #ff00ff;">6</span>)<br />
· 使用 ios_base 的公共成员函数 precision 可以获得和设置流的浮点数精度<br />
streamsize precision() <span style="color: #2e8b57;"><strong>const</strong></span>;<br />
返回流对象的浮点数精度<br />
streamsize precision( streamsize );<br />
设置流的浮点数精度为实参值并返回先前的精度值<br />
· 使用操纵符 setprecision( streamsize )<br />
以上 streamsize 为表示流中大小的实现类型，是一个带符号基本整型的别名<br />
注: 精度的解释在使用不同情况下不同<br />
· 默认情况下解释为最大有效数字(位数不足不用小数末尾的零补齐)<br />
· 在强制使用fixed,scientific或showpoint时解释为小数点后的确切位数(位数不足则用小数末尾的零补齐)<br />
② 计数法设置<br />
向流中插入浮点数时，让系统自动选择计数法(默认)，还是强制指定为定点计数法或科学计数法<br />
· 使用操纵符 fixed scientific<br />
· 计数法不存在恢复默认的操纵符，但可以通过给成员函数 unsetf 传入常量 floatfield 达到目的<br />
(unsetf 和 floatfield 均为 ios_base 的公共成员)<br />
③ 小数点显示设置<br />
向流中插入浮点数时，若小数部分为<span style="color: #ff00ff;">0</span>，是否显示小数点和小数部分的<span style="color: #ff00ff;">0</span>(默认不显示)<br />
· 使用操纵符 showpoint noshowpoint<br />
④ 非负数(整数和浮点数)正号设置<br />
向流中插入非负数(包括<span style="color: #ff00ff;">0</span>)时是否显示正号+ (默认不显示)<br />
· 使用操纵符 showpos noshowpos<br />
⑷ 对齐和填充格式<br />
① 使用操纵符 setw( streamsize ) 可指定下次对流的插入操作的最小字段长度<br />
注: 和endl一样，它不改变流的内部状态，只影响下一个输出<br />
② 使用操纵符 setfill( <span style="color: #2e8b57;"><strong>char</strong></span> ) 设置向流中插入对象时用来填充字段的字符(默认为空格)<br />
③ 字段内对齐方式设置(默认左对齐)<br />
· 使用操纵符left right internal<br />
关于internal:<br />
- 对数值表示左对齐前缀(正负号和十六进制的0x)右对齐数值<br />
- 对非数值则与 right 等效<br />
⑸ 空白字符的处理<br />
· 使用操纵符 skipws noskipws 设置从流中提取对象时是否忽略空白字符(默认忽略)<br />
· 使用操纵符 ws 忽略输入序列中从当前位置开始尽可能多的空白字符，直到遇到非空白字符才停止<br />
当然，如果已经(默认)设置了 skipws 则没有必要使用 ws</p>
<p>⒏ 无格式I/O操作<br />
⑴ 单字节操作<br />
① istream&amp; get ( <span style="color: #2e8b57;"><strong>char</strong></span>&amp; );<br />
istream 的公共成员函数，从流中提取一个字符存入实参中<br />
<span style="color: #2e8b57;"><strong>int</strong></span> get( );<br />
无参数版本从流中提取一个字符并返回其值<br />
② ostream&amp; put ( <span style="color: #2e8b57;"><strong>char</strong></span> );<br />
ostream 的公共成员函数，将字符参数值写入输出缓冲区并返回*<span style="color: #804040;"><strong>this</strong></span><br />
③ istream&amp; putback ( <span style="color: #2e8b57;"><strong>char</strong></span> );<br />
istream 的公共成员函数，将字符参数值放回流中并返回*<span style="color: #804040;"><strong>this</strong></span><br />
④ istream&amp; unget ( );<br />
istream 的公共成员函数，将流退回<span style="color: #ff00ff;">1</span>字节并返回*<span style="color: #804040;"><strong>this</strong></span><br />
⑤ <span style="color: #2e8b57;"><strong>int</strong></span> peek ( );<br />
istream 的公共成员函数，读取并返回流中的下一个字符，但不提取它<br />
注: 为允许返回EOF，put(无参数版本)和peek的返回值都为 <span style="color: #2e8b57;"><strong>int</strong></span><br />
⑵ 多字节操作<br />
① istream&amp; get (<span style="color: #2e8b57;"><strong>char</strong></span>* s, streamsize n, <span style="color: #2e8b57;"><strong>char</strong></span> delim );<br />
istream 的公共成员函数，不断从流中提取字符并以字符串形式存入s指向首地址的数组，<br />
直到出现以下情况之一:<br />
- 已经提取了(n-<span style="color: #ff00ff;">1</span>)个字符;<br />
- 遇到了定界字符delim(如果不提供此参数则为<span style="color: #6a5acd;">&#8216;\n&#8217;</span>);<br />
(被找到的定界字符不会被提取，而是继续留在流中作为下一个要被提取的字符)<br />
- 到达文件末尾;<br />
- 提取过程中发生错误;<br />
最后返回*<span style="color: #804040;"><strong>this</strong></span>.<br />
注: 提取的字符以字符串的形式储存，故将自动添加结尾的<span style="color: #6a5acd;">&#8216;\0&#8242;</span>，而被提取的最大字符数也为(n-<span style="color: #ff00ff;">1</span>)而非n<br />
② istream&amp; getline (<span style="color: #2e8b57;"><strong>char</strong></span>* s, streamsize n, <span style="color: #2e8b57;"><strong>char</strong></span> delim );<br />
istream 的公共成员函数，与上面参数表相同的get版本类似，唯一区别在于定界字符会被提取并丢弃<br />
③ istream&amp; read ( <span style="color: #2e8b57;"><strong>char</strong></span>* s, streamsize n );<br />
istream 的公共成员函数，从流中提取n个字符存入s指向首地址的数组，<br />
直到出现以下情况之一：<br />
- 已经提取了n个字符<br />
- 到达文件末尾(eofbit和failbit会被设置)<br />
- 提取过程中发生错误<br />
最后返回*<span style="color: #804040;"><strong>this</strong></span>.<br />
注: 与get和getline不同，read不把提取的字符保存为字符串，故不会自动加上<span style="color: #6a5acd;">&#8216;\0&#8242;</span><br />
④ streamsize  gcount ( ) <span style="color: #2e8b57;"><strong>const</strong></span>;<br />
istream 的公共成员函数，返回上次无格式输入操作提取的字符个数<br />
(peek, putback, unget不提取字符，故对此gcount返回<span style="color: #ff00ff;">0</span>)<br />
⑤ ostream&amp; write ( <span style="color: #2e8b57;"><strong>const</strong></span> <span style="color: #2e8b57;"><strong>char</strong></span>* s , streamsize n );<br />
ostream 的公共成员函数，把s指向首地址的数组的前n个字符写入输出流缓冲区<br />
⑥ istream&amp;  ignore ( streamsize n = <span style="color: #ff00ff;">1</span>, <span style="color: #2e8b57;"><strong>int</strong></span> delim = <span style="color: #ff00ff;">EOF</span> );<br />
istream 的公共成员函数，从流中提取并抛弃字符，直到:<br />
- 已经提取了n个字符或遇到了定界字符delim(该字符不会被提取)<br />
最后返回*<span style="color: #804040;"><strong>this</strong></span>.</p>
<p>⒐ 流的随机访问<br />
⑴ 用于随机访问的成员函数<br />
· pos_type tellg ();<br />
pos_type tellp ();<br />
返回输入/输出流中的标记当前的绝对位置<br />
· istream&amp; seekg ( pos_type pos );<br />
ostream&amp; seekp ( pos_type pos );<br />
将输入/输出流中的标记重新定位至参数值表示的位置<br />
· istream&amp; seekg ( off_type off, ios_base::seekdir dir );<br />
ostream&amp; seekp ( off_type off, ios_base::seekdir dir );<br />
将输入/输出流中的标记重新定位至距离dir偏移量为off的位置<br />
⑵ 关于上述函数参数和返回值的类型<br />
① pos_type和off_type都是类的成员类型，分别表示流中的标记位置和偏移量<br />
后者可正可负，分别表示向前和向后偏移<br />
② seekdir类型用来表示偏移的启示位置，其可能值如下<br />
beg 流的开头<br />
cur 流的当前位置<br />
end 流的末尾<br />
⑶ 以上tell和seek版本分别有两个版本g(get)和p(put)<br />
其区别在于g版是istream类的成员，而p版是ostream类的成员<br />
对于iostream对象，而者都适用，它们都操作同一个标记(而非输入输出各一个)<br />
⑷ 普通iostream对象一般不允许随机访问，以上内容主要适用fstream和sstream</p>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter4/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第四章'>C++ Primer 读书笔记 &#8211; 第四章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter2/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第二章'>C++ Primer 读书笔记 &#8211; 第二章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter5/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第五章'>C++ Primer 读书笔记 &#8211; 第五章</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2008/09/cpp-primer-reding-notes-chapter8/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>C++ Primer 读书笔记 &#8211; 第七章</title>
		<link>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter7/</link>
		<comments>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter7/#comments</comments>
		<pubDate>Thu, 28 Aug 2008 09:14:50 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[笔记]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2008/08/c-primer-%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-%e7%ac%ac%e4%b8%83%e7%ab%a0/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter7/" title="C++ Primer 读书笔记 - 第七章"></a>[笔记索引] 第７章 函数 ㈠ 函数的声明和定义 ⒈ 与变量类似： ⑴ 函数必须在调用前声明 ⑵ 函数声明可与定义分离 ⑶ 一个函数只能定义一次但可声明多次 ⒉ 函数声明由函数返回类型、函数返回类型和形参列表组成 三者描述了函数的接口，称为函数原型(function prototype) ⑴ 函数的操作数，即形参(parameter)，在一对圆括号中声明，并以逗号分隔 形参名是可选的，但形参需要在定义函数时命名才能使用 ⑵ 函数执行的运算在一个称为函数体(function body)的块语句中定义 ⒊ 函数一般在头文件中声明，在源文件中定义 此时应使后者包含前者，以便编译器检查定义和声明是否一致 ⒋ 将一个较小的、常被调用的函数指定为 inline 可使函数在调用点展开，以避免调用函数的额外开销 但内联说明对编译器来说只是一个建议，编译器也可能选择忽略 ㈡ 函数的调用和参数传递 ⒈ 函数的调用 ⑴ 使用调用操作符()实现函数调用 ① 操作数是函数名和一组(可能为空的)用逗号分开的实参(argument) ② &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter7/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter4/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第四章'>C++ Primer 读书笔记 &#8211; 第四章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter2/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第二章'>C++ Primer 读书笔记 &#8211; 第二章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter5/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第五章'>C++ Primer 读书笔记 &#8211; 第五章</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter7/" title="C++ Primer 读书笔记 - 第七章"></a><p style="text-align: right;"><a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/" target="_blank"><span style="font-size: 12px;"> [笔记索引]</span></a></p>
<p><span style="font-size: 12px;">第７章 函数</span></p>
<p><span style="font-size: 12px;"> ㈠ 函数的声明和定义<br />
⒈ 与变量类似：<br />
⑴ 函数必须在调用前声明<br />
⑵ 函数声明可与定义分离<br />
⑶ 一个函数只能定义一次但可声明多次<br />
⒉ 函数声明由函数返回类型、函数返回类型和形参列表组成<br />
三者描述了函数的接口，称为函数原型(function prototype)<br />
⑴ 函数的操作数，即形参(parameter)，在一对圆括号中声明，并以逗号分隔<br />
形参名是可选的，但形参需要在定义函数时命名才能使用<br />
⑵ 函数执行的运算在一个称为函数体(function body)的块语句中定义<br />
⒊ 函数一般在头文件中声明，在源文件中定义<br />
此时应使后者包含前者，以便编译器检查定义和声明是否一致<br />
⒋ 将一个较小的、常被调用的函数指定为 </span><span style="color: #2e8b57;"><strong>inline</strong></span> 可使函数在调用点展开，以避免调用函数的额外开销<br />
但内联说明对编译器来说只是一个建议，编译器也可能选择忽略</p>
<p>㈡ 函数的调用和参数传递<br />
⒈ 函数的调用<br />
⑴ 使用调用操作符()实现函数调用<br />
① 操作数是函数名和一组(可能为空的)用逗号分开的实参(argument)<br />
② 结果类型为函数返回值的类型，结果为函数的返回值<br />
⑵ 函数调用做两件事：<br />
① 首先(隐式)定义形参，并用对应的实参进行初始化<br />
· 形参的初始化与变量一样：<br />
如果形参为非引用类型则复制实参的值，如果形参为引用类型则它只是实参的别名<br />
② 主调函数(calling function)的执行被挂起，被调函数(called function)开始执行<br />
⒉ 非引用形参<br />
⑴ 指针形参<br />
① 可以通过传入指针来间接访问指针所指对象<br />
② 若需要保护指针指向的对象，可指定形参为指向 <span style="color: #2e8b57;"><strong>const</strong></span> 对象的指针<br />
⑵ <span style="color: #2e8b57;"><strong>const</strong></span> 形参<br />
① 用来初始化 <span style="color: #2e8b57;"><strong>const</strong></span> 形参的实参无论是不是 <span style="color: #2e8b57;"><strong>const</strong></span> 都可以<br />
在函数中不能改变该实参的局部副本<br />
② 非引用形参是否为 <span style="color: #2e8b57;"><strong>const</strong></span> 不影响编译器对其所属函数类型的判别<br />
·注：对于指针，注意区分 <span style="color: #2e8b57;"><strong>const</strong></span> 是修饰指针本身的性质还是所指对象的性质<br />
⑶ 不适合复制实参的情况<br />
① 需要修改实参值时<br />
② 需要将大型对象作为实参传递时<br />
③ 无法复制对象时<br />
以上情况可通过将形参定义为指针或引用类型解决<br />
⒊ 引用形参<br />
⑴ 可以使用引用形参返回额外信息<br />
⑵ 可以利用 <span style="color: #2e8b57;"><strong>const</strong></span> 引用形参避免复制实参<br />
⑶ 使用引用形参时，将不需要修改的定义为 <span style="color: #2e8b57;"><strong>const</strong></span> 会更灵活<br />
非 <span style="color: #2e8b57;"><strong>const</strong></span> 引用形参不能用 <span style="color: #2e8b57;"><strong>const</strong></span> 左值或右值初始化，而定义为 <span style="color: #2e8b57;"><strong>const</strong></span> 则无此问题<br />
⒋ 一般通过传递迭代器来传递 vector 等容器<br />
⒌ 数组形参<br />
⑴ 当把数组直接作为实参传给函数时：<br />
① 若形参不是数组的引用，则自动转换为指向首元素的指针<br />
例如以下三种定义完全等价：<br />
<span style="color: #2e8b57;"><strong>void</strong></span> f(<span style="color: #2e8b57;"><strong>int</strong></span>*); <span style="color: #0000ff;">//推荐，明确指出操作对象为指针</span><br />
<span style="color: #2e8b57;"><strong>void</strong></span> f(<span style="color: #2e8b57;"><strong>int</strong></span>[]);<br />
<span style="color: #2e8b57;"><strong>void</strong></span> f(<span style="color: #2e8b57;"><strong>int</strong></span>[<span style="color: #ff00ff;">10</span>]);<span style="color: #0000ff;">//维数被编译器忽略，写法容易引起误解</span><br />
② 若形参是数组的引用则不发生转换，函数得到的是数组本身<br />
此时编译器会检查形参和实参数组的维数是否匹配<br />
⑵ 把数组传递给函数处理时，可以：<br />
⒈ 使用标准库规范，即传入首元素指针和超出末端指针<br />
⒉ 传递数组首元素地址，并显式传递表示数组大小的形参<br />
⑶ 可以通过传递指向数组的指针来传递多维数组<br />
⒍ 含有可变形参的函数<br />
为兼容C语言而保留的特性，只能传入简单数据类型，大多数类类型对象都不能正确复制<br />
⒎ 默认实参<br />
⑴ 通过给形参表中的形参提供明确的初值可指定默认实参，如<br />
<span style="color: #2e8b57;"><strong>void</strong></span> f( <span style="color: #2e8b57;"><strong>int</strong></span>, <span style="color: #2e8b57;"><strong>int</strong></span> = <span style="color: #ff00ff;">1</span>, <span style="color: #2e8b57;"><strong>int</strong></span> = <span style="color: #ff00ff;">2</span> );<br />
① 默认实参可以是任何适当类型的表达式<br />
② 如果一个形参有默认实参，则其后的所有形参都必须有默认实参<br />
③ 在一个文件中，只能为一个形参指定默认实参一次<br />
通常应在函数声明中指定默认实参，并将该声明放在合适的头文件中<br />
⑵ 调用函数时可省略有默认值的实参：若省略则使用默认实参，否则使用用户提供的实参<br />
· 默认实参只能用来替换函数调用缺少的靠后的实参<br />
因此设计带有默认实参的函数时，应将最少使用默认实参的形参排在最前，最多使用默认实参的形参排在最后</p>
<p>㈢ 局部对象的作用域和生命期<br />
⒈ 函数体是一个作用域，在其中定义的变量称为局部变量(local variable)，只可在该函数中访问<br />
局部变量和形参均不能重名<br />
⒉ 自动对象(automatic objects)是局部于函数的对象，会在每次函数调用时重新创建，并在函数结束时撤销<br />
非静态局部变量和形参都是自动对象<br />
⒊ <span style="color: #2e8b57;"><strong>static</strong></span> 局部对象确保不迟于在程序执行流程第一次经过该对象的定义语句时初始化，一旦创建在程序结束前都不会撤销<br />
在所在函数被多次调用的过程中，static 局部对象会持续存在并保持它的值</p>
<p>㈣ 函数的返回值和 <span style="color: #804040;"><strong>return</strong></span> 语句<br />
⒈ 函数返回类型可为内置类型、类类型或复合类型，但不能是另一个函数或内置数组类型<br />
为 <span style="color: #2e8b57;"><strong>void</strong></span> 类型表示该函数不返回任何值<br />
⒉ <span style="color: #804040;"><strong>return</strong></span> 语句<br />
用于结束当前函数，将控制权返回给该函数的主调函数<br />
⑴ 没有返回值的 <span style="color: #804040;"><strong>return</strong></span> 语句<br />
① 只能用于返回类型为 <span style="color: #2e8b57;"><strong>void</strong></span> 的函数<br />
② 非必需，隐式 <span style="color: #804040;"><strong>return</strong></span> 发生在函数最后一个语句完成时<br />
⑵ 具有返回值的 <span style="color: #804040;"><strong>return</strong></span> 语句<br />
① 返回类型不是 <span style="color: #2e8b57;"><strong>void</strong></span> 的函数必须返回一个值(主函数 main 除外)<br />
注：应在函数中的每条执行路径末尾都提供 <span style="color: #804040;"><strong>return</strong></span> 语句；若不提供编译器可能也无法发现<br />
② 用函数返回值初始化在调用函数处创建的临时对象，与用实参初始化形参的方法一样<br />
即若返回非引用类型则得到副本，若返回引用类型则得到对象本身<br />
③ 切勿返回局部对象的引用或指针</p>
<p>㈤ 函数的重载<br />
⒈ 出现在相同作用域中的两个函数，如果具有相同的名字而形参表不同，则为重载函数(overloaded function)<br />
仅仅是返回类型不同、默认实参不同、非引用形参的 <span style="color: #2e8b57;"><strong>const</strong></span> 性质不同不能实现重载<br />
⒉ 若局部地声明函数，则该函数屏蔽而非重载在外层作用域中声明的同名函数<br />
⒊ 重载确定的三个步骤<br />
⑴ 确定候选函数集合<br />
候选函数(candidate function)是与被调函数同名的函数，且在调用点上声明可见<br />
⑵ 从候选函数集合中选择可行函数(找不到则此调用错误)<br />
可行函数(viable function)满足两个条件：<br />
① 函数形参与该调用的实参个数匹配(考虑默认实参)<br />
② 每个实参的类型须与对应形参匹配(类型相同或可隐式转换)<br />
⑶ 寻找最佳匹配(有多个匹配程度相同的则调用有二义性)<br />
原则是实参类型与形参类型越接近则匹配越佳。具体匹配程度从高到低为：<br />
① 精确匹配：实参与形参类型相同<br />
② 通过类型提升实现的匹配<br />
③ 通过标准转换实现的匹配<br />
④ 通过类类型转换实现的匹配</p>
<p>㈥ 函数指针<br />
⒈ 定义形式<br />
返回类型 (*标识符)(形参表)<br />
⒉ 可使用 <span style="color: #2e8b57;"><strong>typedef</strong></span> 简化定义<br />
<span style="color: #2e8b57;"><strong>typedef</strong></span> 返回类型 (*自定义类型名)(形参表)<br />
⒊ 对函数指针进行初始化和赋值，可使用：<br />
⑴ 同类型的函数<br />
引用函数名但不调用该函数时，函数名被自动解释为指向函数的指针<br />
因此可以直接使用函数名对函数指针初始化或赋值，不需要使用取地址操作符&amp;<br />
·注：指针类型须与函数完全匹配<br />
⑵ 同类型的函数指针<br />
⑶ <span style="color: #ff00ff;">0</span>值常量表达式<br />
⒋ 通过函数指针调用函数<br />
可以不使用解引用操作符，直接使用函数调用操作符()调用所指函数<br />
⒌ 函数指针作形参时，星号*可写可不写<br />
⒍ 函数的指针作函数返回值<br />
函数指针返回类型 (*函数名(函数形参表))(函数指针形参表)</p>
<p>㈦ 主函数 main<br />
⒈ 返回值<br />
⑴ 主函数返回类型为 <span style="color: #2e8b57;"><strong>int</strong></span>, 返回值在大多数系统中是一个状态指示器<br />
返回<span style="color: #ff00ff;">0</span>表示程序运行成功，其它大部分值表示失败<br />
⑵ 不需要显式使用 <span style="color: #804040;"><strong>return</strong></span> 语句，编译器将隐式插入返回<span style="color: #ff00ff;">0</span>的语句<br />
⑶ 为使返回值独立于机器，cstdlib 头文件定义了两个预处理变量<br />
<span style="color: #ff00ff;">EXIT_FAILURE</span>(运行失败)    <span style="color: #ff00ff;">EXIT_SUCCESS</span>(运行成功)<br />
⒉ 使用主函数形参处理命令行选项<br />
<span style="color: #2e8b57;"><strong>int</strong></span> main(<span style="color: #2e8b57;"><strong>int</strong></span> argc, <span style="color: #2e8b57;"><strong>char</strong></span>* argv[])<br />
· argv为一个C风格字符串数组，储存了从命令行调用程序时输入的字符串(包括程序名和参数)<br />
· argc表示argv中字符串的个数<br />
⒊ 主函数 main 不允许被显式调用、取地址或重载</p>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter4/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第四章'>C++ Primer 读书笔记 &#8211; 第四章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter2/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第二章'>C++ Primer 读书笔记 &#8211; 第二章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter5/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第五章'>C++ Primer 读书笔记 &#8211; 第五章</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter7/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>C++ Primer 读书笔记 &#8211; 第六章</title>
		<link>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter6/</link>
		<comments>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter6/#comments</comments>
		<pubDate>Sun, 24 Aug 2008 09:01:47 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[笔记]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2008/08/c-primer-%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-%e7%ac%ac%e5%85%ad%e7%ab%a0/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter6/" title="C++ Primer 读书笔记 - 第六章"></a>[笔记索引] 第６章 语句 ㈠ 简单语句 ⒈ 表达式语句(expression_r statement) 一个表达式加上结尾的分号，执行时导致该表达式被求值 ⒉ 空语句(null statement) 只由一个单独的分号组成，当语法上需要一个语句但逻辑上并不需要时使用 ⒊ 声明语句 用于声明或定义对象或类 ㈡ 复合语句 ⒈ 复合语句(compound statement)又被称为块(block)，是用一对花括号{}括起的(可能为空的)语句序列 ⒉ 通常用于语法规则要求使用单个语句但程序逻辑需要多个语句时 ⒊ 块标示了一个作用域，在块中引入的名字只能在其内部访问 ㈢ 控制流语句 注：作为语句控制结构的一部分定义的变量，仅在该语句内可见 ⒈ 条件分支结构 ⑴ if 语句 关于 else-if 匹配的二义性问题: else 匹配给最后出现尚未匹配的 if ⑵ &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter6/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter5/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第五章'>C++ Primer 读书笔记 &#8211; 第五章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter4/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第四章'>C++ Primer 读书笔记 &#8211; 第四章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter3/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第三章'>C++ Primer 读书笔记 &#8211; 第三章</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter6/" title="C++ Primer 读书笔记 - 第六章"></a><p style="text-align: right;"><a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/" target="_blank"><span style="font-size: 12px;"> [笔记索引]</span></a></p>
<p><span style="font-size: 12px;">第６章 语句<br />
㈠ 简单语句<br />
⒈ 表达式语句(expression_r statement)<br />
一个表达式加上结尾的分号，执行时导致该表达式被求值<br />
⒉ 空语句(null statement)<br />
只由一个单独的分号组成，当语法上需要一个语句但逻辑上并不需要时使用<br />
⒊ 声明语句<br />
用于声明或定义对象或类<br />
㈡ 复合语句<br />
⒈ 复合语句(compound statement)又被称为块(block)，是用一对花括号{}括起的(可能为空的)语句序列<br />
⒉ 通常用于语法规则要求使用单个语句但程序逻辑需要多个语句时<br />
⒊ 块标示了一个作用域，在块中引入的名字只能在其内部访问<br />
㈢ 控制流语句<br />
注：作为语句控制结构的一部分定义的变量，仅在该语句内可见<br />
⒈ 条件分支结构<br />
⑴ </span><span style="color: #804040;"><strong>if</strong></span> 语句<br />
关于 <span style="color: #804040;"><strong>else</strong></span>-<span style="color: #804040;"><strong>if</strong></span> 匹配的二义性问题: <span style="color: #804040;"><strong>else</strong></span> 匹配给最后出现尚未匹配的 <span style="color: #804040;"><strong>if</strong></span><br />
⑵ <span style="color: #804040;"><strong>switch</strong></span> 语句<br />
① <span style="color: #804040;"><strong>switch</strong></span> 在计算表达式的值后跳转到匹配的标号处(无匹配则跳转至 <span style="color: #804040;"><strong>default</strong></span>)，并从该点开始一直执行下去，<br />
直至 <span style="color: #804040;"><strong>switch</strong></span> 语句结束或遇到 <span style="color: #804040;"><strong>break</strong></span> 语句<br />
② <span style="color: #804040;"><strong>switch</strong></span> 求解表达式的结果须为整型，每个 <span style="color: #804040;"><strong>case</strong></span> 标号的值也须为各不相同的整型常量表达式<br />
③ <span style="color: #804040;"><strong>switch</strong></span> 内部的变量定义<br />
· 可以在 <span style="color: #804040;"><strong>switch</strong></span> 求解的表达式中定义和初始化变量<br />
· 为防止跳过变量定义，只允许在最后一个标号后定义变量<br />
· 也可以引入块语句，在其中定义变量<br />
⒉ 循环<br />
⑴ <span style="color: #804040;"><strong>while</strong></span> 语句<br />
注：循环条件中定义的变量在每次循环时都要经历创建和撤销的过程<br />
⑵ <span style="color: #804040;"><strong>for</strong></span> 循环语句<br />
注：语句头中的初始化语句、循环条件和表达式三者都可以省略<br />
循环条件省略表示永远为 <span style="color: #ff00ff;">true</span><br />
⑶ <span style="color: #804040;"><strong>do</strong></span> <span style="color: #804040;"><strong>while</strong></span> 语句<br />
注：不能在循环条件中定义变量<br />
⒊ <span style="color: #804040;"><strong>break</strong></span> 语句<br />
用于结束最近的外围 <span style="color: #804040;"><strong>while</strong></span>, <span style="color: #804040;"><strong>do</strong></span> <span style="color: #804040;"><strong>while</strong></span>, <span style="color: #804040;"><strong>for</strong></span> 或 <span style="color: #804040;"><strong>switch</strong></span> 语句，并在该语句后继续执行<br />
⒋ <span style="color: #804040;"><strong>continue</strong></span> 语句<br />
导致最近的外围循环语句(<span style="color: #804040;"><strong>for</strong></span>, <span style="color: #804040;"><strong>while</strong></span>, <span style="color: #804040;"><strong>do</strong></span> <span style="color: #804040;"><strong>while</strong></span>)正在进行的这次迭代提前结束<br />
⒌ <span style="color: #804040;"><strong>goto</strong></span> 语句<br />
⑴ <span style="color: #804040;"><strong>goto</strong></span> 语句提供了函数内部的无条件跳转，实现从 <span style="color: #804040;"><strong>goto</strong></span> 语句跳转到同一函数内某个带标号的语句<br />
除非有足够理由，应避免使用 <span style="color: #804040;"><strong>goto</strong></span> 语句<br />
⑵ 在任何语句前提供一个标识符和冒号，就得到一个带标号的语句(labeled statement)<br />
标识符: 语句<br />
使用 <span style="color: #804040;"><strong>goto</strong></span> 语句跳转到该语句： <span style="color: #804040;"><strong>goto</strong></span> 标识符;<br />
由于这里的标识符只能用作 <span style="color: #804040;"><strong>goto</strong></span> 的目标，因此可以与其它类型的标识符(如变量名)同名<br />
⑶ <span style="color: #804040;"><strong>goto</strong></span> 语句不能跨越变量的定义语句向前跳转<br />
若确实需在 <span style="color: #804040;"><strong>goto</strong></span> 和跳转目标位置间定义变量，则须定义在块中<br />
⒍ <span style="color: #804040;"><strong>try</strong></span>, <span style="color: #804040;"><strong>catch</strong></span> 语句和 <span style="color: #804040;"><strong>throw</strong></span> 表达式<br />
用于异常处理<br />
⒎ <span style="color: #804040;"><strong>return</strong></span> 语句<br />
用于结束当前函数，返回函数被调用处继续执行</p>
<p>⒍⒕ 使用预处理器进行调试<br />
⒈ 使用 NDEBUG 预处理变量实现有条件的调试代码(类似头文件保护符)<br />
<span style="color: #a020f0;"> #ifndef NDEBUG</span><br />
<span style="color: #a020f0;"> #define NDEBUG</span><br />
<span style="color: #0000ff;">// 调试代码</span><br />
<span style="color: #a020f0;"> #endif</span><br />
如果定义了 NDEBUG 就不执行调试代码<br />
⒉ 使用 NDEBUG 预处理变量以及 assert 预处理宏<br />
定义在头文件cassert中，常用来检查不可能发生的状况，形式为<br />
assert(表达式)<br />
如果表达式结果为 <span style="color: #ff00ff;">false</span>, assert 输出信息并终止程序<br />
如果定义了 NDEBUG 预处理变量，assert 将被忽略，不会产生任何运行时代价<br />
⒊ 预处理器定义了四种在调试时有用的常量<br />
<span style="color: #ff00ff;">__FILE__</span> 文件名<br />
<span style="color: #ff00ff;">__LINE__</span> 当前行号<br />
<span style="color: #ff00ff;">__TIME__</span> 编译时间<br />
<span style="color: #ff00ff;">__DATE__</span> 编译日期</p>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter5/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第五章'>C++ Primer 读书笔记 &#8211; 第五章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter4/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第四章'>C++ Primer 读书笔记 &#8211; 第四章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter3/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第三章'>C++ Primer 读书笔记 &#8211; 第三章</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter6/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>C++ Primer 读书笔记 &#8211; 第五章</title>
		<link>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter5/</link>
		<comments>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter5/#comments</comments>
		<pubDate>Fri, 22 Aug 2008 17:31:47 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[笔记]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2008/08/c-primer-%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-%e7%ac%ac%e4%ba%94%e7%ab%a0/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter5/" title="C++ Primer 读书笔记 - 第五章"></a>[笔记索引] 第５章 表达式 ⒈ 表达式(expression_r)是一个C++程序中最低级的计算，由一或多个用一个操作符(operator)连接起来的操作数(operands)组成 ⒉ 每个表达式都产生一个结果。表达式可以用作操作数，因此可用多个操作符编写复合表达式 ⒊ 在求解表达式的过程中如果需要储存运算结果，编译器会自动创建没有名字的临时对象(temporary object)，这些对象会在外围最大的表达式结束后释放 ⒋ 表达式是否合法、合法表达式含义如何(执行什么操作、结果是什么类型)均取决于操作数的类型 ⒌⒈ 算术操作符 ⒈ 按优先级从高到低排列为： 一元正+, 一元负-; 乘法*, 除法/, 取模%; 加法+, 减法- ⒉ 关于除法/ ⑴ 两整数相除结果仍为整数，商的小数部分被截去 ⑵ 一正一负两整数相除，结果值向0一侧还是向-∞一侧取整依赖于机器 ⒊ 关于取模 ⑴ 操作数只能为整型 ⑵ 两操作数均为负时结果为负或0; 两操作数一正一负时，结果的符号随哪个操作数而定依赖于机器 ⒌⒉ 关系操作符和逻辑操作符 ⒈ 按优先级从高到低排列为： &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter5/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter2/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第二章'>C++ Primer 读书笔记 &#8211; 第二章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter4/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第四章'>C++ Primer 读书笔记 &#8211; 第四章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter3/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第三章'>C++ Primer 读书笔记 &#8211; 第三章</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter5/" title="C++ Primer 读书笔记 - 第五章"></a><p style="text-align: right;"><a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/" target="_blank"><span style="font-size: 12px;"> [笔记索引]</span></a></p>
<p><span style="font-size: 12px;">第５章 表达式<br />
⒈ 表达式(expression_r)是一个C++程序中最低级的计算，由一或多个用一个操作符(operator)连接起来的操作数(operands)组成<br />
⒉ 每个表达式都产生一个结果。表达式可以用作操作数，因此可用多个操作符编写复合表达式<br />
⒊ 在求解表达式的过程中如果需要储存运算结果，编译器会自动创建没有名字的临时对象(temporary object)，这些对象会在外围最大的表达式结束后释放<br />
⒋ 表达式是否合法、合法表达式含义如何(执行什么操作、结果是什么类型)均取决于操作数的类型</span></p>
<p><span style="font-size: 12px;"> ⒌⒈ 算术操作符<br />
⒈ 按优先级从高到低排列为：<br />
一元正+, 一元负-; 乘法*, 除法/, 取模%; 加法+, 减法-<br />
⒉ 关于除法/<br />
⑴ 两整数相除结果仍为整数，商的小数部分被截去<br />
⑵ 一正一负两整数相除，结果值向</span><span style="color: #ff00ff;">0</span>一侧还是向-∞一侧取整依赖于机器<br />
⒊ 关于取模<br />
⑴ 操作数只能为整型<br />
⑵ 两操作数均为负时结果为负或<span style="color: #ff00ff;">0</span>;<br />
两操作数一正一负时，结果的符号随哪个操作数而定依赖于机器</p>
<p>⒌⒉ 关系操作符和逻辑操作符<br />
⒈ 按优先级从高到低排列为：<br />
逻辑非!; 小于&lt;, 小于等于&lt;=, 大于&gt;, 大于等于&gt;=; 相等==, 不等!=; 逻辑与&amp;&amp;; 逻辑或||<br />
⒉ 关系操作符和逻辑操作符接受算术或指针型操作数，并返回 <span style="color: #2e8b57;"><strong>bool</strong></span> 型值<br />
逻辑操作符视其操作数为条件表达式<br />
⒊ 逻辑与&amp;&amp;和逻辑或||操作符支持短路求值(short-circuit evaluation)<br />
⒋ 不应串接使用关系操作符<br />
形如i&lt;j&lt;k的表达式得不到预期结果</p>
<p>⒌⒊ 位操作符<br />
⒈ 按优先级从高到低排列为：<br />
位取反~; 左移&lt;&lt;, 右移&gt;&gt;; 位与&amp;; 位异或^; 位或|<br />
⒉ 位操作符使用整型操作数，将其视为二进制位的集合<br />
由于负整数的符号位如何处理依赖于机器，因此应使用 <span style="color: #2e8b57;"><strong>unsigned</strong></span> 整型操作数</p>
<p>⒌⒋ 赋值操作符<br />
⒈ 赋值操作符的左操作数须为非 <span style="color: #2e8b57;"><strong>const</strong></span> 左值<br />
赋值表达式的结果即为其左操作数(左值)<br />
⒉ 赋值操作符从右向左结合，因此当各操作数都有相同的通用类型时，允许在一个表达式中进行多次赋值，如：<br />
i = j = k = <span style="color: #ff00ff;">0</span>;<br />
⒊ 复合赋值操作符<br />
对于任意二元算术操作符或二元位操作符 op<br />
a op= b;<br />
相当于<br />
a = a op b;<br />
二者显著的差别在于前者只计算了一次左操作数，后者则计算了两次</p>
<p>⒌⒌ 自增和自减操作符<br />
⒈ 自增++和自减&#8211;操作符为对象加<span style="color: #ff00ff;">1</span>或减<span style="color: #ff00ff;">1</span>提供了方便简短的实现方式，有前置和后置两种使用形式<br />
前置操作返回加(减)<span style="color: #ff00ff;">1</span>后的对象(左值)，后置操作返回操作数的原值(右值)<br />
⒉ 出于性能考虑，应只在必要时才使用后置操作符<br />
后置操作符需要先保存原值以便返回，而前置操作符只需加(减)<span style="color: #ff00ff;">1</span>后直接返回对象即可</p>
<p>⒌⒍ 箭头操作符<br />
箭头操作符用于获取指针指向类类型对象的成员。<br />
下面两个表达式等价：<br />
p-&gt;foo;<br />
(*p).foo;</p>
<p>⒌⒎ 条件操作符<br />
⒈ 条件操作符是C++中唯一的三元操作符，能将简短的 <span style="color: #804040;"><strong>if</strong></span>-<span style="color: #804040;"><strong>else</strong></span> 语句嵌入表达式<br />
格式为 cond ? expr1 : expr2<br />
首先计算 cond 值，若为 <span style="color: #ff00ff;">true</span> 则计算并返回 expr1, 为 <span style="color: #ff00ff;">false</span> 则计算并返回 expr2<br />
⒉ 避免深度嵌套以保证可读性</p>
<p>⒌⒏ <span style="color: #804040;"><strong>sizeof</strong></span> 操作符<br />
⒈ <span style="color: #804040;"><strong>sizeof</strong></span> 操作符用于返回一个对象或类型的大小(单位字节，类型 <span style="color: #2e8b57;"><strong>size_t</strong></span>)<br />
形式为 <span style="color: #804040;"><strong>sizeof</strong></span> expr<br />
或  <span style="color: #804040;"><strong>sizeof</strong></span>(type_name)<br />
⒉ <span style="color: #804040;"><strong>sizeof</strong></span> 表达式的结果是编译时常量<br />
对表达式使用 <span style="color: #804040;"><strong>sizeof</strong></span> 时该表达式的值并不会被计算<br />
⒊ 对数组作 <span style="color: #804040;"><strong>sizeof</strong></span> 将得到整个数组在内存中的长度</p>
<p>⒌⒐ 逗号操作符<br />
逗号表达式是一组由逗号分隔的表达式，从左向右计算并返回最右边表达式的值(若该表达式为左值则返回左值)</p>
<p>⒌⒒ <span style="color: #804040;"><strong>new</strong></span> 和 <span style="color: #804040;"><strong>delete</strong></span> 表达式<br />
⒈ <span style="color: #804040;"><strong>new</strong></span> 和 <span style="color: #804040;"><strong>delete</strong></span> 表达式分别用于动态创建和释放单个对象或一个数组<br />
⒉ <span style="color: #804040;"><strong>new</strong></span> 表达式<br />
⑴ <span style="color: #804040;"><strong>new</strong></span> 表达式动态创建单个对象或一个数组，并返回指向该对象或数组首元素的指针<br />
① 动态数组维数为<span style="color: #ff00ff;">0</span>值时也将返回有效的非零指针，但不能解引用<br />
② 可以创建动态的 <span style="color: #2e8b57;"><strong>const</strong></span> 对象或数组，它们无法修改但可以释放<br />
③ 如果无法获取需要的空间，系统将抛出 bad_alloc 异常<br />
⑵ 分配与对象(或数组元素)初始化<br />
① 默认内置类型不初始化，类类型调用默认构造函数(必须提供)<br />
单个对象: <span style="color: #804040;"><strong>new</strong></span> [<span style="color: #2e8b57;"><strong>const</strong></span>] 类型名<br />
一个数组: <span style="color: #804040;"><strong>new</strong></span> [<span style="color: #2e8b57;"><strong>const</strong></span>] 类型名[维数]<br />
② 添加空括号()可执行值初始化(value initialization)<br />
即内置类型对象置为<span style="color: #ff00ff;">0</span>，类类型对象调用默认构造函数(必须提供)<br />
单个对象: <span style="color: #804040;"><strong>new</strong></span> [<span style="color: #2e8b57;"><strong>const</strong></span>] 类型名()<br />
一个数组: <span style="color: #804040;"><strong>new</strong></span> [<span style="color: #2e8b57;"><strong>const</strong></span>] 类型名[维数]()<br />
注：对于后者，我使用的编译器中，VC初始化后会把元素置为<span style="color: #ff00ff;">0</span>，但mingw不会<br />
③ 执行直接初始化<br />
单个对象: <span style="color: #804040;"><strong>new</strong></span> [<span style="color: #2e8b57;"><strong>const</strong></span>] 类型名(初始化式)<br />
动态数组元素不支持类似初始化方式<br />
⒊ <span style="color: #804040;"><strong>delete</strong></span> 表达式<br />
⑴ 由 <span style="color: #804040;"><strong>new</strong></span> 动态分配的对象或数组需要显式地使用 <span style="color: #804040;"><strong>delete</strong></span> 表达式释放(否则会造成内存泄漏)<br />
⑵ <span style="color: #804040;"><strong>delete</strong></span> ptr 和 <span style="color: #804040;"><strong>delete</strong></span>[] ptr 分别释放单个对象和动态数组<br />
① 如果指针不指向由 <span style="color: #804040;"><strong>new</strong></span> 分配的对象，则对其 <span style="color: #804040;"><strong>delete</strong></span> 不合法<br />
例外：对零指针 <span style="color: #804040;"><strong>delete</strong></span> 是合法的<br />
② 读写已释放的对象或对同一内存空间多次 <span style="color: #804040;"><strong>delete</strong></span> 都可能导致错误<br />
因此 <span style="color: #804040;"><strong>delete</strong></span> 之后应立即重置该指针的值(一般重置为<span style="color: #ff00ff;">0</span>)<br />
③ 释放动态数组如果丢掉方括号[]，将可能导致编译器无法发现的错误</p>
<p>⒌⒑ 复合表达式的求值<br />
⒈ 含有两个或以上操作符的表达式称为复合表达式(compound expression_r)<br />
操作数和操作符的结合方式决定了符合表达式的值，而前者取决于操作符的优先级和结合性：<br />
操作数优先与优先级更高的操作符结合；操作符优先级相同时则由结合性决定结合方向<br />
⒉ 圆括号()凌驾于优先级之上<br />
若不确定结合方式，则使用圆括号()强制确定操作数的组合<br />
⒊ 结合方式确定并不意味着操作数的计算顺序并不确定<br />
若修改了操作数的值，则不要在同一语句的其它地方再使用它，除非操作数的计算次序不成问题<br />
⒋ <a href="http://tomtung.jimdo.com/download/141592104/48aefa26/7eef8a1fec7302d00f283d68ed6b141c80302a10/Operator+Precedence+Table.html" target="_blank"><span style="font-size: 12px;">运算符优先级与结合性表</span></a></p>
<p><span style="font-size: 12px;">⒌⒓ 类型转换<br />
⒈ 隐式类型转换<br />
两个类型间存在可转换关系，则称两个类型相关<br />
⑴ 何时发生<br />
当编译器期望获得某种类型的数据却得到另一种类型的，就会尝试自动转换类型<br />
具体情况包括：<br />
① 表达式中不同类型的多个操作数被转换到同一类型<br />
② 用作条件的表达式被转换为 </span><span style="color: #2e8b57;"><strong>bool</strong></span> 型<br />
③ 用一表达式对某变量初始化或赋值时，前者转换为后者的类型<br />
④ 给函数传递实参和返回值时可能发生隐式类型转换<br />
⑵ 内置类型转换规则<br />
① 算术类型间的转换<br />
ⅰ 浮点型转换为整型时小数部分被抛弃(在赋值、初始化等情况下发生)<br />
ⅱ 二元(算术、关系等)操作符表达式中，为试图保留精度会把较小的类型转换为较大的类型<br />
ａ类型提升<br />
· 将比 <span style="color: #2e8b57;"><strong>int</strong></span> 小的整型提升为 <span style="color: #2e8b57;"><strong>int</strong></span> 或(当 <span style="color: #2e8b57;"><strong>int</strong></span> 不足以容纳原类型的所有可能值时)<span style="color: #2e8b57;"><strong>unsigned</strong></span> <span style="color: #2e8b57;"><strong>int</strong></span><br />
· 将 <span style="color: #2e8b57;"><strong>float</strong></span> 提升为 <span style="color: #2e8b57;"><strong>double</strong></span><br />
ｂ <span style="color: #2e8b57;"><strong>signed</strong></span> 与 <span style="color: #2e8b57;"><strong>unsigned</strong></span> 之间的转换<br />
· 对于 <span style="color: #2e8b57;"><strong>signed</strong></span> 较小类型和 <span style="color: #2e8b57;"><strong>unsigned</strong></span> 较大或相同类型，前者转换为后者<br />
· 对于 <span style="color: #2e8b57;"><strong>signed</strong></span> 较大类型和 <span style="color: #2e8b57;"><strong>unsigned</strong></span> 较小类型，如何操作依赖于机器:<br />
前者若能表示后者所有可能值则后者转换为前者，否则都转换为前者的 <span style="color: #2e8b57;"><strong>unsigned</strong></span> 形式<br />
以上两种情况，负值有可能会被转换为 <span style="color: #2e8b57;"><strong>unsigned</strong></span> 导致溢出，造成意料之外的结果<br />
② 转换为指针<br />
ⅰ 表达式中的数组名会自动转换为指向数组首元素的指针<br />
但以下情况除外：<br />
ａ数组作为取地址操作符&amp;的操作数<br />
ｂ数组作为 <span style="color: #804040;"><strong>sizeof</strong></span> 操作符的操作数<br />
ｃ使用数组对数组的引用初始化时<br />
ⅱ 指向任意类型对象的指针都可转换为 <span style="color: #2e8b57;"><strong>void</strong></span>* 型<br />
ⅲ 整型常量<span style="color: #ff00ff;">0</span>可转换为任意指针类型<br />
③ <span style="color: #2e8b57;"><strong>bool</strong></span> 类型转换<br />
ⅰ 算术值和指针值转换为 <span style="color: #2e8b57;"><strong>bool</strong></span> 型<br />
<span style="color: #ff00ff;">0</span> → <span style="color: #ff00ff;">false</span> 非<span style="color: #ff00ff;">0</span> → <span style="color: #ff00ff;">true</span><br />
ⅱ <span style="color: #2e8b57;"><strong>bool</strong></span> 值转换为算术类型<br />
<span style="color: #ff00ff;">true</span> → <span style="color: #ff00ff;">1</span> <span style="color: #ff00ff;">false</span> → <span style="color: #ff00ff;">0</span><br />
④ 枚举成员转换为整型<br />
枚举类型对象或枚举成员可自动转换为整型(具体类型依赖于机器和枚举成员最大值，但至少为int)<br />
⑤ 转换为 <span style="color: #2e8b57;"><strong>const</strong></span> 对象<br />
非 <span style="color: #2e8b57;"><strong>const</strong></span> 对象在初始化相关的 <span style="color: #2e8b57;"><strong>const</strong></span> 型引用时自动转换为 <span style="color: #2e8b57;"><strong>const</strong></span><br />
非 <span style="color: #2e8b57;"><strong>const</strong></span> 对象的地址或指向非 <span style="color: #2e8b57;"><strong>const</strong></span> 对象的指针也可转换为指向 <span style="color: #2e8b57;"><strong>const</strong></span> 对象的指针<br />
⒉ 显式类型转换<br />
⑴ 若可能，避免使用强制类型转换<br />
确实需要使用时也应尽量小心<br />
⑵ 命名的强制类型转换<br />
① <span style="color: #804040;"><strong>dynamic_cast</strong></span><br />
用于运行时类型识别(RTTI)<br />
② <span style="color: #804040;"><strong>const_cast</strong></span><br />
可以添加或去除指针、数据成员指针或引用的 <span style="color: #2e8b57;"><strong>const</strong></span> 特性<br />
③ <span style="color: #804040;"><strong>static_cast</strong></span><br />
ⅰ 可以显式完成编译器隐式执行的任何类型转换<br />
因潜在精度损失产生的编译器警告会被关闭<br />
· 可以用以避免不必要的隐式转换，如<br />
已知 d 为 <span style="color: #2e8b57;"><strong>double</strong></span>, i 为 <span style="color: #2e8b57;"><strong>int</strong></span>, 将 i*=d; 写成 i*=<span style="color: #804040;"><strong>static_cast</strong></span>&lt;<span style="color: #2e8b57;"><strong>int</strong></span>&gt;(d); 可省去将 i 转换为 <span style="color: #2e8b57;"><strong>double</strong></span> 这一非必要步骤<br />
④ <span style="color: #804040;"><strong>reinterpret_cast</strong></span><br />
为操作数的位模式提供较低层次的重新解释，结果依赖编译器和机器<br />
任何不当使用都可能导致运行时错误<br />
⑶ 旧式强制转换<br />
在合法使用 <span style="color: #804040;"><strong>static_cast</strong></span> 或 <span style="color: #804040;"><strong>const_cast</strong></span> 的地方，提供与命名强制转换一样的功能<br />
若两种转换均不合法，就执行 <span style="color: #804040;"><strong>reinterpret_cast</strong></span><br />
由于不易判别每个显式转换的潜在风险，不推荐使用</p>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter2/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第二章'>C++ Primer 读书笔记 &#8211; 第二章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter4/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第四章'>C++ Primer 读书笔记 &#8211; 第四章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter3/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第三章'>C++ Primer 读书笔记 &#8211; 第三章</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter5/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>C++ Primer 读书笔记 &#8211; 第四章</title>
		<link>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter4/</link>
		<comments>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter4/#comments</comments>
		<pubDate>Tue, 19 Aug 2008 17:17:47 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[笔记]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2008/08/c-primer-%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-%e7%ac%ac%e5%9b%9b%e7%ab%a0/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter4/" title="C++ Primer 读书笔记 - 第四章"></a>[笔记索引] 第４章 数组和指针 现代C++程序应使用vector和迭代器代替数组和指针，除非前二者不满足对效率的特殊要求 ⒋⒈ 数组 ⒈ 数组是由类型说明符、标识符和维度组成的复合类型，能保存一组某种类型的未命名对象 ⒉ 定义和初始化 ⑴ 类型说明符规定了存放于数组中元素的类型 可使用除引用外的任意类型，包括数组本身(数组的数组即多维数组) ⑵ 维数指定数组中包含的元素个数，须用值大于等于1的常量表达式定义，一经指定不可改变 ⑶ 可以使用初值列表(用花括号括起的一组用逗号分隔的初值，可为空)显式提供元素的初值 ① 此时可不指定维数，数组长度将由初值列表中的元素个数自动确定 ② 若指定维数： ⅰ 维数值不能小于初值个数 ⅱ 若维数值大于初始化列表中提供的元素个数，则只用初值列表初始化数组中前面对应的元素 其余元素，若为内置类型则初始化为0，若为类类型则调用默认构造函数 ③ 对于多维数组，除第一维外其余维数都须显示指定 初值列表可内嵌花括号以指明各初值对应的位置，若不使用内嵌花括号则依次初始化 ⑷ 若未提供初值列表，则数组元素像普通变量一样初始化 ⑸ 使用字符串字面值初始化字符数组时注意前者结尾隐含的空字符 ⒊ 不允许数组直接复制和赋值 ⒋ 数组元素可通过下标操作符[]访问，下标从0开始 下标越界将导致运行时错误 ⒋⒉ 指针 &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter4/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter3/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第三章'>C++ Primer 读书笔记 &#8211; 第三章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter2/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第二章'>C++ Primer 读书笔记 &#8211; 第二章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 索引'>C++ Primer 读书笔记 &#8211; 索引</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter4/" title="C++ Primer 读书笔记 - 第四章"></a><p style="text-align: right;"><a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/" target="_blank"><span style="font-size: 12px;"> [笔记索引]</span></a></p>
<p><span style="font-size: 12px;">第４章 数组和指针<br />
现代C++程序应使用vector和迭代器代替数组和指针，除非前二者不满足对效率的特殊要求</span></p>
<p><span style="font-size: 12px;"> ⒋⒈ 数组<br />
⒈ 数组是由类型说明符、标识符和维度组成的复合类型，能保存一组某种类型的未命名对象<br />
⒉ 定义和初始化<br />
⑴ 类型说明符规定了存放于数组中元素的类型<br />
可使用除引用外的任意类型，包括数组本身(数组的数组即多维数组)<br />
⑵ 维数指定数组中包含的元素个数，须用值大于等于</span><span style="color: #ff00ff;">1</span>的常量表达式定义，一经指定不可改变<br />
⑶ 可以使用初值列表(用花括号括起的一组用逗号分隔的初值，可为空)显式提供元素的初值<br />
① 此时可不指定维数，数组长度将由初值列表中的元素个数自动确定<br />
② 若指定维数：<br />
ⅰ 维数值不能小于初值个数<br />
ⅱ 若维数值大于初始化列表中提供的元素个数，则只用初值列表初始化数组中前面对应的元素<br />
其余元素，若为内置类型则初始化为<span style="color: #ff00ff;">0</span>，若为类类型则调用默认构造函数<br />
③ 对于多维数组，除第一维外其余维数都须显示指定<br />
初值列表可内嵌花括号以指明各初值对应的位置，若不使用内嵌花括号则依次初始化<br />
⑷ 若未提供初值列表，则数组元素像普通变量一样初始化<br />
⑸ 使用字符串字面值初始化字符数组时注意前者结尾隐含的空字符<br />
⒊ 不允许数组直接复制和赋值<br />
⒋ 数组元素可通过下标操作符[]访问，下标从<span style="color: #ff00ff;">0</span>开始<br />
下标越界将导致运行时错误</p>
<p>⒋⒉ 指针<br />
⒈ 指针是一种能储存对象地址的对象<br />
⒉ 定义和初始化<br />
⑴ 形式： 类型名* 标识符;<br />
类型名指定指针指向对象的类型(类型名为 <span style="color: #2e8b57;"><strong>void</strong></span> 时指针可指向任意类型的对象)<br />
① 连续声明多个指针时，每个标识符前都要加*号<br />
② 可以连续使用多个星号*表示指向指针的指针<br />
③ <span style="color: #2e8b57;"><strong>const</strong></span> 限定符<br />
ⅰ 指向 <span style="color: #2e8b57;"><strong>const</strong></span> 对象的指针<br />
ａ形式： <span style="color: #2e8b57;"><strong>const</strong></span> 类型名* 标识符;<br />
（或 类型名 <span style="color: #2e8b57;"><strong>const</strong></span>* 标识符;）<br />
ｂ <span style="color: #2e8b57;"><strong>const</strong></span> 对象只能与这种指针关联<br />
ｃ不能通过这种指针修改所指对象，无论所指是否为 <span style="color: #2e8b57;"><strong>const</strong></span><br />
由于有此特性，该种指针常用作函数形参以防止所指对象被意外修改<br />
ⅱ <span style="color: #2e8b57;"><strong>const</strong></span> 指针<br />
ａ形式：类型名* <span style="color: #2e8b57;"><strong>const</strong></span> 标识符<br />
ｂ指针本身的值不能修改，即不能改变指向，但可以改变所指对象的值<br />
ⅲ 指向 <span style="color: #2e8b57;"><strong>const</strong></span> 对象的 <span style="color: #2e8b57;"><strong>const</strong></span> 指针<br />
ａ形式： <span style="color: #2e8b57;"><strong>const</strong></span> 类型名* <span style="color: #2e8b57;"><strong>const</strong></span> 标识符<br />
ｂ既不能改变所指对象的值，也不能改变指向<br />
ⅳ <span style="color: #2e8b57;"><strong>typedef</strong></span> T* pT; 则 <span style="color: #2e8b57;"><strong>const</strong></span> pT t 和 pT <span style="color: #2e8b57;"><strong>const</strong></span> t 均与 T* <span style="color: #2e8b57;"><strong>const</strong></span> t 等价<br />
④ 指向数组的指针<br />
ⅰ 形式： 类型名 (*标识符)[维数][维数]..[维数];<br />
ⅱ <span style="color: #2e8b57;"><strong>typedef</strong></span> 类型名新类型名[维数][维数]..[维数];<br />
得到数组类型的别名，可以此简化定义<br />
⑵ 应避免使用未初始化的指针<br />
而一个有效的指针必然为以下三种状态之一：<br />
① 保存某确定对象的地址<br />
② 指向一个对象的下一位置<br />
③ 值为<span style="color: #ff00ff;">0</span><br />
因此在对指针初始化和赋值时只能使用：<br />
① 值为<span style="color: #ff00ff;">0</span>的常量表达式<br />
② 类型匹配的对象的地址(使用取地址操作符&amp;获得,该运算符只能对左值使用)<br />
③ 另一对象下一位置的地址<br />
④ 同类型另一有效指针<br />
⒊ 指针操作<br />
⑴ 使用解引用操作符*可以获得指针指向对象的左值，从而操作所指对象<br />
而对指针直接进行赋值等操作将改变指针本身的值，使指针指向另一对象<br />
⑵ 指针可被当作数组的迭代器，用以访问数组元素<br />
① 指针的算术操作<br />
ⅰ 指针与整型值相加(减)，得到指向所指元素向后(前)移动相应位置的数组元素的新指针<br />
ⅱ 两个指向同一数组中元素的指针(含超出末端指针)相减，得到一个 <span style="color: #2e8b57;"><strong>ptrdiff_t</strong></span> 型(定义在头文件 cstddef 中， <span style="color: #2e8b57;"><strong>signed</strong></span> 整型)结果，表示两指针所指元素间的距离<br />
② 对指针进行下标操作[]，返回所指元素向后(前)移动相应位置的数组元素引用<br />
③ 可以计算超出末端指针，但不允许对其进行解引用操作<br />
计算越界指针也是非法的<br />
⑶ <span style="color: #2e8b57;"><strong>void</strong></span>* 指针不支持以上操作</p>
<p>⒋⒊ C风格字符串<br />
⒈ C风格字符串是以空字符结尾的字符数组<br />
字符串字面值就是其实例<br />
⒉ C风格字符串的标准库函数(头文件 cstring)<br />
传给以下库函数的须为指向以空字符结尾的字符数组的非零指针<br />
⑴ <span style="color: #2e8b57;"><strong>size_t</strong></span> strlen( <span style="color: #2e8b57;"><strong>char</strong></span> *str );<br />
返回字符串长度(不包括结尾的空字符)<br />
⑵ <span style="color: #2e8b57;"><strong>int</strong></span> strcmp( <span style="color: #2e8b57;"><strong>const</strong></span> <span style="color: #2e8b57;"><strong>char</strong></span> *str1, <span style="color: #2e8b57;"><strong>const</strong></span> <span style="color: #2e8b57;"><strong>char</strong></span> *str2 );<br />
比较两个字符串，如果前者大于后者返回正数，小于返回负数，等于返回<span style="color: #ff00ff;">0</span><br />
注：不能使用&gt;,&lt;,==操作符比较，它们只会比较存放地址而不会比较字符串<br />
⑶ <span style="color: #2e8b57;"><strong>char</strong></span> *strcat( <span style="color: #2e8b57;"><strong>char</strong></span> *str1, <span style="color: #2e8b57;"><strong>const</strong></span> <span style="color: #2e8b57;"><strong>char</strong></span> *str2 );<br />
将str2连接到str1后面，并返回str1<br />
此函数不进行越界检查<br />
⑷ <span style="color: #2e8b57;"><strong>char</strong></span> *strcpy( <span style="color: #2e8b57;"><strong>char</strong></span> *to, <span style="color: #2e8b57;"><strong>const</strong></span> <span style="color: #2e8b57;"><strong>char</strong></span> *from );<br />
将str2复制到str1字符串，并返回str1<br />
此函数不进行越界检查<br />
⑸ <span style="color: #2e8b57;"><strong>char</strong></span> *strncat( <span style="color: #2e8b57;"><strong>char</strong></span> *str1, <span style="color: #2e8b57;"><strong>const</strong></span> <span style="color: #2e8b57;"><strong>char</strong></span> *str2, <span style="color: #2e8b57;"><strong>size_t</strong></span> count );<br />
将str2的前至多count个字符连接到str1后面，并返回str1<br />
如果越界则截断字符串，因此比strcat安全<br />
⑹ <span style="color: #2e8b57;"><strong>char</strong></span> *strncpy( <span style="color: #2e8b57;"><strong>char</strong></span> *to, <span style="color: #2e8b57;"><strong>const</strong></span> <span style="color: #2e8b57;"><strong>char</strong></span> *from, <span style="color: #2e8b57;"><strong>size_t</strong></span> count );<br />
将str2的前至多count个字符复制到str1，并返回str1<br />
如果越界则截断字符串，因此比strcpy安全<br />
⒊ 对于大部分程序而言，标准库类型 string 无论安全性还是效率均强过C风格字符串，因此应尽可能使用 string</p>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter3/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第三章'>C++ Primer 读书笔记 &#8211; 第三章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter2/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第二章'>C++ Primer 读书笔记 &#8211; 第二章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 索引'>C++ Primer 读书笔记 &#8211; 索引</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter4/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>C++ Primer 读书笔记 &#8211; 第三章</title>
		<link>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter3/</link>
		<comments>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter3/#comments</comments>
		<pubDate>Tue, 12 Aug 2008 15:05:31 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[笔记]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2008/08/c-primer-%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-%e7%ac%ac%e4%b8%89%e7%ab%a0/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter3/" title="C++ Primer 读书笔记 - 第三章"></a>[笔记索引] 第３章 标准库类型 ⒊⒌ 标准库bitset类型 [以下下标和位数均为 size_t 型(定义在头文件 cstddef 中， unsigned 整型)] ⒈ bitset 是一种类模板，用于保存位集，并提供测位和置位操作 ⒉ 定义和初始化 bitset 在定义时需要以常量表达式的形式提供位数N 初始化时参数提供位数不足则剩余高阶位置为0，位数过多则抛弃多余高阶位 ⑴ bitset&#60;N&#62; b; 默认构造函数置各位为0 ⑵ bitset&#60;N&#62; b( unsigned long u ); 使用 unsigned long 值u的二进制形式初始化 ⑶ bitset&#60;N&#62; b( string s, &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter3/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter2/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第二章'>C++ Primer 读书笔记 &#8211; 第二章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 索引'>C++ Primer 读书笔记 &#8211; 索引</a></li>
<li><a href='http://blog.tomtung.com/2007/06/c-cpp-io-odd-behavior/' rel='bookmark' title='诡异的事：关于C/C++输入输出'>诡异的事：关于C/C++输入输出</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter3/" title="C++ Primer 读书笔记 - 第三章"></a><p style="text-align: right;"><a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/" target="_blank"><span style="font-size: 12px;"> [笔记索引]</span></a></p>
<p><span style="font-size: 12px;">第３章 标准库类型</span></p>
<p><span style="font-size: 12px;"> ⒊⒌ 标准库bitset类型<br />
<strong>[以下下标和位数均为 <span style="color: #2e8b57;">size_t</span> 型(定义在头文件 cstddef 中， <span style="color: #339900;">unsigned</span> 整型)]</strong><br />
⒈ bitset 是一种类模板，用于保存位集，并提供测位和置位操作<br />
⒉ 定义和初始化<br />
bitset 在定义时需要以常量表达式的形式提供位数N<br />
初始化时参数提供位数不足则剩余高阶位置为</span><span style="color: #ff00ff;">0</span>，位数过多则抛弃多余高阶位<br />
⑴ bitset&lt;N&gt; b;<br />
默认构造函数置各位为<span style="color: #ff00ff;">0</span><br />
⑵ bitset&lt;N&gt; b( <span style="color: #2e8b57;"><strong>unsigned</strong></span> <span style="color: #2e8b57;"><strong>long</strong></span> u );<br />
使用 <span style="color: #2e8b57;"><strong>unsigned</strong></span> <span style="color: #2e8b57;"><strong>long</strong></span> 值u的二进制形式初始化<br />
⑶ bitset&lt;N&gt; b( string s, <span style="color: #2e8b57;"><strong>size_t</strong></span> pos=<span style="color: #ff00ff;">0</span> );<br />
bitset&lt;N&gt; b( string s, <span style="color: #2e8b57;"><strong>size_t</strong></span> pos, <span style="color: #2e8b57;"><strong>size_t</strong></span> m );<br />
使用 string 对象s或其(下标pos起到结尾或长度为m的)子串初始化<br />
(子)串最右端对应低阶(low-order)位，向左依次类推<br />
⒊ 操作<br />
除支持<strong>所有内置位运算符</strong>以及==和!=外，还支持以下操作:<br />
⑴ 访问整个 bitset 对象<br />
① <span style="color: #2e8b57;"><strong>bool</strong></span> any();<br />
返回是否各位不全为<span style="color: #ff00ff;">0</span><br />
② <span style="color: #2e8b57;"><strong>bool</strong></span> none();<br />
返回是否各位全为<span style="color: #ff00ff;">0</span><br />
③ <span style="color: #2e8b57;"><strong>size_t</strong></span> count();<br />
返回为<span style="color: #ff00ff;">1</span>的位的个数<br />
④ <span style="color: #2e8b57;"><strong>size_t</strong></span> size();<br />
返回能容纳的位数<br />
⑵ 访问 bitset 对象中的位<br />
(从低阶位起，各位编号依次为 <span style="color: #ff00ff;">0</span>, <span style="color: #ff00ff;">1</span>, <span style="color: #ff00ff;">2</span>, <span style="color: #ff00ff;">3</span> &#8230;)<br />
① 下标操作符[]返回指定位的引用<br />
② <span style="color: #2e8b57;"><strong>bool</strong></span> test( <span style="color: #2e8b57;"><strong>size_t</strong></span> pos );<br />
返回指定位的值<br />
③ bitset&lt;N&gt;&amp; set( <span style="color: #2e8b57;"><strong>size_t</strong></span> pos, <span style="color: #2e8b57;"><strong>int</strong></span> val=<span style="color: #ff00ff;">1</span> );<br />
设定指定位的值并返回对象的引用<br />
④ bitset&lt;N&gt;&amp; reset( <span style="color: #2e8b57;"><strong>size_t</strong></span> pos );<br />
清零指定位并返回对象的引用<br />
⑤ bitset&lt;N&gt;&amp; flip( <span style="color: #2e8b57;"><strong>size_t</strong></span> pos );<br />
取反特定位并返回对象的引用<br />
也可以b[pos].flip()，返回指定位的引用<br />
⑶ 设置整个 bitset 对象<br />
① bitset&lt;N&gt;&amp; set();<br />
置所有位为<span style="color: #ff00ff;">1</span>并返回对象的引用<br />
② bitset&lt;N&gt;&amp; reset();<br />
清零所有位并返回对象的引用<br />
③ bitset&lt;N&gt;&amp; flip();<br />
取反所有位并返回对象的引用<br />
⑷ 获取 bitset 对象的值<br />
① <span style="color: #2e8b57;"><strong>unsigned</strong></span> <span style="color: #2e8b57;"><strong>long</strong></span> to_ulong();<br />
返回位模式相同的 <span style="color: #2e8b57;"><strong>unsigned</strong></span> <span style="color: #2e8b57;"><strong>long</strong></span> 值<br />
若越界则产生运行时异常<br />
② string to_string();<br />
返回字符串形式<br />
③ 可使用输出操作符打印 bitset 对象</p>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter2/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 第二章'>C++ Primer 读书笔记 &#8211; 第二章</a></li>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 索引'>C++ Primer 读书笔记 &#8211; 索引</a></li>
<li><a href='http://blog.tomtung.com/2007/06/c-cpp-io-odd-behavior/' rel='bookmark' title='诡异的事：关于C/C++输入输出'>诡异的事：关于C/C++输入输出</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter3/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>C++ Primer 读书笔记 &#8211; 第二章</title>
		<link>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter2/</link>
		<comments>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter2/#comments</comments>
		<pubDate>Thu, 07 Aug 2008 15:06:22 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[笔记]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2008/08/c-primer-%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-%e7%ac%ac%e4%ba%8c%e7%ab%a0/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter2/" title="C++ Primer 读书笔记 - 第二章"></a>[笔记索引] 第２章 变量和基本类型 ⒉⒈ 基本内置类型 ⒈ 算数类型(arithmetic types)：表示数值(即整数和浮点数)的类型 ⒉⒈⒈ 整型 ⒈ 整型(Integral Types)：表示整数、字符和布尔值的算数类型。包括： ⑴ char: 字符型，通常单个机器字节，最小8位 wchar_t: 宽字符型，最小16位 用于扩展字符集，如汉字和日语 ⑵ short: 短整型，通常半个字长，最小16位 由于范围较小容易越界，执行整型算数运算时很少使用 int: 整型，通常1个字长，最小16位，大多数机器使用32位表示 用于整型运算时不易出错 long: 长整型，通常1或2个机器字长，最小32位 对用32位表示 int 而用64位表示 long 的机器，后者代价远高于前者，使用时如何选择应视情况而定 ⑶ bool: 布尔型，赋值时0值算数类型为 false, 其余为 true ⒉ &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter2/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 索引'>C++ Primer 读书笔记 &#8211; 索引</a></li>
<li><a href='http://blog.tomtung.com/2007/06/c-cpp-io-odd-behavior/' rel='bookmark' title='诡异的事：关于C/C++输入输出'>诡异的事：关于C/C++输入输出</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter2/" title="C++ Primer 读书笔记 - 第二章"></a><p style="text-align: right;"><a href="http://blog.sina.com.cn/s/blog_4a443fd70100aq4s.html" target="_blank"><span style="font-size: 12px;"> </span></a><span style="font-size: 12px;">[<a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/">笔记索引</a>]</span></p>
<p><span style="font-size: 12px;">第２章 变量和基本类型</span></p>
<p><span style="font-size: 12px;"> ⒉⒈ 基本内置类型<br />
⒈ 算数类型(arithmetic types)：表示数值(即整数和浮点数)的类型</span></p>
<p><span style="font-size: 12px;"> ⒉⒈⒈ 整型<br />
⒈ 整型(Integral Types)：表示整数、字符和布尔值的算数类型。包括：<br />
⑴ </span><span style="color: #2e8b57;"><strong>char</strong></span>: 字符型，通常单个机器字节，最小<span style="color: #ff00ff;">8</span>位<br />
<span style="color: #804040;"><strong>wchar_t</strong></span>:<br />
宽字符型，最小<span style="color: #ff00ff;">16</span>位<br />
用于扩展字符集，如汉字和日语<br />
⑵ <span style="color: #2e8b57;"><strong>short</strong></span>:<br />
短整型，通常半个字长，最小<span style="color: #ff00ff;">16</span>位<br />
由于范围较小容易越界，执行整型算数运算时很少使用<br />
<span style="color: #804040;"><strong>int</strong></span>:<br />
整型，通常<span style="color: #ff00ff;">1</span>个字长，最小<span style="color: #ff00ff;">16</span>位，大多数机器使用<span style="color: #ff00ff;">32</span>位表示<br />
用于整型运算时不易出错<br />
<span style="color: #804040;"><strong>long</strong></span>:<br />
长整型，通常<span style="color: #ff00ff;">1</span>或<span style="color: #ff00ff;">2</span>个机器字长，最小<span style="color: #ff00ff;">32</span>位<br />
对用<span style="color: #ff00ff;">32</span>位表示 <span style="color: #2e8b57;"><strong>int</strong></span> 而用<span style="color: #ff00ff;">64</span>位表示 <span style="color: #2e8b57;"><strong>long</strong></span> 的机器，后者代价远高于前者，使用时如何选择应视情况而定<br />
⑶ <span style="color: #2e8b57;"><strong>bool</strong></span>:<br />
布尔型，赋值时<span style="color: #ff00ff;">0</span>值算数类型为 <span style="color: #ff00ff;">false</span>, 其余为 <span style="color: #ff00ff;">true</span><br />
⒉ 带符号(<span style="color: #2e8b57;"><strong>signed</strong></span>)和无符号(<span style="color: #2e8b57;"><strong>unsigned</strong></span>)类型<br />
⑴ 带符号类型可以表示正数、<span style="color: #ff00ff;">0</span>和负数; 无符号只能表示非负数<br />
⑵ <span style="color: #2e8b57;"><strong>int</strong></span>,<span style="color: #2e8b57;"><strong>short</strong></span>,<span style="color: #2e8b57;"><strong>long</strong></span> 都默认为 <span style="color: #2e8b57;"><strong>signed</strong></span><br />
⑶ <span style="color: #2e8b57;"><strong>char</strong></span> 有<span style="color: #ff00ff;">3</span>种不同类型: 普通, <span style="color: #2e8b57;"><strong>unsigned</strong></span>, <span style="color: #2e8b57;"><strong>signed</strong></span>, 但只有<span style="color: #ff00ff;">2</span>种表示方式<br />
<span style="color: #2e8b57;"><strong>char</strong></span> 使用哪一种表示方式由编译器决定<br />
⒊ 整型值的表示<br />
⑴ <span style="color: #2e8b57;"><strong>unsigned</strong></span> 型中，所有位都用来表示数值<br />
⑵ <span style="color: #2e8b57;"><strong>signed</strong></span> 型如何用位由编译器决定。常见策略为使用其中一个位作符号位，该位为<span style="color: #ff00ff;">1</span>则值为负，为<span style="color: #ff00ff;">0</span>则值为<span style="color: #ff00ff;">0</span>或正<br />
⒋ 将一个越界值赋给一个指定类型的变量时：<br />
⑴ 若类型为 <span style="color: #2e8b57;"><strong>unsigned</strong></span>, 编译器通过对越界值取模来满足要求<br />
⑵ 若类型为 <span style="color: #2e8b57;"><strong>signed</strong></span>, 行为未定义</p>
<p>⒉⒈⒉ 浮点型<br />
⑴ <span style="color: #2e8b57;"><strong>float</strong></span>:<br />
单精度浮点数，一般一个字长，最小<span style="color: #ff00ff;">6</span>位有效数字<br />
其可能的精度损失不可忽视<br />
⑵ <span style="color: #2e8b57;"><strong>double</strong></span>:<br />
双精度浮点数，一般两个字长，最小<span style="color: #ff00ff;">10</span>位有效数字<br />
其计算代最小<span style="color: #ff00ff;">8</span>位价与 <span style="color: #2e8b57;"><strong>float</strong></span> 相当，使用它基本不会错<br />
⑶ <span style="color: #2e8b57;"><strong>long</strong></span> <span style="color: #2e8b57;"><strong>double</strong></span>:<br />
扩展精度浮点数，一般三或四个字长，最小<span style="color: #ff00ff;">10</span>位有效数字<br />
其提供的额外精度往往无必要，且需承担额外的运行代价</p>
<p>⒉⒉ 字面值常量<br />
⒈ 整数字面值<br />
⑴ 定义字面值整型常量可以使用十进制、八进制(以<span style="color: #ff00ff;">0</span>开头表示)和十六进制(以0x或0X开头表示)中的任意一种<br />
⑵ 默认为 <span style="color: #2e8b57;"><strong>int</strong></span> 或 <span style="color: #2e8b57;"><strong>long</strong></span> 型(值适合 <span style="color: #2e8b57;"><strong>int</strong></span> 就为 <span style="color: #2e8b57;"><strong>int</strong></span> 型，比 <span style="color: #2e8b57;"><strong>int</strong></span> 大最小<span style="color: #ff00ff;">8</span>位就为 <span style="color: #2e8b57;"><strong>long</strong></span> 型)<br />
⑶ 数值后加上后缀 U, L, UL 分别表示 <span style="color: #2e8b57;"><strong>unsigned</strong></span>, <span style="color: #2e8b57;"><strong>long</strong></span>, <span style="color: #2e8b57;"><strong>unsigned</strong></span> <span style="color: #2e8b57;"><strong>long</strong></span><br />
⒉ 浮点数字面值<br />
⑴ 可以使用普通的十进制表示法或科学计数法表示。使用后者时，用E或e来标示指数<br />
⑵ 默认为 <span style="color: #2e8b57;"><strong>double</strong></span> 型<br />
⑶ 数值后加上后缀 F, L 分别表示 <span style="color: #2e8b57;"><strong>float</strong></span>, <span style="color: #2e8b57;"><strong>long</strong></span> <span style="color: #2e8b57;"><strong>double</strong></span><br />
⒊ 布尔字面值: <span style="color: #ff00ff;">true</span>, <span style="color: #ff00ff;">false</span><br />
⒋ 字符字面值<br />
⑴ 通过用单引号引起的字符或转义字符定义<br />
⑵ 前面加上L表示wchar_t型<br />
⑶ C++中定义的转义字符有：<br />
\&#8217; 单引号<br />
\<span style="color: #ff00ff;">&#8221; 双引号</span><br />
\\ 反斜杠<br />
\<span style="color: #ff00ff;">0</span> 空字符<br />
\a 响铃<br />
\b 后退<br />
\f 走纸<br />
\n 换行<br />
\r 回车<br />
\t 水平制表符<br />
\v 垂直制表符<br />
\oct 编号为oct的字符(oct表示一个八进制数，不必以<span style="color: #ff00ff;">0</span>开头)<br />
\xhex 编号为hex的字符(hex表示一个十六进制数，不以0x或0X开头)<br />
⒌ 字符串字面值<br />
⑴ 通过用双引号引起的若干个字符或转义字符定义<br />
⑵ 类型为以 <span style="color: #ff00ff;">NULL</span> 结束的 <span style="color: #2e8b57;"><strong>const</strong></span> <span style="color: #2e8b57;"><strong>char</strong></span> 型数组(结尾的空字符由编译器自动添加)<br />
⑶ 前面加上L表示宽字符串面值<br />
⑷ 两个相邻的仅由空格、制表符或换行符分开的(宽)字符串面值，可连接成一个新(宽)字符串字面值<br />
这一特性可用于处理长字符串字面值<br />
试图连接一个字符串字面值和一个宽字符串字面值将导致未定义行为<br />
⑸ C++代码中，在一行末尾加反斜线符号(其后不可再出现空格或注释)可将该行与下一行当作同一行处理(因此第二行可能不允许有正常的缩进)<br />
这一特性也可用于处理长字符串字面值，但并不常用</p>
<p>⒉⒊ 变量<br />
⒈ 左值(lvalue)和右值(rvalue)<br />
⑴ 左值：可以出现在赋值操作的左边或右边的值。非 <span style="color: #2e8b57;"><strong>const</strong></span> 左值可读可写。<br />
⑵ 右值：可以出现在赋值操作右边但不能出现在左边的值。可读不可写。<br />
变量是左值<br />
⒉ 变量名，可由字母或下划线开头，用字母、数字和下划线组成；大小写敏感<br />
⒊ 变量的定义<br />
⑴ 变量的定义为变量分配存储空间，还能同时指定其初始值。在程序中，一个变量有且仅有一个定义<br />
⑵ 变量的初始化<br />
① 提供初始化式时，支持两种初始化形式：<br />
a. 复制初始化：用等号(=) (注:不是赋值)<br />
b. 直接初始化：将初始化式放在括号中；更灵活，效率更高<br />
对内置类型而言，二者几乎没有差别；对类类型而言，有时必须用后者<br />
② 不提供初始化式：<br />
a. 对内置类型变量：在函数体外定义的自动初始化为<span style="color: #ff00ff;">0</span>，在函数体内定义的不进行自动初始化<br />
b. 对类类型变量：若该类提供默认构造函数则调用它，否则必须提供显式初始化式<br />
③ 同时定义多个变量时，以上各种初始化形式(包括不提供初始化式)都可混用；可以用同一定义中前面已定义变量的值给后面的变量初始化<br />
⑶ 在变量首次使用处定义变量往往能增加代码可读性，减少开销<br />
⒋ 变量的声明<br />
⑴ 变量的声明向程序表明变量的类型和名字。声明可以在程序中出现多次。定义也是声明<br />
⑵ 使用 <span style="color: #2e8b57;"><strong>extern</strong></span> 关键字可声明一个变量而不定义它，常用于使数据横跨多个文件的作用域<br />
⑶ 若声明有初始化式，则被当作定义，即使使用了 <span style="color: #2e8b57;"><strong>extern</strong></span> 关键字也一样<br />
⑷ 在全局作用域中定义的非 <span style="color: #2e8b57;"><strong>const</strong></span> 对象在整个程序的各个文件中都可见，只要在其它文件中声明它就可以访问；<br />
但全局作用域中的 <span style="color: #2e8b57;"><strong>const</strong></span> 对象默认为其定义所在文件的局部对象，只有在定义前指定它为 <span style="color: #2e8b57;"><strong>extern</strong></span> 才能在其它文件中访问</p>
<p>⒉⒊⒍ 名字的作用域<br />
⒈ 作用域(scope)：程序的一部分，名字在其中有意义。<br />
C++中的作用域有以下几种：<br />
⑴ 全局：名字定义在任何其他作用域外<br />
⑵ 类：名字由类定义<br />
⑶ 命名空间：名字在命名空间中定义<br />
⑷ 局部：名字在函数中定义<br />
⑸ 块：名字定义在语句块中(即定义在一对花括号里)<br />
⑹ 语句：名字在语句(如 <span style="color: #804040;"><strong>if</strong></span> 、 <span style="color: #804040;"><strong>while</strong></span> 、 <span style="color: #804040;"><strong>for</strong></span> 语句)的条件内定义<br />
⒉ 作用域可嵌套，外层作用域的名字在内层可见。内层作用域中的名字可以屏蔽(hide)外层中同样的</p>
<p>⒉⒌ 引用<br />
⒈ 引用(reference)是一种复合类型(即用其它类型定义的类型)，表示另一个(非引用)对象的别名，对它的所有操作都直接作用在该对象上<br />
引用主要用作函数形参<br />
⒉ 引用定义格式为<br />
类型名&amp; 标识符<br />
( 数组的引用定义格式： 类型名 (&amp;标识符)[维度]<br />
指针的引用定义格式： 类型名*&amp; 标识符         )<br />
同时定义多个引用时每个引用的标识符前都要加&amp;(以及[维度]和*)<br />
定义引用必须用对应类型的对象初始化，表示绑定到该对象，且一经绑定不可更改<br />
⒊ <span style="color: #2e8b57;"><strong>const</strong></span> 引用是只读的，可以绑定到不同但相关类型的对象，也可绑定到右值；<br />
非 <span style="color: #2e8b57;"><strong>const</strong></span> 对象只能绑定到与该引用同类型的对象</p>
<p>⒉⒍ 定义类型名<br />
⒈ <span style="color: #2e8b57;"><strong>typedef</strong></span> 用于定义一个类型的同义词。定义格式为：<br />
<span style="color: #2e8b57;"><strong>typedef</strong></span> 类型 标识符;<br />
⒉ 常为以下<span style="color: #ff00ff;">3</span>种目的使用 <span style="color: #2e8b57;"><strong>typedef</strong></span> :<br />
⑴ 隐藏特定类型实现，强调使用类型的目的<br />
⑵ 精简类型定义以便理解<br />
⑶ 多次使用同一类型，保持每次使用目的明确</p>
<p>⒉⒎ 枚举<br />
⒈ 枚举(enumeration)类型由一组枚举成员(enumerator)组成<br />
其中每个枚举成员都有自己的名字，初始化为整型 <span style="color: #2e8b57;"><strong>const</strong></span> 值(值可重复，但初始化后不能修改)<br />
⒉ 定义格式： <span style="color: #2e8b57;"><strong>enum</strong></span> 枚举类型名 {用逗号分开的枚举成员列表};<br />
可以用常量表达式通过赋值操作符为列表中某些枚举成员提供初始值，未提供初值的默认每个值比前一个大<span style="color: #ff00ff;">1</span>(第一个默认为<span style="color: #ff00ff;">0</span>)<br />
⒊ 每个 <span style="color: #2e8b57;"><strong>enum</strong></span> 都唯一定义一种类型，枚举类型的对象初始化或赋值时只能通过该类型的枚举成员或另一对象进行</p>
<p>⒉⒐ 头文件<br />
⒈ 因为头文件包含在多个源文件中，故一般只应包含对象和函数的声明而不包含定义，但以下除外：<br />
⑴ 类<br />
⑵ 用常量表达式初始化的 <span style="color: #2e8b57;"><strong>const</strong></span> 对象<br />
(若不是用常量表达式初始化，则应和其它对象一样在一个文件中定义并初始化，并在头文件中添加 <span style="color: #2e8b57;"><strong>extern</strong></span> 声明以便多文件共享)<br />
⑶ <span style="color: #2e8b57;"><strong>inline</strong></span> 函数<br />
以上都可以在多个文件中定义(只要定义一致)。需要在头文件定义是因为编译器需要它们的定义来产生代码<br />
⒉ 预处理器<br />
⑴ #include 只接受一个头文件名作为参数<br />
头文件名在尖括号中被认为是标准头文件，编译器将在预定义位置查找<br />
头文件名在引号中被认为是非系统头文件，通常在源文件所在位置查找<br />
⑵ 可使用预处理器定义头文件保护符(header guard)避免多重包含，如：<br />
<span style="color: #a020f0;"> #ifndef XXX_HEADER_H</span><br />
<span style="color: #a020f0;"> #define XXX_HEADER_H</span><br />
<span style="color: #0000ff;">// 头文件内容，包括类等的定义</span><br />
<span style="color: #a020f0;"> #endif</span></p>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/' rel='bookmark' title='C++ Primer 读书笔记 &#8211; 索引'>C++ Primer 读书笔记 &#8211; 索引</a></li>
<li><a href='http://blog.tomtung.com/2007/06/c-cpp-io-odd-behavior/' rel='bookmark' title='诡异的事：关于C/C++输入输出'>诡异的事：关于C/C++输入输出</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter2/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>C++ Primer 读书笔记 &#8211; 索引</title>
		<link>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/</link>
		<comments>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/#comments</comments>
		<pubDate>Thu, 07 Aug 2008 01:31:31 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[新长征路上的代码]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[笔记]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2008/08/c-primer-%e8%af%bb%e4%b9%a6%e7%ac%94%e8%ae%b0-%e5%86%99%e5%9c%a8%e5%89%8d%e9%9d%a2%e6%9b%b4%e6%96%b0%e7%b4%a2%e5%bc%95/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/" title="C++ Primer 读书笔记 - 索引"></a>终于开始读第二遍了……开始真没把这书当回事。搞OI的时候一直是用 C++ 的（当然是作为 a better C ……当然不包括STL……），C++ 的教材也读过（顺便Orz下谭老师），觉得写程序就是算法比较麻烦，语言只是个基础，随随便便就搞定了。这书是年初买的，当时还想着一个月看完呢，结果，呃，断断续续拖了仨月到高考前才看完第一遍。C++ 里各种各样的功能及细节一时让我觉得自己在备考地理历史或是别的什么文科课程…… 这本书我觉得和CLRS很像。书名都非常谦虚（一个叫 Primer 一个叫 Introduction），都比书名暗示的要厚、难，虽然没有特别 tricky 的内容却贵在全面，如果能够掌握对付一般情况都绰绰有余。 在读第一遍的时候我就已经做了比较粗略的笔记，现在重新整理一下。所有帮助理解的辅助内容都尽量被剔除，为了帮助理解而做的顺序调整也被恢复，使得关键点以更结构化的形式组织起来。我希望有了整理的笔记就可以抛开原书（去BUAA实在不想带这书，太沉了- -）。 笔记中很多基础点可能会因为之前就已经熟知而被忽略。这个笔记并不适合初学者。它适合： 1. 我自己——这是做笔记的主要目的 2. 看过全书希望迅速浏览各个关键点来复习的同学 3. 已经读过别的 C++ 书籍不知道此书是否还值得一读的同学 4. 希望能借助别人的笔记加工出自己的 ……………… 恩……笔记会以章为单位放出，已经放出的内容也可能被修改。如果发现任何错误、遗漏、啰嗦等不妥的地方，欢迎指正。 笔记会放在新分类“新长征路上的代码”中。之前关于OI的分类不再更新。 &#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;菜菜的分割线&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212; 笔记索引 第二章 - 基本内置类型字面值常量变量作用域引用(包括指针的引用、数组的引用) 枚举 第三章 - bitset &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/06/c-cpp-io-odd-behavior/' rel='bookmark' title='诡异的事：关于C/C++输入输出'>诡异的事：关于C/C++输入输出</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/" title="C++ Primer 读书笔记 - 索引"></a><p><a href="http://www.douban.com/subject/1767741/"><img style="padding-right: 20px; padding-left: 0px; float: left; padding-bottom: 20px; padding-top: 0px;" src="http://otho.douban.com/mpic/s1638975.jpg" alt="" /></a><a href="http://www.douban.com/subject/1415354/"><img style="padding-right: 20px; padding-left: 0px; float: left; padding-bottom: 20px; padding-top: 0px;" src="http://otho.douban.com/mpic/s2391721.jpg" alt="" /></a> 终于开始读第二遍了……开始真没把这书当回事。搞OI的时候一直是用 C++ 的（当然是作为 a better C ……当然不包括STL……），C++ 的教材也读过（顺便Orz下谭老师），觉得写程序就是算法比较麻烦，语言只是个基础，随随便便就搞定了。这书是年初买的，当时还想着一个月看完呢，结果，呃，断断续续拖了仨月到高考前才看完第一遍。C++ 里各种各样的功能及细节一时让我觉得自己在备考地理历史或是别的什么文科课程……<br />
这本书我觉得和CLRS很像。书名都非常谦虚（一个叫 Primer 一个叫 Introduction），都比书名暗示的要厚、难，虽然没有特别 tricky 的内容却贵在全面，如果能够掌握对付一般情况都绰绰有余。<br />
在读第一遍的时候我就已经做了比较粗略的笔记，现在重新整理一下。所有帮助理解的辅助内容都尽量被剔除，为了帮助理解而做的顺序调整也被恢复，使得关键点以更结构化的形式组织起来。我希望有了整理的笔记就可以抛开原书（去BUAA实在不想带这书，太沉了- -）。</p>
<p>笔记中很多基础点可能会因为之前就已经熟知而被忽略。这个笔记并不适合初学者。它适合：</p>
<p>1. 我自己——这是做笔记的主要目的</p>
<p>2. 看过全书希望迅速浏览各个关键点来复习的同学</p>
<p>3. 已经读过别的 C++ 书籍不知道此书是否还值得一读的同学</p>
<p>4. 希望能借助别人的笔记加工出自己的</p>
<p>………………</p>
<p>恩……笔记会以章为单位放出，已经放出的内容也可能被修改。如果发现任何错误、遗漏、啰嗦等不妥的地方，欢迎指正。</p>
<p>笔记会放在新分类“新长征路上的代码”中。之前关于OI的分类不再更新。</p>
<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;菜菜的分割线&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;</p>
<pre><span style="font-size: 12px;">笔记索引</span>

<a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter2/" target="_blank"><span style="font-size: 12px;">第二章</span></a><span style="font-size: 12px;"> - 基本内置类型字面值常量变量作用域引用</span><span style="color: #999999;">(包括指针的引用、数组的引用)</span> 枚举

<a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter3/" target="_blank"><span style="font-size: 12px;">第三章</span></a><span style="font-size: 12px;"> - bitset </span><span style="color: #999999;">(vector、string和迭代器放到第九章和第十一章)</span>

<a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter4/" target="_blank"><span style="font-size: 12px;">第四章</span></a><span style="font-size: 12px;"> - 数组</span><span style="color: #999999;">(动态数组放到第五章new和delete表达式部分)</span> <span style="font-size: 12px;">C风格字符串</span>

<span style="font-size: 12px;">        指针</span><span style="font-size: 12px; color: #999999;">(包括指向数组的指针，不包括第七章的函数指针)</span>

<a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter5/" target="_blank"><span style="font-size: 12px;">第五章</span></a> <span style="font-size: 12px;">- 操作符(结合性、优先级)</span><span style="color: #999999;">(不包括与作用域,RTTI,成员指针,异常等有关操作符)</span>

<span style="color: #000000;">        表达式</span> 类型转换(显式、隐式)

<span style="font-size: 12px;"><a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter6/" target="_blank">第六章</a> - 语句</span><span style="color: #999999;">(与异常处理有关的内容放到第十七章；不包括第七章return语句)</span>

        使用预处理器调试

<span style="font-size: 12px;"><a href="http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-chapter7/" target="_blank"><span style="font-size: 12px;">第七章</span></a> - 函数</span><span style="color: #999999;">(类成员函数放到第十二章)</span>

<span style="font-size: 12px;"><a href="http://blog.tomtung.com/2008/09/cpp-primer-reding-notes-chapter8/" target="_blank"><span style="font-size: 12px;">第八章</span></a> - 标准IO库</span><span style="color: #999999;">(包括附录3中的所有内容)</span>

（停止更新）
</pre>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/06/c-cpp-io-odd-behavior/' rel='bookmark' title='诡异的事：关于C/C++输入输出'>诡异的事：关于C/C++输入输出</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2008/08/cpp-primer-reding-notes-index/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>OI最后的谢幕·18岁新的开始</title>
		<link>http://blog.tomtung.com/2007/11/oi-curtain-call/</link>
		<comments>http://blog.tomtung.com/2007/11/oi-curtain-call/#comments</comments>
		<pubDate>Thu, 22 Nov 2007 14:01:46 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[NOI]]></category>
		<category><![CDATA[NOIP]]></category>
		<category><![CDATA[保送]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/11/oi%e6%9c%80%e5%90%8e%e7%9a%84%e8%b0%a2%e5%b9%95%c2%b718%e5%b2%81%e6%96%b0%e7%9a%84%e5%bc%80%e5%a7%8b/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/11/oi-curtain-call/" title="OI最后的谢幕·18岁新的开始"></a>这一站是终点，还是另一个起点？ ——几米《地下铁》 NOIP结束的那天一回到家，我就先把QQ上“巨菜逆铭”的头像和“我巨菜”的个人说明去掉，然后把blog里“OI之路”的文章分类改成了“OI往事”。是啊，退役了，不再是一个OIer了，无论是菜是牛都已成往事。这个谢幕也许并不完美，但是我无愧于心。 从去年NOIP06前夕到现在，一年多一点，就是我全部的OI之旅了。虽然非常羡慕那些强省的OIer可以很早就了解到OI之美，然而从NOIP06启航，到在WC里瞻仰群牛大开眼界，然后在省选时置于死地后又峰回路转，接着是NOI由于水平不足加之准备策略失误导致的惨败，最后是NOIP07并不完美的结局，我经历了一个OI巨菜所能期望经历的一切。有过泪水，有过欢笑，有过初涉OI时的无知无畏，也有过面对大牛时的相形见绌。这短暂却充实的一年因为经历丰富而显得格外漫长，而曾经的那一幕幕却仍如在昨日。我甚至还清楚地记得今年过年那天，我一个人呆在房子里，不顾隔壁的喧嚣，计算USACO里那道The Clocks的搜索树节点个数。那时候还是我刚刚入门、OI热情最高涨的时候啊，每次深夜关掉电脑从思考中回过神来都恍若隔世。 的确，每个OIer对于自己的OI之路都会有很深刻的记忆。我会一直记得USACO那些诡异的奶牛们和“Your Program Works The First Time”时的惊喜，记得Vijos刷出五六页Waiting时的万分焦急，记得20分钟啪啪啪拍出一套完整SBT后小小的得意，记得几个小时都过不了一道搜索时的郁闷，记得深夜里面对显示器又涩又痒的眼睛，记得大赛之前自己砰砰乱跳的心。短短一年的OI之路，曾经阳光明媚，也曾经曲折泥泞，不管怎么说，我跌跌撞撞地一直走了下来，我不曾后悔。 OI在每个OIer心中都已不仅仅是一项学科竞赛。即使不像Ghost说的达到“信仰”的高度，也是一种信念、一种精神。还有哪科竞赛会使我们收获全国范围内的真挚友谊？还有哪科竞赛会让我们在大年三十仍在群里一边开玩笑一边讨论算法？还有哪科竞赛会让我们在网上自发组织这么多自己出题（M67牛说这些题比NOIP07题的水平高多了）的模拟赛，甚至用举办模拟赛的方法庆祝七夕、庆祝光棍节、过生日甚至见证爱情？OIer是所有搞竞赛的学生中最另类的一些，OI也是唯一与高考科目无关的竞赛。我们因此也面对了更多的困难，经受着更大的考验，然而OI承载着OIer们的梦想，OIer们也为此付出了太多太多。 我曾经很多次感叹，我学到的越多就越清楚地意识到自己的无知。这确实是实话。OI和演讲比赛不同，我不需要刻意掩藏自己的胆怯，强做出一副自信满满、天不怕地不怕的样子。看到很多初中的小朋友就比自己强出很多，很羡慕他们的环境，而那种挫败感也是很难避免的。况且每天上网和那些不把北大清华当回事的大牛们待在一起，不生出些自卑情绪都难。但和他们在一起我也学会了很多东西，自己水平也有不少长进，生活也充实不少。我想起前一段时间自己在Vijos的签名：“我是巨菜，但我在尽我所能快速成长。”这话看着真提气。 每个OIer无论是牛是菜，在成长过程中都会得到很多无私的帮助，因而在退役的时候也会有一大堆要感谢的人。下面我也要列出一个名单，向帮助过我的人表达我的谢意。谢谢你们，如果没有你们，也许我根本不会走完这条并没有多长的OI之路。 首先要感谢我师傅大漠。师傅在OI方面也许没有我后来遇到的很多冲金夺银的大牛那么牛，但是在我OI之路开始于NOIP06迷茫无助之时，是师傅给我提供了最真诚热情的帮助，教给了我很多东西。正所谓 “师傅领进门”，对此我至今仍心存感激。 然后不能不提的就是我们甘肃大水牛Ghost~~他几乎是对我乃至这两届很多GSOIer的OI生涯影响最大的人了。无论是OI还是数学还是保送问题，我都缠着问他，还有很多重要的决定，都曾向他征求意见。Ghost人非常好，非常乐意帮助我们这些菜，也算是少数几个我不叫“大牛”的大牛了，即使叫也要叫“大水牛”-_-&#124;&#124;&#124;……很难想象一个其实有些不善言谈的人在OI方面能这么牛，而且在QQ上能如此口若悬河，尽妖孽之所极……GSOIer的诡异外号几乎都是他起的，还有“21727”、“interrosting”等等笑料。。。能通过OI认识这个鬼牛，也真是一大幸事~ 很幸运遇到了我们的信息老师老赵，别的学校的OIer都很羡慕啊。咱老赵认真负责，每次比赛前都能收到她的短信或者Email，叮嘱种种细节，甚至包括吃巧克力后什么时候会有比较好的状态什么的，对我们的各种事情也都会很操心。为此清风都说后悔没来咱一中。 还有Ivan，也许真是有缘吧，WC回来以后莫名其妙地主动加了我=.=，然后才知道去fzyz的时候我们就坐的一辆面包车。Ivan是个非常有才华的OIer&#38;MOer，也算是一个我不叫“大牛”的大牛吧。然而也总是不自信，非但从来不B4我这个巨菜，还真诚得让我有些受宠若惊……Ivan差不多是惟一一个说我很“cool” 的人（大汗-_-!），并且还感慨“真奇怪你怎么会没有GF” （瀑布汗-_-&#124;&#124;&#124;），于是我就说啊，Ivan，如果你是个PLMM就好了……总是会在我最迷茫的时候鼓励我，然后发一堆PLMM的PP过来（再瀑布汗-_-&#124;&#124;&#124;）。Ivan，就像你说的，也许我们不能做一辈子OIer，但我们能做一辈子朋友^_^ 哑熊、DFDNN、清风、Anti、BT、亚圣、Marchine，喊出这些名字感觉真亲切。战友也好，对手也罢，说到底我们都是GSOIer，都是非常非常好的朋友。真的很高兴能认识你们，能和你们一起努力。希望和我一样高三的OIer们都能保送到好大学去（Marchine大牛ms是要降50分去清华的）。至于高二的诸位，明年NOI就看你们的了。 感谢zmcplmmdn（=。=），一个未曾谋面的OIer，在每次比赛前都会bless我。NOI挂掉后也曾发来一条很长的信息鼓励我（这条信息我现在还存着呢），真的很感动。希望能在明年NOI的获奖名单上看到你，好好加油。 感谢DD牛和Matrix67牛，你们的教程让我受益匪浅。 感谢每个办模拟赛的大牛，通过这些模拟赛，我得以及时修正NOI时错误的答题策略，没有在NOIP时再次挂掉。 感谢身边所有的朋友。我没有像当年Ghost那样不被理解，甚至被B4；相反，你们给我的更多是鼓励和支持。等大学有着落了请你们早餐哈~ 至于那些曾在这一路上误导我甚至给我设障的人，我已经不记得你们了，也不想再提起你们，就这样。 虽然现在仍存有因我用longlong而被卡在全国线下的危险，但比结果更重要的是这一年我所经历的一切。这一年我失去了很多，也放弃了很多，但却在拼搏的汗水中成长，在失落的泪水中成熟，换来的是一个至少对自己来说不平凡的17岁。这一切，便是我给自己最好的成年礼物。今后我是否会成为一个ACMer？我不知道自己是否还有这样的勇气和实力，但我会一直努力。在OI并不完美的谢幕后，我迎来的是18岁全新的征程。 一直很遗憾OIer们没有一首自己的歌可以抒怀（《隐形的BUG》《爱在编译前》这些改编的搞笑之作应该不算）。在NOI07闭幕会上听到最后一首《拥抱明天》才发现OI精神于奥林匹克精神是何其相似：参与、坚持与友谊。而NOIP07前一天我在毫无准备的情况下听到这首歌时，一时间百感交集，竟禁不住泪水夺眶而出。本文不妨用这首歌的歌词做结吧： 让我们的心相连 把我们的爱奉献 在飒爽英姿赛场上 相逢一笑到永远 让我们的心相连 把我们的爱奉献 在奥林匹克旗帜下 拥抱明天 ﻿ 可能你对下面的文章也感兴趣： 终于有NOIP1=了 &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/11/oi-curtain-call/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/11/got-noip-1st-prize-finally/' rel='bookmark' title='终于有NOIP1=了'>终于有NOIP1=了</a></li>
<li><a href='http://blog.tomtung.com/2007/11/noip07-last-fight/' rel='bookmark' title='NOIP07：最后一役'>NOIP07：最后一役</a></li>
<li><a href='http://blog.tomtung.com/2007/04/usaco-ch2-done/' rel='bookmark' title='做完了USACO第二章，但心里并不轻松'>做完了USACO第二章，但心里并不轻松</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/11/oi-curtain-call/" title="OI最后的谢幕·18岁新的开始"></a><p style="text-align: right;">这一站是终点，还是另一个起点？<br />
——几米《地下铁》</p>
<p>NOIP结束的那天一回到家，我就先把QQ上“巨菜逆铭”的头像和“我巨菜”的个人说明去掉，然后把blog里“OI之路”的文章分类改成了“OI往事”。是啊，退役了，不再是一个OIer了，无论是菜是牛都已成往事。这个谢幕也许并不完美，但是我无愧于心。</p>
<p>从去年NOIP06前夕到现在，一年多一点，就是我全部的OI之旅了。虽然非常羡慕那些强省的OIer可以很早就了解到OI之美，然而从NOIP06启航，到在WC里瞻仰群牛大开眼界，然后在省选时置于死地后又峰回路转，接着是NOI由于水平不足加之准备策略失误导致的惨败，最后是NOIP07并不完美的结局，我经历了一个OI巨菜所能期望经历的一切。有过泪水，有过欢笑，有过初涉OI时的无知无畏，也有过面对大牛时的相形见绌。这短暂却充实的一年因为经历丰富而显得格外漫长，而曾经的那一幕幕却仍如在昨日。我甚至还清楚地记得今年过年那天，我一个人呆在房子里，不顾隔壁的喧嚣，计算USACO里那道The Clocks的搜索树节点个数。那时候还是我刚刚入门、OI热情最高涨的时候啊，每次深夜关掉电脑从思考中回过神来都恍若隔世。</p>
<p>的确，每个OIer对于自己的OI之路都会有很深刻的记忆。我会一直记得USACO那些诡异的奶牛们和“Your Program Works The First Time”时的惊喜，记得Vijos刷出五六页Waiting时的万分焦急，记得20分钟啪啪啪拍出一套完整SBT后小小的得意，记得几个小时都过不了一道搜索时的郁闷，记得深夜里面对显示器又涩又痒的眼睛，记得大赛之前自己砰砰乱跳的心。短短一年的OI之路，曾经阳光明媚，也曾经曲折泥泞，不管怎么说，我跌跌撞撞地一直走了下来，我不曾后悔。</p>
<p>OI在每个OIer心中都已不仅仅是一项学科竞赛。即使不像Ghost说的达到“信仰”的高度，也是一种信念、一种精神。还有哪科竞赛会使我们收获全国范围内的真挚友谊？还有哪科竞赛会让我们在大年三十仍在群里一边开玩笑一边讨论算法？还有哪科竞赛会让我们在网上自发组织这么多自己出题（M67牛说这些题比NOIP07题的水平高多了）的模拟赛，甚至用举办模拟赛的方法庆祝七夕、庆祝光棍节、过生日甚至见证爱情？OIer是所有搞竞赛的学生中最另类的一些，OI也是唯一与高考科目无关的竞赛。我们因此也面对了更多的困难，经受着更大的考验，然而OI承载着OIer们的梦想，OIer们也为此付出了太多太多。</p>
<p>我曾经很多次感叹，我学到的越多就越清楚地意识到自己的无知。这确实是实话。OI和演讲比赛不同，我不需要刻意掩藏自己的胆怯，强做出一副自信满满、天不怕地不怕的样子。看到很多初中的小朋友就比自己强出很多，很羡慕他们的环境，而那种挫败感也是很难避免的。况且每天上网和那些不把北大清华当回事的大牛们待在一起，不生出些自卑情绪都难。但和他们在一起我也学会了很多东西，自己水平也有不少长进，生活也充实不少。我想起前一段时间自己在Vijos的签名：“我是巨菜，但我在尽我所能快速成长。”这话看着真提气。</p>
<div class="wp-caption aligncenter" style="width: 529px"><img title="那些读不完的OI书" src="http://upload.tomtung.com/img/oi-books.jpg" alt="那些读不完的OI书" width="519" height="480" /><p class="wp-caption-text">那些读不完的OI书</p></div>
<p>每个OIer无论是牛是菜，在成长过程中都会得到很多无私的帮助，因而在退役的时候也会有一大堆要感谢的人。下面我也要列出一个名单，向帮助过我的人表达我的谢意。谢谢你们，如果没有你们，也许我根本不会走完这条并没有多长的OI之路。</p>
<p>首先要感谢我师傅大漠。师傅在OI方面也许没有我后来遇到的很多冲金夺银的大牛那么牛，但是在我OI之路开始于NOIP06迷茫无助之时，是师傅给我提供了最真诚热情的帮助，教给了我很多东西。正所谓 “师傅领进门”，对此我至今仍心存感激。</p>
<p>然后不能不提的就是我们甘肃大水牛Ghost~~他几乎是对我乃至这两届很多GSOIer的OI生涯影响最大的人了。无论是OI还是数学还是保送问题，我都缠着问他，还有很多重要的决定，都曾向他征求意见。Ghost人非常好，非常乐意帮助我们这些菜，也算是少数几个我不叫“大牛”的大牛了，即使叫也要叫“大水牛”-_-|||……很难想象一个其实有些不善言谈的人在OI方面能这么牛，而且在QQ上能如此口若悬河，尽妖孽之所极……GSOIer的诡异外号几乎都是他起的，还有“21727”、“interrosting”等等笑料。。。能通过OI认识这个鬼牛，也真是一大幸事~</p>
<p>很幸运遇到了我们的信息老师老赵，别的学校的OIer都很羡慕啊。咱老赵认真负责，每次比赛前都能收到她的短信或者Email，叮嘱种种细节，甚至包括吃巧克力后什么时候会有比较好的状态什么的，对我们的各种事情也都会很操心。为此清风都说后悔没来咱一中。</p>
<p>还有Ivan，也许真是有缘吧，WC回来以后莫名其妙地主动加了我=.=，然后才知道去fzyz的时候我们就坐的一辆面包车。Ivan是个非常有才华的OIer&amp;MOer，也算是一个我不叫“大牛”的大牛吧。然而也总是不自信，非但从来不B4我这个巨菜，还真诚得让我有些受宠若惊……Ivan差不多是惟一一个说我很“cool” 的人（大汗-_-!），并且还感慨“真奇怪你怎么会没有GF” （瀑布汗-_-|||），于是我就说啊，Ivan，如果你是个PLMM就好了……总是会在我最迷茫的时候鼓励我，然后发一堆PLMM的PP过来（再瀑布汗-_-|||）。Ivan，就像你说的，也许我们不能做一辈子OIer，但我们能做一辈子朋友^_^</p>
<p>哑熊、DFDNN、清风、Anti、BT、亚圣、Marchine，喊出这些名字感觉真亲切。战友也好，对手也罢，说到底我们都是GSOIer，都是非常非常好的朋友。真的很高兴能认识你们，能和你们一起努力。希望和我一样高三的OIer们都能保送到好大学去（Marchine大牛ms是要降50分去清华的）。至于高二的诸位，明年NOI就看你们的了。</p>
<p>感谢zmcplmmdn（=。=），一个未曾谋面的OIer，在每次比赛前都会bless我。NOI挂掉后也曾发来一条很长的信息鼓励我（这条信息我现在还存着呢），真的很感动。希望能在明年NOI的获奖名单上看到你，好好加油。</p>
<p>感谢DD牛和Matrix67牛，你们的教程让我受益匪浅。</p>
<p>感谢每个办模拟赛的大牛，通过这些模拟赛，我得以及时修正NOI时错误的答题策略，没有在NOIP时再次挂掉。</p>
<p>感谢身边所有的朋友。我没有像当年Ghost那样不被理解，甚至被B4；相反，你们给我的更多是鼓励和支持。等大学有着落了请你们早餐哈~<br />
至于那些曾在这一路上误导我甚至给我设障的人，我已经不记得你们了，也不想再提起你们，就这样。<br />
虽然现在仍存有因我用longlong而被卡在全国线下的危险，但比结果更重要的是这一年我所经历的一切。这一年我失去了很多，也放弃了很多，但却在拼搏的汗水中成长，在失落的泪水中成熟，换来的是一个至少对自己来说不平凡的17岁。这一切，便是我给自己最好的成年礼物。今后我是否会成为一个ACMer？我不知道自己是否还有这样的勇气和实力，但我会一直努力。在OI并不完美的谢幕后，我迎来的是18岁全新的征程。</p>
<p>一直很遗憾OIer们没有一首自己的歌可以抒怀（《隐形的BUG》《爱在编译前》这些改编的搞笑之作应该不算）。在NOI07闭幕会上听到最后一首《拥抱明天》才发现OI精神于奥林匹克精神是何其相似：参与、坚持与友谊。而NOIP07前一天我在毫无准备的情况下听到这首歌时，一时间百感交集，竟禁不住泪水夺眶而出。本文不妨用这首歌的歌词做结吧：</p>
<p>让我们的心相连<br />
把我们的爱奉献<br />
在飒爽英姿赛场上<br />
相逢一笑到永远</p>
<p>让我们的心相连<br />
把我们的爱奉献<br />
在奥林匹克旗帜下<br />
拥抱明天</p>
<p><object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="480" height="409" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,40,0"><param name="allowScriptAccess" value="sameDomain" /><param name="FlashVars" value="v=9XDOQ3SR&amp;p=http://cache.mofile.com/tv/images/audio.jpg&amp;autoplay=0&amp;nowSkin=0_0" /><param name="wmode" value="transparent" /><param name="src" value="http://tv.mofile.com/cn/xplayer.swf" /><param name="flashvars" value="v=9XDOQ3SR&amp;p=http://cache.mofile.com/tv/images/audio.jpg&amp;autoplay=0&amp;nowSkin=0_0" /><embed type="application/x-shockwave-flash" width="480" height="409" src="http://tv.mofile.com/cn/xplayer.swf" wmode="transparent" flashvars="v=9XDOQ3SR&amp;p=http://cache.mofile.com/tv/images/audio.jpg&amp;autoplay=0&amp;nowSkin=0_0" allowscriptaccess="sameDomain"></embed></object><br />
﻿</p>
<p><img class="alignright" title="http://upload.tomtung.com/img/tomtung-signature.png" src="http://upload.tomtung.com/img/tomtung-signature.png" alt="" width="160" height="99" /></p>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/11/got-noip-1st-prize-finally/' rel='bookmark' title='终于有NOIP1=了'>终于有NOIP1=了</a></li>
<li><a href='http://blog.tomtung.com/2007/11/noip07-last-fight/' rel='bookmark' title='NOIP07：最后一役'>NOIP07：最后一役</a></li>
<li><a href='http://blog.tomtung.com/2007/04/usaco-ch2-done/' rel='bookmark' title='做完了USACO第二章，但心里并不轻松'>做完了USACO第二章，但心里并不轻松</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/11/oi-curtain-call/feed/</wfw:commentRss>
		<slash:comments>19</slash:comments>
		</item>
		<item>
		<title>终于有NOIP1=了</title>
		<link>http://blog.tomtung.com/2007/11/got-noip-1st-prize-finally/</link>
		<comments>http://blog.tomtung.com/2007/11/got-noip-1st-prize-finally/#comments</comments>
		<pubDate>Sat, 17 Nov 2007 09:30:21 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[NOIP]]></category>
		<category><![CDATA[保送]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/11/%e7%bb%88%e4%ba%8e%e6%9c%89noip1%e4%ba%86/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/11/got-noip-1st-prize-finally/" title="终于有NOIP1=了"></a>有1=了。今年题水。第一题我Hash+Qsort，N+MlogM，其实直接快排然后统计NlogN也能过。第二题水模拟，几年没考串没想到就来这么一道。第三题弱dp，各行独立，分别求然后相加就行了，不过要高精。第四题题目没看懂。 还好我、哑熊、清风这三个高三老人的都有1=了。 不过第三题没高精而是unsigned long long拿60分，就看卡不卡数据类型。不过即使这60分丢掉也能进省前五了，问题不大。开始联系大学，还好从此语文生物不再折磨我了，化学学不学则要看报哪些大学。作业落下好多了…… 这里小小地更新下。下次更新如果不出意外应该在几天以后。 最后大赞dfdnn401分=.= 可能你对下面的文章也感兴趣： NOIP07：最后一役 计划2007：为不高考而努力！ 做完了USACO第二章，但心里并不轻松<hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/11/noip07-last-fight/' rel='bookmark' title='NOIP07：最后一役'>NOIP07：最后一役</a></li>
<li><a href='http://blog.tomtung.com/2007/01/plan-for-2007/' rel='bookmark' title='计划2007：为不高考而努力！'>计划2007：为不高考而努力！</a></li>
<li><a href='http://blog.tomtung.com/2007/04/usaco-ch2-done/' rel='bookmark' title='做完了USACO第二章，但心里并不轻松'>做完了USACO第二章，但心里并不轻松</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/11/got-noip-1st-prize-finally/" title="终于有NOIP1=了"></a><div>有1=了。今年题水。第一题我Hash+Qsort，N+MlogM，其实直接快排然后统计NlogN也能过。第二题水模拟，几年没考串没想到就来这么一道。第三题弱dp，各行独立，分别求然后相加就行了，不过要高精。第四题题目没看懂。</div>
<div>还好我、哑熊、清风这三个高三老人的都有1=了。</div>
<div>不过第三题没高精而是unsigned long long拿60分，就看卡不卡数据类型。不过即使这60分丢掉也能进省前五了，问题不大。开始联系大学，还好从此语文生物不再折磨我了，化学学不学则要看报哪些大学。作业落下好多了……</div>
<div>这里小小地更新下。下次更新如果不出意外应该在几天以后。</div>
<div>最后大赞dfdnn401分=.=</div>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/11/noip07-last-fight/' rel='bookmark' title='NOIP07：最后一役'>NOIP07：最后一役</a></li>
<li><a href='http://blog.tomtung.com/2007/01/plan-for-2007/' rel='bookmark' title='计划2007：为不高考而努力！'>计划2007：为不高考而努力！</a></li>
<li><a href='http://blog.tomtung.com/2007/04/usaco-ch2-done/' rel='bookmark' title='做完了USACO第二章，但心里并不轻松'>做完了USACO第二章，但心里并不轻松</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/11/got-noip-1st-prize-finally/feed/</wfw:commentRss>
		<slash:comments>9</slash:comments>
		</item>
		<item>
		<title>NOIP07：最后一役</title>
		<link>http://blog.tomtung.com/2007/11/noip07-last-fight/</link>
		<comments>http://blog.tomtung.com/2007/11/noip07-last-fight/#comments</comments>
		<pubDate>Fri, 16 Nov 2007 14:19:17 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[NOIP]]></category>
		<category><![CDATA[保送]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/11/noip07%ef%bc%9a%e6%9c%80%e5%90%8e%e4%b8%80%e5%bd%b9/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/11/noip07-last-fight/" title="NOIP07：最后一役"></a>现在距离NOIP07复赛开始还剩下9个小时了。竟然没有了前几天的紧张。 由于模拟赛频频失手，让我非常恐慌，最严重的时候甚至晚上躺在床上一身一身地出冷汗。模拟赛最高也不过偶尔碰见弱题的200分，最低则是无可挽回的0分。 OI生涯即将结束。我不断憧憬着1=后并没有多么美好的生活（但总比高考好），但是心里仍然没有底。在请最后一天假的时候我给班主任说“一等问题不大，只要不阴沟了翻船”的时候甚至有些脸红。曾经感觉十拿九稳的事情到了眼前却无法掌握。 今天下午在群里放歌，随手下了《滚滚长江东逝水》和《拥抱明天》两首歌放——这都是NOI闭幕会上曾经被演唱的歌。没想到听完前者鼻子这是有些酸，听完后者（这是闭幕会的最后一首歌）心里竟然真好像被什么东西击中了一样，泪流满面。这不是郭小四的矫情。听着歌真的万千感慨涌上心头。真的，OI已经是一个信念，一个梦想。在追逐的过程中，我们付出了太多。 祝福大家。尤其是哑熊、清风、我和其他高三的OIer们。最后一次机会了，希望OI生涯不要留下遗憾。 Bless all&#8230; P.S.我的生日大家不要管了，高三时间紧，我知道选礼物真的非常非常费时间，只要有人祝福我就很知足了。况且若NOIP挂掉也不会有心情过什么生日了。（星驰你是例外啊……由于你长期拖欠，现罚你送我和他们给你送的规格一样的~~hoho~） 可能你对下面的文章也感兴趣： 计划2007：为不高考而努力！ 做完了USACO第二章，但心里并不轻松 第一次月考及其他<hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/01/plan-for-2007/' rel='bookmark' title='计划2007：为不高考而努力！'>计划2007：为不高考而努力！</a></li>
<li><a href='http://blog.tomtung.com/2007/04/usaco-ch2-done/' rel='bookmark' title='做完了USACO第二章，但心里并不轻松'>做完了USACO第二章，但心里并不轻松</a></li>
<li><a href='http://blog.tomtung.com/2007/09/first-monthly-exam-etc/' rel='bookmark' title='第一次月考及其他'>第一次月考及其他</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/11/noip07-last-fight/" title="NOIP07：最后一役"></a><div>现在距离NOIP07复赛开始还剩下9个小时了。竟然没有了前几天的紧张。</div>
<div>由于模拟赛频频失手，让我非常恐慌，最严重的时候甚至晚上躺在床上一身一身地出冷汗。模拟赛最高也不过偶尔碰见弱题的200分，最低则是无可挽回的0分。</div>
<div>OI生涯即将结束。我不断憧憬着1=后并没有多么美好的生活（但总比高考好），但是心里仍然没有底。在请最后一天假的时候我给班主任说“一等问题不大，只要不阴沟了翻船”的时候甚至有些脸红。曾经感觉十拿九稳的事情到了眼前却无法掌握。</div>
<div>今天下午在群里放歌，随手下了《<span style="font-family: 宋体;">滚滚长江东逝水</span>》和《拥抱明天》两首歌放——这都是NOI闭幕会上曾经被演唱的歌。没想到听完前者鼻子这是有些酸，听完后者（这是闭幕会的最后一首歌）心里竟然真好像被什么东西击中了一样，泪流满面。这不是郭小四的矫情。听着歌真的万千感慨涌上心头。真的，OI已经是一个信念，一个梦想。在追逐的过程中，我们付出了太多。</div>
<div>祝福大家。尤其是哑熊、清风、我和其他高三的OIer们。最后一次机会了，希望OI生涯不要留下遗憾。</div>
<div>Bless all&#8230;</div>
<div>P.S.我的生日大家不要管了，高三时间紧，我知道选礼物真的非常非常费时间，只要有人祝福我就很知足了。况且若NOIP挂掉也不会有心情过什么生日了。（星驰你是例外啊……由于你长期拖欠，现罚你送我和他们给你送的规格一样的~~hoho~）</div>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/01/plan-for-2007/' rel='bookmark' title='计划2007：为不高考而努力！'>计划2007：为不高考而努力！</a></li>
<li><a href='http://blog.tomtung.com/2007/04/usaco-ch2-done/' rel='bookmark' title='做完了USACO第二章，但心里并不轻松'>做完了USACO第二章，但心里并不轻松</a></li>
<li><a href='http://blog.tomtung.com/2007/09/first-monthly-exam-etc/' rel='bookmark' title='第一次月考及其他'>第一次月考及其他</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/11/noip07-last-fight/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>[NOIP05] 篝火晚会</title>
		<link>http://blog.tomtung.com/2007/10/noip05-fire/</link>
		<comments>http://blog.tomtung.com/2007/10/noip05-fire/#comments</comments>
		<pubDate>Tue, 02 Oct 2007 07:51:53 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/10/noip05-%e7%af%9d%e7%81%ab%e6%99%9a%e4%bc%9a/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/10/noip05-fire/" title="[NOIP05] 篝火晚会"></a>篝火晚会 (fire.pas/c/cpp) 【问题描述】 佳佳刚进高中，在军训的时候，由于佳佳吃苦耐劳，很快得到了教官的赏识，成为了“小教官”。在军训结束的那天晚上，佳佳被命令组织同学们进行篝火晚会。一共有n个同学，编号从1到n。一开始，同学们按照1，2，……，n的顺序坐成一圈，而实际上每个人都有两个最希望相邻的同学。如何下命令调整同学的次序，形成新的一个圈，使之符合同学们的意愿，成为摆在佳佳面前的一大难题。 佳佳可向同学们下达命令，每一个命令的形式如下： (b1, b2,&#8230; bm -1, bm) 这里m的值是由佳佳决定的，每次命令m的值都可以不同。这个命令的作用是移动编号是b1，b2，…… bm –1，bm的这m个同学的位置。要求b1换到b2的位置上，b2换到b3的位置上，……，要求bm换到b1的位置上。 执行每个命令都需要一些代价。我们假定如果一个命令要移动m个人的位置，那么这个命令的代价就是m。我们需要佳佳用最少的总代价实现同学们的意愿，你能帮助佳佳吗？ 【输入文件】 输入文件fire.in的第一行是一个整数n（3 &#60;= n &#60;= 50000），表示一共有n个同学。其后n行每行包括两个不同的正整数，以一个空格隔开，分别表示编号是1的同学最希望相邻的两个同学的编号，编号是2的同学最希望相邻的两个同学的编号，……，编号是n的同学最希望相邻的两个同学的编号。 【输出文件】 输出文件fire.out包括一行，这一行只包含一个整数，为最小的总代价。如果无论怎么调整都不能符合每个同学的愿望，则输出-1。 【样例输入】 4 3 4 4 3 1 2 1 2 【样例输出】 2 【数据规模】 对于30%的数据，n &#60;= 1000； 对于全部的数据，n &#60;= &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/10/noip05-fire/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/09/noip05-river/' rel='bookmark' title='[NOIP05]过河'>[NOIP05]过河</a></li>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1006/' rel='bookmark' title='[VIJOS 1006]晴天小猪历险记之 Hill'>[VIJOS 1006]晴天小猪历险记之 Hill</a></li>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1037/' rel='bookmark' title='[VIJOS 1037]搭建双塔'>[VIJOS 1037]搭建双塔</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/10/noip05-fire/" title="[NOIP05] 篝火晚会"></a><h1 style="text-align: center;">篝火晚会</h1>
<p style="text-align: center;">(fire.pas/c/cpp)</p>
<p>【问题描述】<br />
佳佳刚进高中，在军训的时候，由于佳佳吃苦耐劳，很快得到了教官的赏识，成为了“小教官”。在军训结束的那天晚上，佳佳被命令组织同学们进行篝火晚会。一共有n个同学，编号从1到n。一开始，同学们按照1，2，……，n的顺序坐成一圈，而实际上每个人都有两个最希望相邻的同学。如何下命令调整同学的次序，形成新的一个圈，使之符合同学们的意愿，成为摆在佳佳面前的一大难题。</p>
<p>佳佳可向同学们下达命令，每一个命令的形式如下：</p>
<p>(b1, b2,&#8230; bm -1, bm)</p>
<p>这里m的值是由佳佳决定的，每次命令m的值都可以不同。这个命令的作用是移动编号是b1，b2，…… bm –1，bm的这m个同学的位置。要求b1换到b2的位置上，b2换到b3的位置上，……，要求bm换到b1的位置上。</p>
<p>执行每个命令都需要一些代价。我们假定如果一个命令要移动m个人的位置，那么这个命令的代价就是m。我们需要佳佳用最少的总代价实现同学们的意愿，你能帮助佳佳吗？</p>
<p>【输入文件】<br />
输入文件fire.in的第一行是一个整数n（3 &lt;= n &lt;= 50000），表示一共有n个同学。其后n行每行包括两个不同的正整数，以一个空格隔开，分别表示编号是1的同学最希望相邻的两个同学的编号，编号是2的同学最希望相邻的两个同学的编号，……，编号是n的同学最希望相邻的两个同学的编号。</p>
<p>【输出文件】<br />
输出文件fire.out包括一行，这一行只包含一个整数，为最小的总代价。如果无论怎么调整都不能符合每个同学的愿望，则输出-1。</p>
<p>【样例输入】<br />
4<br />
3 4<br />
4 3<br />
1 2<br />
1 2</p>
<p>【样例输出】<br />
2</p>
<p>【数据规模】<br />
对于30%的数据，n &lt;= 1000；<br />
对于全部的数据，n &lt;= 50000。</p>
<p>【题解】</p>
<p>至此我就算做完了NOIP05提高组的全部四道题，发现除了第一题外其它题都不水，对于我这样的水平来说很值得一做。过河的状态压缩我想了好久；本题优化方法较过河简单，但也需要动动脑子；至于那个等价表达式则是典型的ws题：思想简单（特值法+用栈求表达式的值），但实现起来却比较繁琐，细节硕多，稍不留神就会出错，我前后一共提交了5次才AC（得到的分数分别是20、30、40、90、100）。还好这些题都是自己独立搞出来的。顺带一提，等价表达式取特值时并不像很多人说的要多个，一个足矣，当然不要取太特殊的，比如取a=1,-2,-3,0这样的就很容易被强数据阴掉，但要是取个a=-5.65742它不就没治了；同时B4数据中括号不匹配的情况。</p>
<p>当然这都是题外话，下面言归正传。</p>
<p>首先需要看到的是，虽然佳佳下达的命令形式很ws，但是在求总代价的时候却完全不需要管它。显然，在佳佳下达完一系列诡异的命令后，最后有几个人离开了原来的位置，这种情况下最小代价就是几（为什么？）。看清了这一点，问题就转化为：要使得所有人满意，最少需要让几个人离开原来的位置？</p>
<p>我们先考虑无解的情况。把每个人看成无向图中的节点，两人相邻则连一条边。当n个人以某种次序围坐成一个圈的时候，每个节点的度一定都是2。而这种状态一定是初始状态通过若干次次序调整能达到的，即一定有解。那么无解的情况就一定是，根据同学们的希望构图后有同学的度不为2。这样，我们只需要构图，然后看是否所有节点的度都为2就可以判断是否有解了。</p>
<p>如果有解，下面就需要计算最小代价了。对于我们构得的图，DFS(1)后一定得到唯一的一个序列。例如1,5,3,2,4。我们现在就需要比较它和初始状态1,2,3,4,5，看有几个人离开了原来的位置。但这个序列实际代表的是一个环，而且方向正反有两种（即1,3,5,2,4和4,2,5,3,1应该是等价的），我们就需要把初始序列正反分别“转”N次（即1,2,3,4,5; 5,1,2,3,4; 4,5,1,2,3; 3,4,5,1,2; 2,3,4,5,1 以及 5,4,3,2,1; 1,5,4,3,2; 2,1,5,4,3; 3,2,1,5,4; 4,3,2,1,5）和DFS得到的序列比较，看其中最少有几个位置上的人编号不相同，就得到了我们要求的最小代价。</p>
<p>DFS是O(N)的；旋转可以通过指针来实现，所以是O(1)的；每次比较是O(N)的，共进行2N次比较。因此总的复杂度是O(N^2)的。期望得分为30分（我没写过这方法哈）。</p>
<p>进行很多次旋转，每次都需要比较，这种方法实在太慢了，是整个算法的瓶颈。怎么改进呢？我们发现转来转去不管怎么转，任意两个人之间的相对位置关系在这过程中都不会变。于是想到做差。</p>
<p>1 5 3 2 4<br />
- 1 2 3 4 5<br />
&#8212;&#8212;&#8212;&#8212;-<br />
0 3 0 3 4  （如果差小于0则加上N=5）</p>
<p>这表示序列1,5,3,2,4不转动时1,3两个人在原来的位置上，转动3个位置后5和2两个人在原来的位置上，转动4个位置后只有4一个人会在原来的位置上。这就是说，1,5,3,2,4与1,2,3,4,5在旋转后最多有2个位置上的人编号相同，即最少有3个位置上的人编号不相同。同理：</p>
<p>1 5 3 2 4<br />
- 5 4 3 2 1<br />
&#8212;&#8212;&#8212;&#8212;-<br />
1 1 0 0 3  （如果差小于0则加上N=5）</p>
<p>1,5,3,2,4与5,4,3,2,1在旋转后最少有3个位置上的人编号不相同。</p>
<p>取其中的较小者（例子没举好，两个值相同了）为3，即最后要求的最小总代价为3。问题就算解决了。</p>
<p>算法流程总结如下：</p>
<ol>
<li>根据输入构图，要求每个节点度只能为2，否则无解；</li>
<li>dfs得到一个序列seq表示符合条件的环</li>
<li>（下标从1开始）<br />
令a[i]=seq[i]-i（若小于0则+N）<br />
a中最多有x个相等的值<br />
令b[i]=seq[i]-(N+1-i)（若小于0则+N）<br />
a中最多有y个相等的值</li>
<li>n-max(x,y)即为所求</li>
</ol>
<p>改进后的算法复杂度为O(N)，实现起来很简单。期望得分为100。</p>
<pre class="brush:c++">#include &lt;iostream&gt;
#include &lt;fstream&gt;
#include &lt;cassert&gt;
using namespace std;
long N,seq[50001],p=1;
class{
	private:
		long nbr[50001][3];
		bool flag[50001];
		bool isnbr(long v1,long v2){
			for(int i=1;i&lt;=nbr[v1][0];i++)
				if(nbr[v1][i]==v2)	return true;
			return false;
		}
	public:
		bool AddE(long v1,long v2){
			if(isnbr(v1,v2))	return true;
			else if(nbr[v1][0]==2||nbr[v2][0]==2)	return false;
			else{
				nbr[v1][++nbr[v1][0]]=v2;
				nbr[v2][++nbr[v2][0]]=v1;
				return true;
			}
		}
		void dfs(long i){
			assert(!flag[i]);
			flag[i]=true,seq[p++]=i;
			for(int j=1;j&lt;=nbr[i][0];j++){
				if(!flag[nbr[i][j]]){
					dfs(nbr[i][j]);
					break;
				}
			}
		}
}G;
long solve(void){
	long cnt1[50001]={0},cnt2[50001]={0},ans=0;
	for(int i=1,a,b;i&lt;=N;i++){
		a=seq[i]-i;
		if(a&lt;0)	a+=N;
		b=seq[i]-(N+1-i);
		if(b&lt;0)	b+=N;
		if(++cnt1[a]&gt;ans)	ans=cnt1[a];
		if(++cnt2[b]&gt;ans)	ans=cnt2[b];
	}
	return N-ans;
}
int main(){
	ifstream cin("fire.in");
	ofstream cout("fire.out");
	cin &gt;&gt; N;
	for(int i=1,nbr1,nbr2;i&lt;=N;i++){
		cin &gt;&gt; nbr1 &gt;&gt; nbr2;
		if(!G.AddE(i,nbr1)||!G.AddE(i,nbr2)){
			cout &lt;&lt; -1 &lt;&lt; endl;
			return 0;
		}
	}
	G.dfs(1);
	cout &lt;&lt; solve() &lt;&lt; endl;
	return 0;
}
</pre>
<p>最后再给一个在vijos里看到的算法。我自己没有实现过，有兴趣的试试吧。</p>
<blockquote><p>见过的最牛最简单的方法……</p>
<p>将此题转换为冒泡排序，记录下所有交换的次数和两数间的距离，加上就行了……—__—|||</p>
<p>具体是这样的，我们反向思维，本来是要求一个有序数列求出成为无序数列的代价，现在我们把无序数列（即目标数列）进行冒泡排序，然后……就是这样…… 看完之后，偶巨汗……</p></blockquote>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/09/noip05-river/' rel='bookmark' title='[NOIP05]过河'>[NOIP05]过河</a></li>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1006/' rel='bookmark' title='[VIJOS 1006]晴天小猪历险记之 Hill'>[VIJOS 1006]晴天小猪历险记之 Hill</a></li>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1037/' rel='bookmark' title='[VIJOS 1037]搭建双塔'>[VIJOS 1037]搭建双塔</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/10/noip05-fire/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[NOIP05]过河</title>
		<link>http://blog.tomtung.com/2007/09/noip05-river/</link>
		<comments>http://blog.tomtung.com/2007/09/noip05-river/#comments</comments>
		<pubDate>Wed, 19 Sep 2007 13:50:16 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[动态规划]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/09/noip05%e8%bf%87%e6%b2%b3/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/09/noip05-river/" title="[NOIP05]过河"></a>过河 (river.pas/c/cpp) 【问题描述】 在河上有一座独木桥，一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子，青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数，我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点：0，1，……，L（其中L是桥的长度）。坐标为0的点表示桥的起点，坐标为L的点表示桥的终点。青蛙从桥的起点开始，不停的向终点方向跳跃。一次跳跃的距离是S到T之间的任意正整数（包括S,T）。当青蛙跳到或跳过坐标为L的点时，就算青蛙已经跳出了独木桥。 题目给出独木桥的长度L，青蛙跳跃的距离范围S,T，桥上石子的位置。你的任务是确定青蛙要想过河，最少需要踩到的石子数。 【输入文件】 输入文件river.in的第一行有一个正整数L（1 &#60;= L &#60;=10^9），表示独木桥的长度。第二行有三个正整数S，T，M，分别表示青蛙一次跳跃的最小距离，最大距离，及桥上石子的个数，其中1 &#60;= S &#60;= T &#60;= 10，1 &#60;= M &#60;= 100。第三行有M个不同的正整数分别表示这M个石子在数轴上的位置（数据保证桥的起点和终点处没有石子）。所有相邻的整数之间用一个空格隔开。 【输出文件】 输出文件river.out只包括一个整数，表示青蛙过河最少需要踩到的石子数。 【样例输入】 10 2 3 5 2 3 5 6 7 【样例输出】 2 【数据规模】 对于30%的数据，L &#60;= 10000； 对于全部的数据，L &#60;= &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/09/noip05-river/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1037/' rel='bookmark' title='[VIJOS 1037]搭建双塔'>[VIJOS 1037]搭建双塔</a></li>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1006/' rel='bookmark' title='[VIJOS 1006]晴天小猪历险记之 Hill'>[VIJOS 1006]晴天小猪历险记之 Hill</a></li>
<li><a href='http://blog.tomtung.com/2007/05/noi99-chess/' rel='bookmark' title='[NOI99]棋盘分割(Chessboard Division)'>[NOI99]棋盘分割(Chessboard Division)</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/09/noip05-river/" title="[NOIP05]过河"></a><h1 style="text-align: center;">过河</h1>
<p style="text-align: center;">(river.pas/c/cpp)</p>
<p>【问题描述】</p>
<p>在河上有一座独木桥，一只青蛙想沿着独木桥从河的一侧跳到另一侧。在桥上有一些石子，青蛙很讨厌踩在这些石子上。由于桥的长度和青蛙一次跳过的距离都是正整数，我们可以把独木桥上青蛙可能到达的点看成数轴上的一串整点：0，1，……，L（其中L是桥的长度）。坐标为0的点表示桥的起点，坐标为L的点表示桥的终点。青蛙从桥的起点开始，不停的向终点方向跳跃。一次跳跃的距离是S到T之间的任意正整数（包括S,T）。当青蛙跳到或跳过坐标为L的点时，就算青蛙已经跳出了独木桥。</p>
<p>题目给出独木桥的长度L，青蛙跳跃的距离范围S,T，桥上石子的位置。你的任务是确定青蛙要想过河，最少需要踩到的石子数。</p>
<p>【输入文件】</p>
<p>输入文件river.in的第一行有一个正整数L（1 &lt;= L &lt;=10^9），表示独木桥的长度。第二行有三个正整数S，T，M，分别表示青蛙一次跳跃的最小距离，最大距离，及桥上石子的个数，其中1 &lt;= S &lt;= T &lt;= 10，1 &lt;= M &lt;= 100。第三行有M个不同的正整数分别表示这M个石子在数轴上的位置（数据保证桥的起点和终点处没有石子）。所有相邻的整数之间用一个空格隔开。</p>
<p>【输出文件】</p>
<p>输出文件river.out只包括一个整数，表示青蛙过河最少需要踩到的石子数。</p>
<p>【样例输入】</p>
<p>10</p>
<p>2 3 5</p>
<p>2 3 5 6 7</p>
<p>【样例输出】</p>
<p>2</p>
<p>【数据规模】</p>
<p>对于30%的数据，L &lt;= 10000；</p>
<p>对于全部的数据，L &lt;= 10^9。</p>
<p>【题解】</p>
<p>一看题，这无后效性和最优子结构也太明显了，随手就可以列出转移方程。令dp[i]表示处于i位置时，最少还要踩到几个石子（包括当前点）才能到达对岸。bridge[i]为点i处的石子个数（0或1）。则</p>
<p>当0≤i&lt;L时：dp[i] = min{dp[i+j]} + bridge[i]   (S≤j≤T)</p>
<p>当i≥L时：dp[i]=0</p>
<p>问题的解即为dp[0]。</p>
<p>但是这样写完交上去最多只能得30分。直接硬搞，复杂度是O(L*(T-S))的，T-S最大为9，无关紧要，但是L的最大值则是令人发指的10^9（青蛙跳这么长距离不累死么 &#8211; -），不TLE……恐怕需要10年以后的CPU了。那怎么办呢？（思考过程也许比较繁琐，要结论请直接看倒数第5段）</p>
<p>数据范围往往是算法选择的最重要的提示。我们看到虽然L这么巨大，但是石子的总数却不超过100个。这说明什么？说明桥上必然有大段大段的无石子区间，而无石子区间的最大长度在极限情况下将接近10^9。即在跳跃中，青蛙经常会很郁闷地在巨长的无石子区间上跳啊跳啊，想踩个石子都踩不到。上面那个普通dp肯定会导致空白区间上大量的无必要决策而无谓耗费大量时间。要减少这种时间浪费，我们可以试图把大段的无石子区间等效转化为较短的无石子区间，从而使时间开销降至可承受范围。</p>
<p>首先考虑最简单的情况：S=T。这种情况下，这个诡异的青蛙只能跳固定的长度。而跳的起点是0位置，那么青蛙经过的就只有S（或者说T，一回事）的整数倍点。如果石子的位置为S的整数倍，那么青蛙就一定会踩到，否则一定踩不到。比如S=T=3，L=100，那么青蛙经过的点就为且仅为0，3，6，&#8230;，96，99。在21、27、33、66等处的石子就一定能踩到，而20、40、80、92等处的石子就踩不到。于是，当S=T时，我们只需要数位置为S整数倍的石子有几个就得到了答案，不需要进行上面说到的“等效转化”。</p>
<p>那如果S≠T呢？我们也从一个最简单的例子看起。假如S=5，T=6，青蛙从0位置开始跳，那么它可能到达的点是：</p>
<p>5，6，</p>
<p>10，11，12，</p>
<p>15，16，17，18，</p>
<p>20，21，22，23，24，</p>
<p>25，26，27，28，29，30，31，32，33，……</p>
<p>可以看到，这些点组成了一个个连续的区间，各相邻区间起点间的距离都是S=5，开始区间长度为2，然后长度变为3，4，然后完全连在一起，后面的位置都可达。如果桥上0~999都没有石子，1000处有一个石子，那么我们只关心青蛙是否选择跳到999，998，997，996，995，994这几个位置，因为青蛙在这段无石子区间上跳半天最后总会跳到这6个位置上，在无石子区间中不管怎么跳其实都无所谓。那么，我们完全可以无视25以后的数，把这段区间等效为0~25，以确保25以后所有位置都可达且有20~25这段连续区间来代替994~999这段等长的区间。这样，我们在这段无石子区间上的决策就大大减少了。</p>
<p>桥上的其他无石子区间是否可以如法炮制呢？答案是肯定的。对于以后任何一段长度大于25+6=31的无石子区间（这是采取“等效”措施的“门槛”），我们都可以把它的长度看成31。（25为什么要加上6呢？考虑一下，进入这段无石子区间后，青蛙开始往前跳的的起点，它可能不是区间本身的起点。）这样处理后，即使桥长度达到10^9，也可以在非常短的时间内出解。事实上，我们不仅可以把无石子区间长度等效为31，等效为36，121，500也都可以，只要长度比31大就行（当然“门槛”也要相应升高）。我们把这个等效区间的“最短”长度（本例中为31）称为“等效区间最短长度”。</p>
<p>下面推广。当S≠T时我们发现，在T不变的情况下，T-S越大（即S越小），等效区间最短长度越短。T-S不变的情况下，T越大，等效区间最短长度越长。那么对于题目给出的数据范围，S=9，T=10时（满足T-S最小且T最大）得到最大的“等效区间最短长度”为100。对于其他的S和T，我们不需要专门计算它们对应的等效区间最短长度，直接采用100这个值就可以了。</p>
<p><strong>综上，若S=T，我们直接数位置能被S整除的石子个数；若S≠T，如果某无石子区间长度大于100，则等效为100，否则不变，然后再dp。</strong></p>
<p>至于为什么有同学取比100小的数也AC了，我觉得（不一定对哈，没验证）应该是数据弱了。经实验，即使取20也可以AC，取10也才WA一个点。</p>
<p>对于等效区间最短长度的这番计算其实完全不必要。比赛时最好的方法是：取时间复杂度可接受的最大值……这样最省事。</p>
<p>上面可能做麻烦了，欢迎提供简明解法，谢谢。</p>
<p>最后说点题外话。这题我调了一晚上，郁闷死了。最后发现问题竟然是：石子位置没有按照升序排列，我却想当然这么处理了，结果一个点都过不去……</p>
<p>代码如下：</p>
<pre class="brush:c++">#include &lt;iostream&gt;
#include &lt;fstream&gt;
#include &lt;bitset&gt;
#include &lt;cstdlib&gt;
using namespace std;
unsigned long L_orig,L,S,T,M,pos[102],dp[12000],ans;
bitset&lt;12000&gt; bridge,flag;
long memo(long i){
	if(i&gt;=L)	return 0;
	if(flag[i])	return dp[i];
	flag[i]=1,dp[i]=INT_MAX;
	for(int j=S;j&lt;=T;j++)
		if(dp[i]&gt;memo(i+j))
			dp[i]=memo(i+j);
	dp[i]+=bridge[i];
	return dp[i];
}
inline int cmp(const void *a,const void *b){
	return *(long*)a-*(long*)b;
}
int main(){
	ifstream cin("river.in");
	cin &gt;&gt; L_orig &gt;&gt; S &gt;&gt; T &gt;&gt; M;
	if(S==T)
		for(int i=1,tmp;i&lt;=M;i++){
			cin &gt;&gt; tmp;
			if(tmp%S==0)	ans++;
		}
	else{
		pos[M+1]=L_orig;
		for(int i=1,counter=0;i&lt;=M;i++)	cin &gt;&gt; pos[i];
		qsort(pos,M+2, sizeof(pos[0]),cmp);
		for(int i=1;i&lt;=M+1;i++){
			if(pos[i]-pos[i-1]-1&lt;=100)	L+=pos[i]-pos[i-1];
			else	L+=100;
			bridge[L]=1;
		}
		ans=memo(0);
	}
	ofstream fout("river.out");
	fout &lt;&lt; ans &lt;&lt; endl;
	return 0;
}
</pre>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1037/' rel='bookmark' title='[VIJOS 1037]搭建双塔'>[VIJOS 1037]搭建双塔</a></li>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1006/' rel='bookmark' title='[VIJOS 1006]晴天小猪历险记之 Hill'>[VIJOS 1006]晴天小猪历险记之 Hill</a></li>
<li><a href='http://blog.tomtung.com/2007/05/noi99-chess/' rel='bookmark' title='[NOI99]棋盘分割(Chessboard Division)'>[NOI99]棋盘分割(Chessboard Division)</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/09/noip05-river/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>又一件郁闷事</title>
		<link>http://blog.tomtung.com/2007/09/fml/</link>
		<comments>http://blog.tomtung.com/2007/09/fml/#comments</comments>
		<pubDate>Sat, 01 Sep 2007 15:37:17 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[NOI]]></category>
		<category><![CDATA[动态规划]]></category>
		<category><![CDATA[算法]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/09/%e5%8f%88%e4%b8%80%e4%bb%b6%e9%83%81%e9%97%b7%e4%ba%8b/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/09/fml/" title="又一件郁闷事"></a>今天在随手翻《奥赛经典》的时候，竟然看到了NOI那道我得分很低的DP题，几乎原原本本地出现在了233页上，而且包括了详解…… 看来我的曾的确确错失了很多机会。但愿这样的事情不再发生。 可能你对下面的文章也感兴趣： [VIJOS 1037]搭建双塔 [VIJOS 1006]晴天小猪历险记之 Hill [01ACM NEEuropean][URAL1183]Brackets sequence<hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1037/' rel='bookmark' title='[VIJOS 1037]搭建双塔'>[VIJOS 1037]搭建双塔</a></li>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1006/' rel='bookmark' title='[VIJOS 1006]晴天小猪历险记之 Hill'>[VIJOS 1006]晴天小猪历险记之 Hill</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1183-brackets-sequence/' rel='bookmark' title='[01ACM NEEuropean][URAL1183]Brackets sequence'>[01ACM NEEuropean][URAL1183]Brackets sequence</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/09/fml/" title="又一件郁闷事"></a><p>今天在随手翻《奥赛经典》的时候，竟然看到了NOI那道我得分很低的DP题，几乎原原本本地出现在了233页上，而且包括了详解……</p>
<p>看来我的曾的确确错失了很多机会。但愿这样的事情不再发生。</p>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1037/' rel='bookmark' title='[VIJOS 1037]搭建双塔'>[VIJOS 1037]搭建双塔</a></li>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1006/' rel='bookmark' title='[VIJOS 1006]晴天小猪历险记之 Hill'>[VIJOS 1006]晴天小猪历险记之 Hill</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1183-brackets-sequence/' rel='bookmark' title='[01ACM NEEuropean][URAL1183]Brackets sequence'>[01ACM NEEuropean][URAL1183]Brackets sequence</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/09/fml/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>[NOIP06] 2^k进制数</title>
		<link>http://blog.tomtung.com/2007/09/noip06-digital/</link>
		<comments>http://blog.tomtung.com/2007/09/noip06-digital/#comments</comments>
		<pubDate>Sat, 01 Sep 2007 15:09:53 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[组合数学]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/09/noip06-2k%e8%bf%9b%e5%88%b6%e6%95%b0/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/09/noip06-digital/" title="[NOIP06] 2^k进制数"></a>2^K进制数 【题目描述】 设r是个2^k 进制数，并满足以下条件： （1）r至少是个2位的2^k进制数。 （2）作为2^k进制数，除最后一位外，r的每一位严格小于它右边相邻的那一位。 （3）将r转换为2进制数q后，则q的总位数不超过w。 在这里，正整数k（1≤k≤9）和w（k&#60;w≤30000）是事先给定的。 问：满足上述条件的不同的r共有多少个？ 我们再从另一角度作些解释：设S是长度为w 的01字符串（即字符串S由w个“0”或“1”组成），S对应于上述条件（3）中的q。将S从右起划分为若干个长度为k 的段，每段对应一位2k进制的数，如果S至少可分成2段，则S所对应的二进制数又可以转换为上述的2k 进制数r。 例：设k=3，w=7。则r是个八进制数（23=8）。由于w=7，长度为7的01字符串按3位一段分，可分为3段（即1，3，3，左边第一段只有一个二进制位），则满足条件的八进制数有： 2位数：高位为1：6个（即12，13，14，15，16，17），高位为2：5个，…，高位为6：1个（即67）。共6+5+…+1=21个。 3位数：高位只能是1，第2位为2：5个（即123，124，125，126，127），第2位为3：4个，…，第2位为6：1个（即167）。共5+4+…+1=15个。 所以，满足要求的r共有36个。 【输入】 输入只有1行，为两个正整数，用一个空格隔开：k W 【输出】 输出为1行，是一个正整数，为所求的计算结果，即满足条件的不同的r的个数（用十进制数表示），要求最高位不得为0，各数字之间不得插入数字以外的其他字符（例如空格、换行符、逗号等）。 （提示：作为结果的正整数可能很大，但不会超过200位） 【样例输入】 3 7 【样例输出】 36 【题解】 想起去年NOIP做到这题时，我还是巨巨巨巨巨巨巨菜，看了这题的标题就直接放弃了……现在看来这题也不是那么难的。 题目中“从另一角度作些解释”是很重要的，这几乎直接给我们提供了算法（往下看之前请仔细阅读题目中的相关部分）。令N为2^k进制数的最大允许位数，\(a_0\) 为最高位允许的最大值，则有： $$N= \left\lceil \frac{W}{K} \right\rceil$$ $$a_0=2^{W \mod K}-1$$ 那么问题其实就等同于：取N个数（对应于\(2^k\)进制数中的N位），第1个数num[1]（对应于最高位）取值范围为[1,a0]，其余第2~N个数num[i]取值范围都为[1,2^K)。现从其中第i(\( &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/09/noip06-digital/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1014/' rel='bookmark' title='[VIJOS 1014]旅行商简化版'>[VIJOS 1014]旅行商简化版</a></li>
<li><a href='http://blog.tomtung.com/2007/02/usaco-arithmetic-progressions/' rel='bookmark' title='[USACO]Arithmetic Progressions'>[USACO]Arithmetic Progressions</a></li>
<li><a href='http://blog.tomtung.com/2007/05/noi99-chess/' rel='bookmark' title='[NOI99]棋盘分割(Chessboard Division)'>[NOI99]棋盘分割(Chessboard Division)</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/09/noip06-digital/" title="[NOIP06] 2^k进制数"></a>
<h1 style="text-align: center;">2^K进制数</h1>
<p>【题目描述】</p>
<p>设r是个2^k 进制数，并满足以下条件：</p>
<p>（1）r至少是个2位的2^k进制数。</p>
<p>（2）作为2^k进制数，除最后一位外，r的每一位严格小于它右边相邻的那一位。</p>
<p>（3）将r转换为2进制数q后，则q的总位数不超过w。</p>
<p>在这里，正整数k（1≤k≤9）和w（k&lt;w≤30000）是事先给定的。</p>
<p>问：满足上述条件的不同的r共有多少个？</p>
<p>我们再从另一角度作些解释：设S是长度为w 的01字符串（即字符串S由w个“0”或“1”组成），S对应于上述条件（3）中的q。将S从右起划分为若干个长度为k 的段，每段对应一位2k进制的数，如果S至少可分成2段，则S所对应的二进制数又可以转换为上述的2k 进制数r。</p>
<p>例：设k=3，w=7。则r是个八进制数（23=8）。由于w=7，长度为7的01字符串按3位一段分，可分为3段（即1，3，3，左边第一段只有一个二进制位），则满足条件的八进制数有：</p>
<p>2位数：高位为1：6个（即12，13，14，15，16，17），高位为2：5个，…，高位为6：1个（即67）。共6+5+…+1=21个。</p>
<p>3位数：高位只能是1，第2位为2：5个（即123，124，125，126，127），第2位为3：4个，…，第2位为6：1个（即167）。共5+4+…+1=15个。</p>
<p>所以，满足要求的r共有36个。</p>
<p>【输入】</p>
<p>输入只有1行，为两个正整数，用一个空格隔开：k W</p>
<p>【输出】</p>
<p>输出为1行，是一个正整数，为所求的计算结果，即满足条件的不同的r的个数（用十进制数表示），要求最高位不得为0，各数字之间不得插入数字以外的其他字符（例如空格、换行符、逗号等）。</p>
<p>（提示：作为结果的正整数可能很大，但不会超过200位）</p>
<p>【样例输入】</p>
<p>3 7</p>
<p>【样例输出】</p>
<p>36</p>
<p>【题解】</p>
<p>想起去年NOIP做到这题时，我还是巨巨巨巨巨巨巨菜，看了这题的标题就直接放弃了……现在看来这题也不是那么难的。</p>
<p>题目中“从另一角度作些解释”是很重要的，这几乎直接给我们提供了算法（往下看之前请仔细阅读题目中的相关部分）。令N为2^k进制数的最大允许位数，\(a_0\) 为最高位允许的最大值，则有：</p>
<p>$$N= \left\lceil \frac{W}{K} \right\rceil$$ $$a_0=2^{W \mod K}-1$$</p>
<p>那么问题其实就等同于：取N个数（对应于\(2^k\)进制数中的N位），第1个数num[1]（对应于最高位）取值范围为[1,a0]，其余第2~N个数num[i]取值范围都为[1,2^K)。现从其中第i(\( 1 \leq i \leq N-1\))个数num[i]开始取数，一直取到最后一个数num[N]，要求对于任意的j&gt;i满足num[j-1]&lt;num[j]。问取数方案的总数。这就是很基础的组合数学问题了。</p>
<p>先不考虑第1个数，因为限制一个最大值会稍微麻烦一点。我们考虑一般情况。显然，如果取后i个数（\( 2 \leq i &lt; N \)），可供选择的数有 \(2^k-1\) 个，则方案总数就为 \(C(i,2^k-1)\)。因此，从第2~N个数开始取的方案总数就为：$$\sum_{i=2}^{n-1}C_{2^k-1}^i$$</p>
<p>如果从第1个数开始取，则第1个数有1~\(a_0\)共\(a0\)个选择。如果选择a(\(1 \leq a \leq a_0\))，那么[1,\(2^K\))范围内剩下比a大的数就有\(2^k-a-1\)个，剩下N-1个数的可能选择方案就有\(C(2^k-i-1,n-1)\)个。那么从第1个数开始取的总方案数就为：$$\sum_{a=1}^{a_0}C_{2^k-a-1}^{n-1}$$</p>
<p>综上，要求的总方案数就为：$$ans=\sum_{a=1}^{a_0}C_{2^k-a-1}^{n-1}+\sum_{i=2}^{n-1}C_{2^k-1}^i$$</p>
<p>其中组合数\(C(N,K)\)可以利用下面递推式加上一点记忆化来在最短时间内计算得出 $$C_n^k=C_{n-1}^{k-1}+C_{n-1}^k$$</p>
<p>现在按照这个写应该会很简单了。这个如果用Pascal的int64据说能过7个点，用C++的unsigned long long则能过8个，性价比相当高了。但要想AC唯有高精。</p>
<p>源码：</p>
<pre class="brush:c++">
#include &lt;iostream&gt;
#include &lt;fstream&gt;
#include &lt;cmath&gt;
#include &lt;cassert&gt;
#define SIZE 400
using namespace std;
class _int{
	int b[SIZE],l;
public:
	_int(){};
	_int(int n);
	int&amp; operator()(const int&amp;pos){return b[pos];}
	friend const _int operator+(const _int&amp; b1,const _int&amp; b2);
	friend ostream&amp; operator&lt;&lt;(ostream&amp; is,const _int&amp; b);
};
_int::_int(int n){
	memset(b,0,sizeof(b));
	l=0;
	while(n!=0)
		b[l]=n%10,n/=10,l++;
}
const _int operator+(const _int&amp; b1,const _int&amp; b2){
	_int Ans=_int(0);
	Ans.l=b1.l;
	if(Ans.l&lt;b2.l) Ans.l=b2.l;
	for(int i=0;i&lt;Ans.l;i++) Ans.b[i]=b1.b[i]+b2.b[i];
	for(int i=0;i&lt;Ans.l;i++){
		Ans.b[i+1]+=Ans.b[i]/10;
		Ans.b[i]%=10;
		if(Ans.b[Ans.l]!=0) Ans.l++;
	}
	return Ans;
}
ostream&amp; operator&lt;&lt;(ostream&amp; os,const _int&amp; b){
	for(int i=b.l-1;i&gt;=0;i&#8211;) os&lt;&lt;b.b[i];
	return os;
}
const unsigned pow2[10]={1,2,4,8,16,32,64,128,256,512};
unsigned K,W,N,a0;
_int ans,c[512][512];
bool flag[512][512];
_int C(int n,int k){
	if(n&lt;k)	return _int(0);
	if(n==k||k==0)	return _int(1);
	assert(n&lt;512);
	if(flag[n][k])	return c[n][k];
	flag[n][k]=1;
	return c[n][k]=C(n-1,k-1)+C(n-1,k);
}
int main(){
	ifstream cin(&quot;digital.in&quot;);
	cin &gt;&gt; K &gt;&gt; W;
	N=long(ceil(double(W)/K));
	a0=pow2[W%K]-1!=0?pow2[W%K]-1:pow2[K];
	for(int i=1;i&lt;=a0&amp;&amp;pow2[K]-i-1&gt;=N-1;i++)	ans=ans+C(pow2[K]-i-1,N-1);
	for(int i=2;i&lt;=N-1&amp;&amp;pow2[K]-1&gt;=i;i++)	ans=ans+C(pow2[K]-1,i);
	ofstream cout(&quot;digital.out&quot;);
	cout &lt;&lt; ans &lt;&lt; endl;
	return 0;
}
</pre>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1014/' rel='bookmark' title='[VIJOS 1014]旅行商简化版'>[VIJOS 1014]旅行商简化版</a></li>
<li><a href='http://blog.tomtung.com/2007/02/usaco-arithmetic-progressions/' rel='bookmark' title='[USACO]Arithmetic Progressions'>[USACO]Arithmetic Progressions</a></li>
<li><a href='http://blog.tomtung.com/2007/05/noi99-chess/' rel='bookmark' title='[NOI99]棋盘分割(Chessboard Division)'>[NOI99]棋盘分割(Chessboard Division)</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/09/noip06-digital/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[VIJOS 1037]搭建双塔</title>
		<link>http://blog.tomtung.com/2007/08/vijos-1037/</link>
		<comments>http://blog.tomtung.com/2007/08/vijos-1037/#comments</comments>
		<pubDate>Sat, 25 Aug 2007 12:39:16 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[动态规划]]></category>
		<category><![CDATA[算法]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/08/vijos-1037%e6%90%ad%e5%bb%ba%e5%8f%8c%e5%a1%94/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/08/vijos-1037/" title="[VIJOS 1037]搭建双塔"></a>搭建双塔 【描述 Description】 2001年9月11日，一场突发的灾难将纽约世界贸易中心大厦夷为平地，Mr. F曾亲眼目睹了这次灾难。为了纪念“9?11”事件，Mr. F决定自己用水晶来搭建一座双塔。 Mr. F有N块水晶，每块水晶有一个高度，他想用这N块水晶搭建两座有同样高度的塔，使他们成为一座双塔，Mr. F可以从这N块水晶中任取M（1≤M≤N）块来搭建。但是他不知道能否使两座塔有同样的高度，也不知道如果能搭建成一座双塔，这座双塔的最大高度是多少。所以他来请你帮忙。 给定水晶的数量N（1≤N≤100）和每块水晶的高度Hi（N块水晶高度的总和不超过2000），你的任务是判断Mr. F能否用这些水晶搭建成一座双塔（两座塔有同样的高度），如果能，则输出所能搭建的双塔的最大高度，否则输出“Impossible”。 【输入格式 Input Format】 输入的第一行为一个数N，表示水晶的数量。第二行为N个数，第i个数表示第i个水晶的高度。 【输出格式 Output Format】 输出仅包含一行，如果能搭成一座双塔，则输出双塔的最大高度，否则输出一个字符串“Impossible”。 【样例输入 Sample Input】 5 1 3 4 5 2 【样例输出 Sample Output】 7 【题解 Solution】 应该说是很简单的dp，但是我没有独立完成……说明我果然是巨菜啊…… 状态表示是我没见过的-_-。。。看了下别人的状态表示，然后就很容易地写出了方程。 dp(i,j)表示用前i块水晶堆2个塔，两塔间高度差为j时，较低塔的最大高度。 dp(i,j)=max{ dp(i-1,j+h[i]),dp(i-1,j-h[i])+h[i],dp(i-1,h[i]-j)+j,dp(i-1,j) } &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/08/vijos-1037/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1006/' rel='bookmark' title='[VIJOS 1006]晴天小猪历险记之 Hill'>[VIJOS 1006]晴天小猪历险记之 Hill</a></li>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1014/' rel='bookmark' title='[VIJOS 1014]旅行商简化版'>[VIJOS 1014]旅行商简化版</a></li>
<li><a href='http://blog.tomtung.com/2007/05/noi97-game/' rel='bookmark' title='[NOI97]积木游戏(Game)'>[NOI97]积木游戏(Game)</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/08/vijos-1037/" title="[VIJOS 1037]搭建双塔"></a><h1 style="text-align: center;">搭建双塔</h1>
<p>【描述 Description】</p>
<p>2001年9月11日，一场突发的灾难将纽约世界贸易中心大厦夷为平地，Mr. F曾亲眼目睹了这次灾难。为了纪念“9?11”事件，Mr. F决定自己用水晶来搭建一座双塔。</p>
<p>Mr. F有N块水晶，每块水晶有一个高度，他想用这N块水晶搭建两座有同样高度的塔，使他们成为一座双塔，Mr. F可以从这N块水晶中任取M（1≤M≤N）块来搭建。但是他不知道能否使两座塔有同样的高度，也不知道如果能搭建成一座双塔，这座双塔的最大高度是多少。所以他来请你帮忙。</p>
<p>给定水晶的数量N（1≤N≤100）和每块水晶的高度Hi（N块水晶高度的总和不超过2000），你的任务是判断Mr. F能否用这些水晶搭建成一座双塔（两座塔有同样的高度），如果能，则输出所能搭建的双塔的最大高度，否则输出“Impossible”。</p>
<p>【输入格式 Input Format】</p>
<p>输入的第一行为一个数N，表示水晶的数量。第二行为N个数，第i个数表示第i个水晶的高度。</p>
<p>【输出格式 Output Format】</p>
<p>输出仅包含一行，如果能搭成一座双塔，则输出双塔的最大高度，否则输出一个字符串“Impossible”。</p>
<p>【样例输入 Sample Input】</p>
<p>5</p>
<p>1 3 4 5 2</p>
<p>【样例输出 Sample Output】</p>
<p>7</p>
<p>【题解 Solution】</p>
<p>应该说是很简单的dp，但是我没有独立完成……说明我果然是巨菜啊……</p>
<p>状态表示是我没见过的-_-。。。看了下别人的状态表示，然后就很容易地写出了方程。</p>
<p>dp(i,j)表示用前i块水晶堆2个塔，两塔间高度差为j时，较低塔的最大高度。</p>
<p>dp(i,j)=max{ dp(i-1,j+h[i]),dp(i-1,j-h[i])+h[i],dp(i-1,h[i]-j)+j,dp(i-1,j) }</p>
<p>注意处理边界……我还因为边界问题WA了一次，sigh……</p>
<p>源码：</p>
<pre class="brush:c++">#include &lt;fstream&gt;
#include &lt;iostream&gt;
#include &lt;climits&gt;
#include &lt;cassert&gt;
using namespace std;
int N,H[101],dp[101][2001];
bool flag[101][2001];
int memo(int i,int j){
	if(j&lt;0)	return INT_MIN;
	if(i==0&amp;&amp;j==0)	return 0;
	if(i==0)	return INT_MIN;
	assert(i&lt;=N&amp;&amp;j&lt;=2000);
	if(flag[i][j])	return dp[i][j];
	int ans=INT_MIN;
	if(ans&lt;memo(i-1,j))	ans=memo(i-1,j);//do not put
	if(ans&lt;memo(i-1,j+H[i]))	ans=memo(i-1,j+H[i]);//put on the higher
	if(ans&lt;memo(i-1,j-H[i])+H[i])	ans=memo(i-1,j-H[i])+H[i];//put on the lower
	if(ans&lt;memo(i-1,H[i]-j)+j)	ans=memo(i-1,H[i]-j)+j;//put on the lower,and it becomes the higher
	flag[i][j]=true;
	return dp[i][j]=ans;
}
int main(){
	//The Amulet
	unsigned rp=unsigned(-1);

	//ifstream cin("input.txt");
	cin &gt;&gt; N;
	for(int i=1;i&lt;=N;i++)	cin &gt;&gt; H[i];
	int ans = memo(N,0);
	if(ans&lt;=0)	cout &lt;&lt; "Impossible" &lt;&lt; endl;
	else cout &lt;&lt; ans &lt;&lt; endl;
	return 0;
}
</pre>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1006/' rel='bookmark' title='[VIJOS 1006]晴天小猪历险记之 Hill'>[VIJOS 1006]晴天小猪历险记之 Hill</a></li>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1014/' rel='bookmark' title='[VIJOS 1014]旅行商简化版'>[VIJOS 1014]旅行商简化版</a></li>
<li><a href='http://blog.tomtung.com/2007/05/noi97-game/' rel='bookmark' title='[NOI97]积木游戏(Game)'>[NOI97]积木游戏(Game)</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/08/vijos-1037/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[VIJOS 1006]晴天小猪历险记之 Hill</title>
		<link>http://blog.tomtung.com/2007/08/vijos-1006/</link>
		<comments>http://blog.tomtung.com/2007/08/vijos-1006/#comments</comments>
		<pubDate>Sat, 25 Aug 2007 11:34:53 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[动态规划]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/08/vijos-1006%e6%99%b4%e5%a4%a9%e5%b0%8f%e7%8c%aa%e5%8e%86%e9%99%a9%e8%ae%b0%e4%b9%8b-hill/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/08/vijos-1006/" title="[VIJOS 1006]晴天小猪历险记之 Hill"></a>晴天小猪历险记之 Hill 【背景 Background】 在很久很久以前，有一个动物村庄，那里是猪的乐园（^_^），村民们勤劳、勇敢、善良、团结…… 不过有一天，最小的小小猪生病了，而这种病是极其罕见的，因此大家都没有储存这种药物。所以晴天小猪自告奋勇，要去采取这种药草。于是，晴天小猪的传奇故事便由此展开…… 【描述 Description】 这一天，他来到了一座深山的山脚下，因为只有这座深山中的一位隐者才知道这种药草的所在。但是上山的路错综复杂，由于小小猪的病情，晴天小猪想找一条需时最少的路到达山顶，但现在它一头雾水，所以向你求助。 山用一个三角形表示，从山顶依次向下有1段、2段、3段等山路，每一段用一个数字T（1&#60;=T&#60;=100）表示，代表晴天小猪在这一段山路上需要爬的时间，每一次它都可以朝左、右、左上、右上四个方向走（**注意**：在任意一层的第一段也可以走到本层的最后一段或上一层的最后一段）。 晴天小猪从山的左下角出发，目的地为山顶，即隐者的小屋。 【输入格式 Input Format】 第一行有一个数n（2&#60;=n&#60;=1000），表示山的高度。 从第二行至第n+1行，第i+1行有i个数，每个数表示晴天小猪在这一段山路上需要爬的时间。 【输出格式 Output Format】 一个数，即晴天小猪所需要的最短时间。 【样例输入 Sample Input】 5 1 2 3 4 5 6 10 1 7 8 1 1 4 5 6 【样例输出 &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/08/vijos-1006/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/07/vijos-p1092/' rel='bookmark' title='[vijos P1092] 全排列'>[vijos P1092] 全排列</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1014/' rel='bookmark' title='[VIJOS 1014]旅行商简化版'>[VIJOS 1014]旅行商简化版</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/08/vijos-1006/" title="[VIJOS 1006]晴天小猪历险记之 Hill"></a><h1 style="text-align: center;">晴天小猪历险记之 Hill</h1>
<p>【背景 Background】</p>
<p>在很久很久以前，有一个动物村庄，那里是猪的乐园（^_^），村民们勤劳、勇敢、善良、团结……</p>
<p>不过有一天，最小的小小猪生病了，而这种病是极其罕见的，因此大家都没有储存这种药物。所以晴天小猪自告奋勇，要去采取这种药草。于是，晴天小猪的传奇故事便由此展开……</p>
<p>【描述 Description】</p>
<p>这一天，他来到了一座深山的山脚下，因为只有这座深山中的一位隐者才知道这种药草的所在。但是上山的路错综复杂，由于小小猪的病情，晴天小猪想找一条需时最少的路到达山顶，但现在它一头雾水，所以向你求助。</p>
<p>山用一个三角形表示，从山顶依次向下有1段、2段、3段等山路，每一段用一个数字T（1&lt;=T&lt;=100）表示，代表晴天小猪在这一段山路上需要爬的时间，每一次它都可以朝左、右、左上、右上四个方向走（**注意**：在任意一层的第一段也可以走到本层的最后一段或上一层的最后一段）。</p>
<p>晴天小猪从山的左下角出发，目的地为山顶，即隐者的小屋。</p>
<p>【输入格式 Input Format】</p>
<p>第一行有一个数n（2&lt;=n&lt;=1000），表示山的高度。</p>
<p>从第二行至第n+1行，第i+1行有i个数，每个数表示晴天小猪在这一段山路上需要爬的时间。</p>
<p>【输出格式 Output Format】</p>
<p>一个数，即晴天小猪所需要的最短时间。</p>
<p>【样例输入 Sample Input】</p>
<p>5<br />
1<br />
2 3<br />
4 5 6<br />
10 1 7 8<br />
1 1 4 5 6</p>
<p>【样例输出 Sample Output】</p>
<p>10</p>
<p>【时间限制 Time Limitation】</p>
<p>各个测试点1s</p>
<p>【题解 Solution】</p>
<p>题目里说：在任意一层的第一段也可以走到本层的最后一段或上一层的最后一段。我们需要加上一句：反之亦然；这样就可以做了。</p>
<p>这是挺有意思的一题。对我这样的初学者来说，也是不算水题了。这题的原型来自经典的数字三角形，但是条件有所修改。题目简要描述如下：</p>
<p>给出一个N行数字三角形，其中第i行有i列。记i行j列的数字为(i,j)，则求从(1,N)到(1,1)的一条路径，要求路径上的数字之和最小。移动规则为：到达(i,j)位置时只能向左、右、左上、右上四个方向移动。特殊地，三角形左右互通，即由(i,1)可移动到(i,i)或(i-1,i-1)；(i,i)同理。</p>
<p>的确和经典的数字三角形很像吧，仅仅在移动规则上加了两条：1.允许同行的左右移动；2.左右互通。但是加上这小小的两个条件，题目就大不一样了。</p>
<p>下面是三种方法，但事实上前两种都是扯淡，是我在思考时得到的不可行方法。建议直接跳到第三条分割线之后。</p>
<p>注：各转移方程都没有把行两端情况单另写出来……</p>
<p>———————————第1条分割线凌空飞过—————————————</p>
<p>既然和经典问题这么像，那我们就先按照经典的方程来做吧。在经典方程上简单加上向左、向右两个决策，这样行不行呢？显然，现在出现了两种环，一种是向一个方向走再折回来，一种是一直走到行首或行末，然后绕一个大圈回来。这两种环的存在导致了后效性的产生。当然，这时候递归下降还是可行的（用一个bool数组记录哪些点已经到达），但是复杂度无疑是巨大的指数级。我就这么交了一次，结果一个点都没过……-_-</p>
<p>———————————第2条分割线凌空飞过—————————————</p>
<p>这样改动后真的失去无后效性了么？引起这一变化的原因就是同行内的移动，那我们就从同行内的移动考虑。在同行内移动时，一定是从左或从右直接移动到某一个点，然后向上移；绝对不会在一行里面走回头路，也不会赖在一行里不走的。这样，行间转移的无后效性就很明显了。若用dp(i,j)表示(i,j)到(1,1)的路径最小权和，sum(i,j,k)表示第i行中j列到k列的路径最小权和，很容易写出下面的方程：</p>
<p>dp(i,j)=min{ sum(i,j,k)+min{dp(i-1,j),dp(i-1,j-1)} } (1&lt;=k&lt;=i)</p>
<p>其中，sum(i,j,k)可以在O(1)时间内获得。DP的子问题数是O(N^2)的，转移是O(N)的，这样复杂度就是O(N^3)的，对N&lt;=1000这个数据规模还是太大了。至于能得多少分，由于我没写，就不知道了。</p>
<p>———————————第3条分割线凌空飞过—————————————</p>
<p>事实上我们可以利用同行内的递推关系来获得O(N^2)的算法。我们把在第一条分割线下被否定的方程写出来：</p>
<p>dp(i,j)=min{ min{dp(i-1,j),dp(i-1,j)},dp(i,j-1),dp(i,j+1) }+num(i,j)</p>
<p>其中num(i,j)即为位置(i,j)上的数。即对于一个点(i,j)，或者上移至前一行，或者在本行内向左或向右移动。由于不走回头路，若一个点(i,j)的dp值由左边的点(i,j-1)转移得到，则(i,j-1)也必由它自己的左边的点(i,j-2)或前一行转移得到而不必考虑向右。即：</p>
<p>若dp(i,j)=dp(i,j-1)+num(i,j)</p>
<p>则必有dp(i,j-1)=min{ dp(i,j-2),min{dp(i-1,j-2),dp(i-1,j-1)} }+num(i,j-1)（不必考虑转移到dp(i,j)）</p>
<p>向右转移时也是同理。</p>
<p>现需找一个行内递推的起点。我们可取此行内min{dp(i-1,j),dp(i-1,j-1)}+num(i,j)最小的一点，因为它的dp值只能由上一行转移得到。以此为起点，向左向右分别推一圈，就得到了整行内的dp值。</p>
<p>就写这么多……感觉越写越乱了……还是看代码吧：</p>
<pre class="brush:c++">/*
  Name: Vijos P1006 晴天小猪历险记之Hill
  Author: 巨菜逆铭
  Date: 24-08-07 18:58
  Description: Dynamic Programming
*/

#include &lt;iostream&gt;
#include &lt;fstream&gt;
#include &lt;cassert&gt;
#define SYS sys\
tem
using namespace std;
unsigned long N,num[1001],dp[2][1002];
int main(){
	//The Amulet
	unsigned rp=unsigned(-1);

	//ifstream cin("input.txt");
	cin &gt;&gt; N &gt;&gt; dp[0][1];
	dp[0][0]=dp[0][2]=dp[0][1];
	bool flag=1;
	for(int i=2;i&lt;=N;i++,flag=!flag){
		int m,m_num=INT_MAX;
		for(int j=1;j&lt;=i;j++){
			cin &gt;&gt; num[j];
			dp[flag][j]=num[j]+min(dp[!flag][j],dp[!flag][j-1]);
			if(m_num&gt;dp[flag][j])	m=j,m_num=dp[flag][j];
		}
		//walk rightward
		for(int j=m+1;j&lt;=i;j++)
			if(dp[flag][j]&gt;dp[flag][j-1]+num[j])
				dp[flag][j]=dp[flag][j-1]+num[j];
		if(dp[flag][1]&gt;dp[flag][i]+num[1])
			dp[flag][1]=dp[flag][i]+num[1];
		for(int j=2;j&lt;m;j++)
			if(dp[flag][j]&gt;dp[flag][j-1]+num[j])
				dp[flag][j]=dp[flag][j-1]+num[j];
		//walk leftward
		for(int j=m-1;j&gt;=1;j--)
			if(dp[flag][j]&gt;dp[flag][j+1]+num[j])
				dp[flag][j]=dp[flag][j+1]+num[j];
		if(dp[flag][i]&gt;dp[flag][1]+num[i])
			dp[flag][i]=dp[flag][1]+num[i];
		for(int j=i-1;j&gt;m;j--)
			if(dp[flag][j]&gt;dp[flag][j+1]+num[j])
				dp[flag][j]=dp[flag][j+1]+num[j];
		//special step for convenience
		dp[flag][0]=dp[flag][i];
		dp[flag][i+1]=dp[flag][1];
	}
	cout &lt;&lt; dp[!flag][1] &lt;&lt; endl;
	//SYS("pause");
	return 0;
}
</pre>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/07/vijos-p1092/' rel='bookmark' title='[vijos P1092] 全排列'>[vijos P1092] 全排列</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
<li><a href='http://blog.tomtung.com/2007/08/vijos-1014/' rel='bookmark' title='[VIJOS 1014]旅行商简化版'>[VIJOS 1014]旅行商简化版</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/08/vijos-1006/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>[VIJOS 1014]旅行商简化版</title>
		<link>http://blog.tomtung.com/2007/08/vijos-1014/</link>
		<comments>http://blog.tomtung.com/2007/08/vijos-1014/#comments</comments>
		<pubDate>Wed, 15 Aug 2007 13:25:47 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[动态规划]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/08/vijos-1014%e6%97%85%e8%a1%8c%e5%95%86%e7%ae%80%e5%8c%96%e7%89%88/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/08/vijos-1014/" title="[VIJOS 1014]旅行商简化版"></a>旅行商简化版 【背景 Background】 欧几里德旅行商(Euclidean Traveling Salesman)问题也就是货郎担问题一直是困扰全世界数学家、计算机学家的著名问题。现有的算法都没有办法在确定型机器上在多项式时间内求出最优解，但是有办法在多项式时间内求出一个较优解。 为了简化问题，而且保证能在多项式时间内求出最优解，J.L.Bentley提出了一种叫做bitonic tour的哈密尔顿环游。它的要求是任意两点(a,b)之间的相互到达的代价dist(a,b)=dist(b,a)且任意两点之间可以相互到达，并且环游的路线只能是从最西端单向到最东端，再单项返回最西端，并且是一个哈密尔顿回路。 【描述 Description】 著名的NPC难题的简化版本 现在笛卡尔平面上有n(n&#60;=1000)个点，每个点的坐标为(x,y)(-2^31&#60;x,y&#60;2^31，且为整数)，任意两点之间相互到达的代价为这两点的欧几里德距离，现要你编程求出最短bitonic tour。 【输入格式 Input Format】 第一行一个整数n 接下来n行，每行两个整数x,y，表示某个点的坐标。 输入中保证没有重复的两点，保证最西端和最东端都只有一个点。 【输出格式 Output Format】 一行，即最短回路的长度，保留2位小数。 【样例输入 Sample Input】 7 0 6 1 0 2 3 5 4 6 1 7 5 8 &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/08/vijos-1014/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/07/noi05-cckk/' rel='bookmark' title='[NOI 05]聪聪与可可'>[NOI 05]聪聪与可可</a></li>
<li><a href='http://blog.tomtung.com/2007/05/noi99-chess/' rel='bookmark' title='[NOI99]棋盘分割(Chessboard Division)'>[NOI99]棋盘分割(Chessboard Division)</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1183-brackets-sequence/' rel='bookmark' title='[01ACM NEEuropean][URAL1183]Brackets sequence'>[01ACM NEEuropean][URAL1183]Brackets sequence</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/08/vijos-1014/" title="[VIJOS 1014]旅行商简化版"></a><h1 style="text-align: center;">旅行商简化版</h1>
<p>【背景 Background】</p>
<p>欧几里德旅行商(Euclidean Traveling Salesman)问题也就是货郎担问题一直是困扰全世界数学家、计算机学家的著名问题。现有的算法都没有办法在确定型机器上在多项式时间内求出最优解，但是有办法在多项式时间内求出一个较优解。</p>
<p>为了简化问题，而且保证能在多项式时间内求出最优解，J.L.Bentley提出了一种叫做bitonic tour的哈密尔顿环游。它的要求是任意两点(a,b)之间的相互到达的代价dist(a,b)=dist(b,a)且任意两点之间可以相互到达，并且环游的路线只能是从最西端单向到最东端，再单项返回最西端，并且是一个哈密尔顿回路。</p>
<p>【描述 Description】</p>
<p><span style="font-family: 宋体;">著名的NPC难题的简化版本</span></p>
<p><span style="font-family: 宋体;">现在笛卡尔平面上有n(n&lt;=1000)个点，每个点的坐标为(x,y)(-2^31&lt;x,y&lt;2^31，且为整数)，任意两点之间相互到达的代价为这两点的欧几里德距离，现要你编程求出最短bitonic tour。</span></p>
<p><span style="font-family: 宋体;">【输入格式 Input Format】</span></p>
<p><span style="font-family: 宋体;">第一行一个整数n</span></p>
<p><span style="font-family: 宋体;"> </span></p>
<p><span style="font-family: 宋体;">接下来n行，每行两个整数x,y，表示某个点的坐标。</span></p>
<p><span style="font-family: 宋体;"> </span></p>
<p><span style="font-family: 宋体;">输入中保证没有重复的两点，保证最西端和最东端都只有一个点。</span></p>
<p><span style="font-family: 宋体;"> </span></p>
<p><span style="font-family: 宋体;">【输出格式 Output Format】</span></p>
<p><span style="font-family: 宋体;">一行，即最短回路的长度，保留2位小数。</span></p>
<p><span style="font-family: 宋体;"> </span></p>
<p><span style="font-family: 宋体;">【样例输入 Sample Input】</span></p>
<p>7<br />
0 6<br />
1 0<br />
2 3<br />
5 4<br />
6 1<br />
7 5<br />
8 2</p>
<p>【样例输出 Sample Output】</p>
<p>25.58</p>
<p>【时间限制 Time Limitation】</p>
<p>1s</p>
<p>【来源 Source】</p>
<p>《算法导论（第二版）》 15-1</p>
<p>【题解 Solution】</p>
<p>基本DP。</p>
<p>这题需要补充一个条件：任何两点的x坐标都不相等。</p>
<p>首先按照x坐标对所有点进行排序，并从西到东依次编号。这样我们就可以进行DP了。阶段可以按照从西到东的各个点来划分。</p>
<p><img class="aligncenter" title="http://upload.tomtung.com/img/vijos-1014.png" src="http://upload.tomtung.com/img/vijos-1014.png" alt="" width="325" height="159" /></p>
<p>如图，我们对E点代表的阶段进行决策，有两种决策：E连B或E连D。这样，类似B、D的两个点就可以表示一种状态。</p>
<p>我们用dp[i][j]表示一种状态，表示在这两个点代表的状态之后，还能得到的最小长度。则有</p>
<p>dp[i][j]=dp[j][i]=min{dp[i][k]+dist[j][k],dp[k][j]+dist[i][k]}</p>
<p>(k=max(i,j)+1)</p>
<p>dp[0][0]就是所求的解。边界也很好处理。时间复杂度为O(N^2)。</p>
<p>源码：</p>
<pre class="brush:c++">
#include &lt;iostream&gt;
#include &lt;iomanip&gt;
#include &lt;cstdlib&gt;
#include &lt;cmath&gt;
using namespace std;
double dp[1000][1000],dist[1000][1000];
int N;
struct Dot{	long x,y;}dots[1000];
int cmp(const void *a, const void *b){
	return ((Dot*)a)-&gt;x-((Dot*)b)-&gt;x;
}
double memo(int a, int b){
	if(dp[a][b]&gt;0)	return dp[a][b];
	if(a==N-1)	return dist[N-1][b];
	if(b==N-1)	return dist[N-1][a];
	int c=max(a,b)+1;
	return dp[b][a]=dp[a][b]=min(memo(a,c)+dist[b][c],memo(c,b)+dist[a][c]);
}
int main(){
	cin &gt;&gt; N;
	for(int i=0;i&lt;N;i++)
		cin &gt;&gt; dots[i].x &gt;&gt; dots[i].y;
	qsort( dots, N, sizeof(dots[0]), cmp );
	for(int i=0;i&lt;N;i++)
		for(int j=i+1;j&lt;N;j++){
			double x=dots[i].x-dots[j].x,y=dots[i].y-dots[j].y;
			dist[i][j]=dist[j][i]=sqrt(x*x+y*y);
		}
	cout &lt;&lt; fixed &lt;&lt; setprecision(2) &lt;&lt;memo(0,0) &lt;&lt; endl;
	return 0;
}
</pre>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/07/noi05-cckk/' rel='bookmark' title='[NOI 05]聪聪与可可'>[NOI 05]聪聪与可可</a></li>
<li><a href='http://blog.tomtung.com/2007/05/noi99-chess/' rel='bookmark' title='[NOI99]棋盘分割(Chessboard Division)'>[NOI99]棋盘分割(Chessboard Division)</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1183-brackets-sequence/' rel='bookmark' title='[01ACM NEEuropean][URAL1183]Brackets sequence'>[01ACM NEEuropean][URAL1183]Brackets sequence</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/08/vijos-1014/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>NOI败了</title>
		<link>http://blog.tomtung.com/2007/08/noi-fail/</link>
		<comments>http://blog.tomtung.com/2007/08/noi-fail/#comments</comments>
		<pubDate>Mon, 06 Aug 2007 03:56:47 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[NOI]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/08/noi%e8%b4%a5%e4%ba%86/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/08/noi-fail/" title="NOI败了"></a>我NOI败了。 已经过去好几天，经过连着两天的游戏发泄，心情已经渐渐平静下来。但是我仍然无法忘记那长得像一个月的一周，这一周中的一幕幕场景都历历在目。我无法忘记第一试知道我的分数时的惊讶乃至惊恐，以及复评确认无误后的沮丧。无法忘记第二试最后自己的线段树怎么调试也调试不过时，看到精心经营一年的“事业”在自己面前碎成一摊却无计可施的焦虑和无不知所措。无法忘记二试结束后的感受，虽然很饿但是什么也不想吃，独自一人回到宿舍，躺在床上不停地想着“我辜负了所有人的期望”，心里像什么东西在挖，很疼，很茫然。 是啊，我辜负了所有人的期望。这是我比赛收到过祝福最多的一次，但也是挂得最彻底的一次。那时我躺在床上，想了很多。我想起了我的爸妈，想起了我熬到深夜时他们固执的陪伴；想起了乐于助人的好大牛Ghost，半年来他在各个方面提供了我所能期望的最及时有效的帮助；想起了培训老师老赵在得知我按照现有规则省选必败时的操心；想起了周围朋友们的鼓励和支持，想起过了省选后几个同学在我身边笑着喊“得奖了请客！”…… 然而一切都过去了。 这次的挂和我在准备方向、答题策略和个人能力等等方面都有关系。然而尘埃落定后我也无心再去细写。以后如果要比赛，也是完全不同的ACM/ICPC了。 这次Ivan也出人意料地挂掉了——他做前三年的题可是两银一金的。我不得不感叹比赛无常啊。但是闭幕晚会的时候和他坐在一起时，他自己还不停地安慰我。不知道是不是有什么寓意，当时晚会还安排了那首有些悲壮的“滚滚长江东逝水，浪花淘尽英雄”。听到这句我鼻子当时就有些酸了，和Ivan对望一下，感慨万千却又只能默默无言。之后他说了很多，具体的话语已经记不清了，但记得很清楚的是那哽咽的微笑，闪动在眼眶里的坚定，以及最后我们紧紧握在一起的手。 这次哑熊是最郁闷的了。当时他分数下来后，因为和去年xzj拿银的分差不多，我们都觉得拿个铜牌随便了，甚至当晚就进行了庆祝。谁知道今年的分数线高得惊人，他铜都没有拿到。当颁奖时念完铜的名单都没有他时，他一声不吭地坐在那里，脸上是无法掩饰地失望。后来知道，他离铜仅有1分，和去年莫名一样。他后来对我说，他是走的去年莫名的路，我是走的去年Ghost的路。“History will repeat itself again and again”，多么讽刺。 虽然挂了，但还是需要好好休息一下了。心太累了。人如果长期处于竞技状态毫无疑问是会疯掉的。然后就好好学习，同时准备NOIP。虽然NOI挂掉了，但是退而求其次，我觉得其实NOIP拿一等后再联系报送，或者直接去NUAA的ACM班也是个不错的选择。只是担心NOIP再rp一下，像去年鱼牛一样莫名其妙地挂掉。那我就别无选择，只能去高考了。 NOI结束了，生活和学习还要继续。不能因此颓废止步。就像Ghost说过的：“我们是失意者，不是失败者。”选择在OI之路上拼搏努力，我们不曾后悔。 最后摘我的QQ签名与各位失意的OIer共勉： How can you be a loser if you never lose your pride? P.S.其他一些照片可以看哑熊的blog 可能你对下面的文章也感兴趣： NOI来了 该出发了 请假未遂 决定了<hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/07/noi-here-i-come/' rel='bookmark' title='NOI来了 该出发了'>NOI来了 该出发了</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ask-for-leave/' rel='bookmark' title='请假未遂'>请假未遂</a></li>
<li><a href='http://blog.tomtung.com/2007/05/determination/' rel='bookmark' title='决定了'>决定了</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/08/noi-fail/" title="NOI败了"></a><p>我NOI败了。</p>
<p>已经过去好几天，经过连着两天的游戏发泄，心情已经渐渐平静下来。但是我仍然无法忘记那长得像一个月的一周，这一周中的一幕幕场景都历历在目。我无法忘记第一试知道我的分数时的惊讶乃至惊恐，以及复评确认无误后的沮丧。无法忘记第二试最后自己的线段树怎么调试也调试不过时，看到精心经营一年的“事业”在自己面前碎成一摊却无计可施的焦虑和无不知所措。无法忘记二试结束后的感受，虽然很饿但是什么也不想吃，独自一人回到宿舍，躺在床上不停地想着“我辜负了所有人的期望”，心里像什么东西在挖，很疼，很茫然。</p>
<p>是啊，我辜负了所有人的期望。这是我比赛收到过祝福最多的一次，但也是挂得最彻底的一次。那时我躺在床上，想了很多。我想起了我的爸妈，想起了我熬到深夜时他们固执的陪伴；想起了乐于助人的好大牛Ghost，半年来他在各个方面提供了我所能期望的最及时有效的帮助；想起了培训老师老赵在得知我按照现有规则省选必败时的操心；想起了周围朋友们的鼓励和支持，想起过了省选后几个同学在我身边笑着喊“得奖了请客！”……</p>
<p>然而一切都过去了。</p>
<p>这次的挂和我在准备方向、答题策略和个人能力等等方面都有关系。然而尘埃落定后我也无心再去细写。以后如果要比赛，也是完全不同的ACM/ICPC了。</p>
<p>这次Ivan也出人意料地挂掉了——他做前三年的题可是两银一金的。我不得不感叹比赛无常啊。但是闭幕晚会的时候和他坐在一起时，他自己还不停地安慰我。不知道是不是有什么寓意，当时晚会还安排了那首有些悲壮的“滚滚长江东逝水，浪花淘尽英雄”。听到这句我鼻子当时就有些酸了，和Ivan对望一下，感慨万千却又只能默默无言。之后他说了很多，具体的话语已经记不清了，但记得很清楚的是那哽咽的微笑，闪动在眼眶里的坚定，以及最后我们紧紧握在一起的手。</p>
<p>这次哑熊是最郁闷的了。当时他分数下来后，因为和去年xzj拿银的分差不多，我们都觉得拿个铜牌随便了，甚至当晚就进行了庆祝。谁知道今年的分数线高得惊人，他铜都没有拿到。当颁奖时念完铜的名单都没有他时，他一声不吭地坐在那里，脸上是无法掩饰地失望。后来知道，他离铜仅有1分，和去年莫名一样。他后来对我说，他是走的去年莫名的路，我是走的去年Ghost的路。“History will repeat itself again and again”，多么讽刺。</p>
<p>虽然挂了，但还是需要好好休息一下了。心太累了。人如果长期处于竞技状态毫无疑问是会疯掉的。然后就好好学习，同时准备NOIP。虽然NOI挂掉了，但是退而求其次，我觉得其实NOIP拿一等后再联系报送，或者直接去NUAA的ACM班也是个不错的选择。只是担心NOIP再rp一下，像去年鱼牛一样莫名其妙地挂掉。那我就别无选择，只能去高考了。</p>
<p>NOI结束了，生活和学习还要继续。不能因此颓废止步。就像Ghost说过的：“我们是失意者，不是失败者。”选择在OI之路上拼搏努力，我们不曾后悔。</p>
<p>最后摘我的QQ签名与各位失意的OIer共勉：</p>
<p style="text-align: center;">How can you be a loser if you never lose your pride?</p>
<p>P.S.其他一些照片可以看<a href="http://blog.sina.com.cn/s/blog_4c396f4301000ame.html" target="_blank"><span style="color: #0000ff;">哑熊的blog</span></a></p>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/07/noi-here-i-come/' rel='bookmark' title='NOI来了 该出发了'>NOI来了 该出发了</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ask-for-leave/' rel='bookmark' title='请假未遂'>请假未遂</a></li>
<li><a href='http://blog.tomtung.com/2007/05/determination/' rel='bookmark' title='决定了'>决定了</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/08/noi-fail/feed/</wfw:commentRss>
		<slash:comments>16</slash:comments>
		</item>
		<item>
		<title>NOI来了 该出发了</title>
		<link>http://blog.tomtung.com/2007/07/noi-here-i-come/</link>
		<comments>http://blog.tomtung.com/2007/07/noi-here-i-come/#comments</comments>
		<pubDate>Thu, 26 Jul 2007 07:47:41 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[NOI]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/07/noi%e6%9d%a5%e4%ba%86-%e6%88%91%e8%af%a5%e5%87%ba%e5%8f%91%e4%ba%86/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/07/noi-here-i-come/" title="NOI来了 该出发了"></a>嗯……明天一大早的飞机。 最近状态已经非常差了，干什么效率都很低。清风一天搞定了BST，Terro两天搞定了treap，这才叫效率。我呢？不说了…… 真的是累了，所以没了后劲。现在只希望能早点休息。只希望这次能侥幸混个铜，就可以好好休息一下了。当然更大的可能是拿个人人有份的优秀奖，回来再补落下课，还得准备NOIP。实在是不想这样了，真的累了。但是我现在的状态又怎么可能会赢呢。 其实我也不该奢望什么。OI仅仅一年不到，就已经亲历了WC和NOI，已经没什么可遗憾的了。 祝福自己，祝福所有人。 可能你对下面的文章也感兴趣： 开始全面复习 请假未遂 决定了<hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/07/preparing-for-noi/' rel='bookmark' title='开始全面复习'>开始全面复习</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ask-for-leave/' rel='bookmark' title='请假未遂'>请假未遂</a></li>
<li><a href='http://blog.tomtung.com/2007/05/determination/' rel='bookmark' title='决定了'>决定了</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/07/noi-here-i-come/" title="NOI来了 该出发了"></a><p>嗯……明天一大早的飞机。</p>
<p>最近状态已经非常差了，干什么效率都很低。清风一天搞定了BST，Terro两天搞定了treap，这才叫效率。我呢？不说了……</p>
<p>真的是累了，所以没了后劲。现在只希望能早点休息。只希望这次能侥幸混个铜，就可以好好休息一下了。当然更大的可能是拿个人人有份的优秀奖，回来再补落下课，还得准备NOIP。实在是不想这样了，真的累了。但是我现在的状态又怎么可能会赢呢。</p>
<p>其实我也不该奢望什么。OI仅仅一年不到，就已经亲历了WC和NOI，已经没什么可遗憾的了。</p>
<p>祝福自己，祝福所有人。</p>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/07/preparing-for-noi/' rel='bookmark' title='开始全面复习'>开始全面复习</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ask-for-leave/' rel='bookmark' title='请假未遂'>请假未遂</a></li>
<li><a href='http://blog.tomtung.com/2007/05/determination/' rel='bookmark' title='决定了'>决定了</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/07/noi-here-i-come/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>开始全面复习</title>
		<link>http://blog.tomtung.com/2007/07/preparing-for-noi/</link>
		<comments>http://blog.tomtung.com/2007/07/preparing-for-noi/#comments</comments>
		<pubDate>Sat, 21 Jul 2007 07:11:04 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[NOI]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/07/%e5%bc%80%e5%a7%8b%e5%85%a8%e9%9d%a2%e5%a4%8d%e4%b9%a0/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/07/preparing-for-noi/" title="开始全面复习"></a>离noi不到一个星期了，再不复习就来不及了 内容包括： 语言细节（包括stdlib库函数、C的输入输出格式、C++的输入输出格式、cstring库函数、string的各个成员函数、math库等） DP：包括原来做过的题目及各个经典模型（各种背包、LIS、LCS等等） 搜索：看原来做过的题目、例题 数据结构：包括BST（标准BST、SBST、VBST、SBT、ST）、heap（deap最后还是没学，所以需要复习怎样同时维护大小根两个堆）、hash（背背elf）、并查集（带hash和不带的、链表和森林表示） 基本图论：包括spfa、floyd、用栈的欧拉路求法、prim（heaped）、dfs、bfs、拓扑排序 NOI白痴笔试题 gdb调试 很多内容我都可以直接看自己的blog。精心维护一个OI主题的blog是很花时间的，但我想应该是利人（但愿如此）利己的好事。 从开始OI到现在还不到一年，数来数去也就学了这么点东西。一年前我是巨菜；现在我更加清楚地认识到了这一点。 祝福所有人。 可能你对下面的文章也感兴趣： 请假未遂 决定了 命运的冷笑话<hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/06/ask-for-leave/' rel='bookmark' title='请假未遂'>请假未遂</a></li>
<li><a href='http://blog.tomtung.com/2007/05/determination/' rel='bookmark' title='决定了'>决定了</a></li>
<li><a href='http://blog.tomtung.com/2007/05/black-humour-from-god/' rel='bookmark' title='命运的冷笑话'>命运的冷笑话</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/07/preparing-for-noi/" title="开始全面复习"></a><p>离noi不到一个星期了，再不复习就来不及了</p>
<p>内容包括：</p>
<ol>
<li> 语言细节（包括stdlib库函数、C的输入输出格式、C++的输入输出格式、cstring库函数、string的各个成员函数、math库等）</li>
<li> DP：包括原来做过的题目及各个经典模型（各种背包、LIS、LCS等等）</li>
<li>搜索：看原来做过的题目、例题</li>
<li> 数据结构：包括BST（标准BST、SBST、VBST、SBT、ST）、heap（deap最后还是没学，所以需要复习怎样同时维护大小根两个堆）、hash（背背elf）、并查集（带hash和不带的、链表和森林表示）</li>
<li> 基本图论：包括spfa、floyd、用栈的欧拉路求法、prim（heaped）、dfs、bfs、拓扑排序</li>
<li>NOI白痴笔试题</li>
<li>gdb调试</li>
</ol>
<p>很多内容我都可以直接看自己的blog。精心维护一个OI主题的blog是很花时间的，但我想应该是利人（但愿如此）利己的好事。</p>
<p>从开始OI到现在还不到一年，数来数去也就学了这么点东西。一年前我是巨菜；现在我更加清楚地认识到了这一点。</p>
<p>祝福所有人。</p>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/06/ask-for-leave/' rel='bookmark' title='请假未遂'>请假未遂</a></li>
<li><a href='http://blog.tomtung.com/2007/05/determination/' rel='bookmark' title='决定了'>决定了</a></li>
<li><a href='http://blog.tomtung.com/2007/05/black-humour-from-god/' rel='bookmark' title='命运的冷笑话'>命运的冷笑话</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/07/preparing-for-noi/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>[NOI 05]聪聪与可可</title>
		<link>http://blog.tomtung.com/2007/07/noi05-cckk/</link>
		<comments>http://blog.tomtung.com/2007/07/noi05-cckk/#comments</comments>
		<pubDate>Fri, 20 Jul 2007 13:07:41 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/07/noi-05%e8%81%aa%e8%81%aa%e4%b8%8e%e5%8f%af%e5%8f%af/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/07/noi05-cckk/" title="[NOI 05]聪聪与可可"></a>聪聪与可可 主文件名：cchkk 时限：1s 【问题描述】 在一个魔法森林里，住着一只聪明的小猫聪聪和一只可爱的小老鼠可可。虽然灰姑娘非常喜欢她们俩，但是，聪聪终究是一只猫，而可可终究是一只老鼠，同样不变的是，聪聪成天想着要吃掉可可。 一天，聪聪意外得到了一台非常有用的机器，据说是叫GPS，对可可能准确的定位。有了这台机器，聪聪要吃可可就易如反掌了。于是，聪聪准备马上出发，去找可可。而可怜的可可还不知道大难即将临头，仍在森林里无忧无虑的玩耍。 小兔子乖乖听到这件事，马上向灰姑娘报告。灰姑娘决定尽快阻止聪聪，拯救可可，可她不知道还有没有足够的时间。 整个森林可以认为是一个无向图，图中有N个美丽的景点，景点从1至N编号。小动物们都只在景点休息、玩耍。在景点之间有一些路连接。 当聪聪得到GPS时，可可正在景点M(M≤N)处。以后的每个时间单位，可可都会选择去相邻的景点(可能有多个)中的一个或停留在原景点不动。而去这些地方所发生的概率是相等的。假设有P个景点与景点M相邻，它们分别是景点R、景点S，……景点Q，在时刻T可可处在景点M，则在(T＋1)时刻，可可有1/(P+1)的可能在景点R，有1/(P+1)的可能在景点S，……，有1/(P+1)的可能在景点Q，还有1/(P+1)的可能停在景点M。 我们知道，聪聪是很聪明的，所以，当她在景点C时，她会选一个更靠近可可的景点，如果这样的景点有多个，她会选一个标号最小的景点。由于聪聪太想吃掉可可了，如果走完第一步以后仍然没吃到可可，她还可以在本段时间内再向可可走近一步。 在每个时间单位，假设聪聪先走，可可后走。在某一时刻，若聪聪和可可位于同一个景点，则可怜的可可就被吃掉了。 灰姑娘想知道，平均情况下，聪聪几步就可能吃到可可。而你需要帮助灰姑娘尽快的找到答案。 【输入格式】 从文件cchkk.in中读入数据。 数据的第1行为两个整数N和E，以空格分隔，分别表示森林中的景点数和连接相邻景点的路的条数。 第2行包含两个整数C和M，以空格分隔，分别表示初始时聪聪和可可所在的景点的编号。 接下来E行，每行两个整数，第i+2行的两个整数Ai和Bi表示景点Ai和景点Bi之间有一条路。 所有的路都是无向的，即：如果能从A走到B，就可以从B走到A。 输入保证任何两个景点之间不会有多于一条路直接相连，且聪聪和可可之间必有路直接或间接的相连。 【输出格式】 输出到文件cchkk.out中。 输出1个实数，四舍五入保留三位小数，表示平均多少个时间单位后聪聪会把可可吃掉。 【输入样例1】 4 3 1 4 1 2 2 3 3 4 【输出样例1】 1.500 【输入样例2】 9 9 9 3 &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/07/noi05-cckk/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/05/noi99-chess/' rel='bookmark' title='[NOI99]棋盘分割(Chessboard Division)'>[NOI99]棋盘分割(Chessboard Division)</a></li>
<li><a href='http://blog.tomtung.com/2007/05/noi97-game/' rel='bookmark' title='[NOI97]积木游戏(Game)'>[NOI97]积木游戏(Game)</a></li>
<li><a href='http://blog.tomtung.com/2007/05/poi-vi-musketeers/' rel='bookmark' title='[POI VI Stage 1 Problem 1] Musketeers'>[POI VI Stage 1 Problem 1] Musketeers</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/07/noi05-cckk/" title="[NOI 05]聪聪与可可"></a><h2 style="text-align: center;">聪聪与可可</h2>
<p style="text-align: center;">主文件名：cchkk</p>
<p style="text-align: center;">时限：1s</p>
<p>【问题描述】</p>
<p>在一个魔法森林里，住着一只聪明的小猫聪聪和一只可爱的小老鼠可可。虽然灰姑娘非常喜欢她们俩，但是，聪聪终究是一只猫，而可可终究是一只老鼠，同样不变的是，聪聪成天想着要吃掉可可。</p>
<p>一天，聪聪意外得到了一台非常有用的机器，据说是叫GPS，对可可能准确的定位。有了这台机器，聪聪要吃可可就易如反掌了。于是，聪聪准备马上出发，去找可可。而可怜的可可还不知道大难即将临头，仍在森林里无忧无虑的玩耍。</p>
<p>小兔子乖乖听到这件事，马上向灰姑娘报告。灰姑娘决定尽快阻止聪聪，拯救可可，可她不知道还有没有足够的时间。</p>
<p>整个森林可以认为是一个无向图，图中有N个美丽的景点，景点从1至N编号。小动物们都只在景点休息、玩耍。在景点之间有一些路连接。</p>
<p>当聪聪得到GPS时，可可正在景点M(M≤N)处。以后的每个时间单位，可可都会选择去相邻的景点(可能有多个)中的一个或停留在原景点不动。而去这些地方所发生的概率是相等的。假设有P个景点与景点M相邻，它们分别是景点R、景点S，……景点Q，在时刻T可可处在景点M，则在(T＋1)时刻，可可有1/(P+1)的可能在景点R，有1/(P+1)的可能在景点S，……，有1/(P+1)的可能在景点Q，还有1/(P+1)的可能停在景点M。</p>
<p>我们知道，聪聪是很聪明的，所以，当她在景点C时，她会选一个更靠近可可的景点，如果这样的景点有多个，她会选一个标号最小的景点。由于聪聪太想吃掉可可了，如果走完第一步以后仍然没吃到可可，她还可以在本段时间内再向可可走近一步。</p>
<p>在每个时间单位，假设聪聪先走，可可后走。在某一时刻，若聪聪和可可位于同一个景点，则可怜的可可就被吃掉了。</p>
<p>灰姑娘想知道，平均情况下，聪聪几步就可能吃到可可。而你需要帮助灰姑娘尽快的找到答案。</p>
<p>【输入格式】</p>
<p>从文件cchkk.in中读入数据。</p>
<p>数据的第1行为两个整数N和E，以空格分隔，分别表示森林中的景点数和连接相邻景点的路的条数。</p>
<p>第2行包含两个整数C和M，以空格分隔，分别表示初始时聪聪和可可所在的景点的编号。</p>
<p>接下来E行，每行两个整数，第i+2行的两个整数Ai和Bi表示景点Ai和景点Bi之间有一条路。</p>
<p>所有的路都是无向的，即：如果能从A走到B，就可以从B走到A。</p>
<p>输入保证任何两个景点之间不会有多于一条路直接相连，且聪聪和可可之间必有路直接或间接的相连。</p>
<p>【输出格式】</p>
<p>输出到文件cchkk.out中。</p>
<p>输出1个实数，四舍五入保留三位小数，表示平均多少个时间单位后聪聪会把可可吃掉。</p>
<p>【输入样例1】</p>
<pre> 4 3 1 4 1 2 2 3 3 4</pre>
<p>【输出样例1】</p>
<p>1.500</p>
<p>【输入样例2】</p>
<pre> 9 9 9 3 1 2 2 3 3 4 4 5 3 6 4 6 4 7 7 8 8 9</pre>
<p>【输出样例2】</p>
<p>2.167</p>
<p>【数据范围】</p>
<p>对于所有的数据，1≤N,E≤1000。</p>
<p>对于50%的数据，1≤N≤50。</p>
<p>【题解】</p>
<p>NOI还有这么简单的题么……引用Ivan的话：是人就会做。汗，这话说的我做出来以后一点成就感都没有。</p>
<p>唯一一点要注意的就是别让题目阴了。“灰姑娘想知道，平均情况下，聪聪几步就可能吃到可可。”按着这个做就挂了。一定得看清楚样例分析（我这没贴），否则怎么知道这个“步”是指回合而不是真的指具体的步呢。</p>
<p>要拿50分，floyd预处理+递归下降。要拿满分，bfs预处理+记忆化。我这里用的是spfa，纯是为了复习。好像spfa还快点（bfs我也写了）。</p>
<p>源码：</p>
<pre class="brush:c++">#include &lt;fstream&gt;
#include &lt;iomanip&gt;
using namespace std;
int V,E,C,K;//节点数、边数、聪聪和可可的初始位置
short neighbor[1001][1001];//[i][0]:节点i连接的边数;[i][j]:节点i的第j个邻居
unsigned short dist[1001][1001];//[i][j]:i、j两点间的最短距离
double dp[1001][1001];
bool walk(short &amp;c,short k){//让聪聪向可可走一步，并返回是否能吃到
    for(short j=1,c0=c,cc;j&lt;=neighbor[c0][0];j++){
        cc=neighbor[c0][j];
        if(dist[c][k]&gt;dist[cc][k]||(dist[c][k]==dist[cc][k]&amp;&amp;c&gt;cc))
            c=cc;
    }
    return c==k;
}
double memo(short c,short k){
    short c0=c;
    if(dp[c][k]&gt;0)    return dp[c][k];
    if(c==k)    return 0;
    if(walk(c,k)||walk(c,k))    return 1;//这句话诡异吧^^
    double ans=memo(c,k);
    for(short j=1;j&lt;=neighbor[k][0];j++)
        ans+=memo(c,neighbor[k][j]);
    ans/=(neighbor[k][0]+1);
    return dp[c0][k]=ans+1;
}
int main(){
    ifstream fin("cchkk.in");
    ofstream fout("cchkk.out");
    fin &gt;&gt; V &gt;&gt; E &gt;&gt; C &gt;&gt; K;
    for(int i,j,k=0;k&lt;E;k++){
        fin &gt;&gt; i &gt;&gt; j;
        neighbor[i][++neighbor[i][0]]=j;
        neighbor[j][++neighbor[j][0]]=i;
    }
    //用SPFA来预处理任意两点间的最短距离
    memset(dist,-1,sizeof(dist));
    for(int s=1;s&lt;=V;s++){
        short queue[1001],head=0,tail=1;
        bool inqueue[1001]={0};
        queue[0]=s,dist[s][s]=0;
        while(head&lt;tail){
            short u = queue[head++];
            inqueue[u]=false;
            if(head&gt;1000)    head=0;
            for(short j=1,v;j&lt;=neighbor[u][0];j++){
                v=neighbor[u][j];
                if(dist[s][v]&gt;dist[s][u]+1){
                    dist[s][v]=dist[s][u]+1;
                    if(!inqueue[v]){
                        queue[tail++]=v,inqueue[v]=true;
                        if(tail&gt;1000)    tail=0;
                    }
                }
            }
        }
    }
    fout &lt;&lt; fixed &lt;&lt; setprecision(3) &lt;&lt; memo(C,K) &lt;&lt; endl;
    return 0;
}
</pre>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/05/noi99-chess/' rel='bookmark' title='[NOI99]棋盘分割(Chessboard Division)'>[NOI99]棋盘分割(Chessboard Division)</a></li>
<li><a href='http://blog.tomtung.com/2007/05/noi97-game/' rel='bookmark' title='[NOI97]积木游戏(Game)'>[NOI97]积木游戏(Game)</a></li>
<li><a href='http://blog.tomtung.com/2007/05/poi-vi-musketeers/' rel='bookmark' title='[POI VI Stage 1 Problem 1] Musketeers'>[POI VI Stage 1 Problem 1] Musketeers</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/07/noi05-cckk/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[NOI06]生日快乐(using SBT)</title>
		<link>http://blog.tomtung.com/2007/07/noi06-happybirthday/</link>
		<comments>http://blog.tomtung.com/2007/07/noi06-happybirthday/#comments</comments>
		<pubDate>Tue, 17 Jul 2007 03:58:34 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[数据结构]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/07/noi06%e7%94%9f%e6%97%a5%e5%bf%ab%e4%b9%90/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/07/noi06-happybirthday/" title="[NOI06]生日快乐(using SBT)"></a>第二十三届全国信息学奥林匹克竞赛 NOI 2006 第一试 题目名称 生日快乐 可执行文件名 happybirthday 输入文件名 N/A 输出文件名 N/A 时限 4 秒 测试点数目 10 每测试点分值 10 是否有部分分 无 题目类型 交互 生日快乐 【任务描述】 今天是栋栋的生日，他邀请了N 个好友参加Party 。朋友们都知道，栋栋最喜欢吃果冻。因此, 每个朋友带来的生日礼物全是一包果冻.。 在每个朋友送他一包果冻的同时，栋栋还要这个朋友送他一个幸运号码L（1 ≤ L ≤ N）。然后栋栋会先把这包果冻放在一旁，并且把之前的所有果冻包按照果冻的数量从小到大排序（如果果冻数量相等，先后顺序任意）。接着，栋栋再把当前这包果冻插入到有序的果冻包队列中，使得这个队列仍然有序（如果存在其他的果冻包与该果冻包数量相等，则把该果冻包放在它们的前面）。完成这个操作后，栋栋就会进行如下操作： &#8220;如果这个朋友是男生，栋栋会从他送的包的后一个包开始向后数L 个（该朋友的幸运号码），从那个包里取出一个果冻，吃掉。 &#8220;如果这个朋友是女生，栋栋会从她送的包的前一个包开始向前数L 个（该朋友的幸运号码），从那个包里取出一个果冻，吃掉。 栋栋实在是太粗心了，以至于他收完所有的礼物后，都不知道吃过哪些朋友的果冻，现在，他希望你帮他一下，当他每吃一个果冻后马上告诉他可能吃的是谁送的(由于排序不是确定的，所以栋栋只要你给他一种可能的答案就行了)。 这是一个交互式的题目，你必须调用库函数来完成所有操作而不能访问任何文件。 &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/07/noi06-happybirthday/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/06/size-balanced-tree-in-cpp/' rel='bookmark' title='Size Balanced Tree in C++'>Size Balanced Tree in C++</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
<li><a href='http://blog.tomtung.com/2007/07/ceoi99-etc-parity-game/' rel='bookmark' title='[CEOI99][POJ1733][URAL1003][VIJOS1112]Parity Game'>[CEOI99][POJ1733][URAL1003][VIJOS1112]Parity Game</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/07/noi06-happybirthday/" title="[NOI06]生日快乐(using SBT)"></a><p style="text-align: center;">第二十三届全国信息学奥林匹克竞赛<br />
NOI 2006<br />
第一试</p>
<table style="width: 200px;" border="1" cellspacing="1" cellpadding="1" align="center">
<tbody>
<tr>
<td><span style="font-family: 宋体;">题目名称</span></td>
<td><span style="font-family: 宋体;">生日快乐</span></td>
</tr>
<tr>
<td><span style="font-family: 宋体;">可执行文件名</span></td>
<td><span style="font-family: 宋体;">happybirthday</span></td>
</tr>
<tr>
<td><span style="font-family: 宋体;">输入文件名</span></td>
<td><span style="font-family: 宋体;">N/A</span></td>
</tr>
<tr>
<td><span style="font-family: 宋体;">输出文件名</span></td>
<td><span style="font-family: 宋体;">N/A</span></td>
</tr>
<tr>
<td><span style="font-family: 宋体;">时限</span></td>
<td><span style="font-family: 宋体;">4 秒</span></td>
</tr>
<tr>
<td><span style="font-family: 宋体;">测试点数目</span></td>
<td><span style="font-family: 宋体;">10</span></td>
</tr>
<tr>
<td><span style="font-family: 宋体;">每测试点分值</span></td>
<td><span style="font-family: 宋体;">10</span></td>
</tr>
<tr>
<td><span style="font-family: 宋体;">是否有部分分</span></td>
<td><span style="font-family: 宋体;">无</span></td>
</tr>
<tr>
<td><span style="font-family: 宋体;">题目类型</span></td>
<td><span style="font-family: 宋体;">交互</span></td>
</tr>
</tbody>
</table>
<h3 style="text-align: center;">生日快乐</h3>
<p>【任务描述】</p>
<p>今天是栋栋的生日，他邀请了N 个好友参加Party 。朋友们都知道，栋栋最喜欢吃果冻。因此, 每个朋友带来的生日礼物全是一包果冻.。</p>
<p>在每个朋友送他一包果冻的同时，栋栋还要这个朋友送他一个幸运号码L（1 ≤ L ≤ N）。然后栋栋会先把这包果冻放在一旁，并且把之前的所有果冻包按照果冻的数量从小到大排序（如果果冻数量相等，先后顺序任意）。接着，栋栋再把当前这包果冻插入到有序的果冻包队列中，使得这个队列仍然有序（如果存在其他的果冻包与该果冻包数量相等，则把该果冻包放在它们的前面）。完成这个操作后，栋栋就会进行如下操作：</p>
<blockquote style="margin-right: 0px;" dir="ltr"><p>&#8220;如果这个朋友是男生，栋栋会从他送的包的后一个包开始向后数L 个（该朋友的幸运号码），从那个包里取出一个果冻，吃掉。</p>
<p>&#8220;如果这个朋友是女生，栋栋会从她送的包的前一个包开始向前数L 个（该朋友的幸运号码），从那个包里取出一个果冻，吃掉。</p></blockquote>
<p>栋栋实在是太粗心了，以至于他收完所有的礼物后，都不知道吃过哪些朋友的果冻，现在，他希望你帮他一下，当他每吃一个果冻后马上告诉他可能吃的是谁送的(由于排序不是确定的，所以栋栋只要你给他一种可能的答案就行了)。</p>
<p>这是一个交互式的题目，你必须调用库函数来完成所有操作而不能访问任何文件。</p>
<p>【数据规模和约定】</p>
<p>对于所有的数据，我们保证：1 ≤ n ≤ 500000，0 ≤ count ≤ 10^8 ，1 ≤ luckynumber ≤ n.在测试时，你的数据也应该满足我们的数据范围，否则有可能运行异常。</p>
<p>【题解】</p>
<p>平衡树的的典型应用。我这里对SBT(Size Balanced Tree)进行2处改写来做这题：首先是在Insert过程中返回插入节点的秩，这是一个顺便的改动，非常简单；再就是在Delete和Select的基础上改写出了SBT_Select_Delete，即选择指定秩的节点删除，并返回此节点。这个也不难，看看我的代码就知道了。只是注意，既然删除的节点要回收利用，那么从树中删除后就需要处理清楚size和ch[]域。</p>
<p>BTW，题目中给的count范围包括0，这是不负责任的。仔细想想就发现一旦出现这样的节点你就无法确定该如何处理了。当然了，评测时并没有给出这样的数据，否则鬼知道怎么处理呢。</p>
<p>时空开销：</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/noi06-happybirthday.png" src="http://upload.tomtung.com/img/noi06-happybirthday.png" alt="" width="420" height="333" /></p>
<p>代码：</p>
<pre class="brush:c++">#include "happybirthday_lib_c.cpp"
struct SBTNode{
	SBTNode *ch[2];
	long size, key, id;
	SBTNode(long _key, long _id, long _size);
}NIL=SBTNode(0,0,0);
SBTNode::SBTNode(long _key, long _id, long _size=1){
	key=_key,id=_id,size=_size;
	ch[0]=ch[1]=&amp;NIL;
}
typedef SBTNode* SBTree;
void SBT_Rotate(SBTree &amp;x, bool flag){
	SBTNode *y = x-&gt;ch[!flag];
	x-&gt;ch[!flag]=y-&gt;ch[flag],y-&gt;ch[flag]=x;
	y-&gt;size=x-&gt;size;
	x-&gt;size=x-&gt;ch[0]-&gt;size+x-&gt;ch[1]-&gt;size+1;
	x=y;
}
void SBT_Maintain(SBTree &amp;T,bool flag){
	if(T-&gt;ch[flag]-&gt;ch[flag]-&gt;size&gt;T-&gt;ch[!flag]-&gt;size)
		SBT_Rotate(T,!flag);
	else if(T-&gt;ch[flag]-&gt;ch[!flag]-&gt;size&gt;T-&gt;ch[!flag]-&gt;size)
		SBT_Rotate(T-&gt;ch[flag],flag),SBT_Rotate(T,!flag);
	else return;
	SBT_Maintain(T-&gt;ch[0],0),SBT_Maintain(T-&gt;ch[1],1);
	SBT_Maintain(T,0),SBT_Maintain(T,1);
}
long SBT_Insert(SBTree &amp;T, SBTNode *toAdd){
	if(T==&amp;NIL){
		T=toAdd;
		return 1;
	}else{
		T-&gt;size++;
		bool flag=toAdd-&gt;key&gt;T-&gt;key;
		long r=SBT_Insert(T-&gt;ch[flag],toAdd)+(flag?T-&gt;ch[0]-&gt;size+1:0);
		SBT_Maintain(T,flag);
		return r;
	}
}
SBTNode *SBT_Select_Delete(SBTree &amp;T,long i){
	if(T==&amp;NIL)	return &amp;NIL;
	T-&gt;size--;
	unsigned long r = T-&gt;ch[0]-&gt;size + 1;
	if(i==r){
		SBTNode *toDel = NULL;
		if(T-&gt;ch[0]==&amp;NIL||T-&gt;ch[1]==&amp;NIL){
			toDel=T;
			T=T-&gt;ch[T-&gt;ch[1]!=&amp;NIL];
		}else{
			toDel = SBT_Select_Delete(T-&gt;ch[1],1);
			swap(T-&gt;key,toDel-&gt;key),swap(T-&gt;id,toDel-&gt;id);
		}
		toDel-&gt;ch[0]=toDel-&gt;ch[1]=&amp;NIL;
		toDel-&gt;size=1;
		return toDel;
	}
	else return SBT_Select_Delete(T-&gt;ch[i&gt;r],i&gt;r?i-r:i);
}
int main(){
	long c,l,r,id=1; bool isboy;
	SBTNode *toEat; SBTree T=&amp;NIL;
	for(init();getpresent(c,l,isboy);id++){
		SBTNode *toAdd = new SBTNode(c,id);
		r = SBT_Insert(T,toAdd);
		if(isboy)	r+=l;
		else r-=l;
		if(r&lt;=0||r&gt;T-&gt;size) tell(-1);
		else{
			toEat = SBT_Select_Delete(T,r);
			tell(toEat-&gt;id);
			if(--toEat-&gt;key&gt;0)	SBT_Insert(T,toEat);
			else delete toEat;
		}
	}
	return 0;
}
</pre>
<p>P.S.有一件事情很郁闷，那就是……这题用不着平衡树-_-|||</p>
<p>因为数据都是random的，普通BST表现反倒会更好。。。（但是比赛时谁知道呢）</p>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/06/size-balanced-tree-in-cpp/' rel='bookmark' title='Size Balanced Tree in C++'>Size Balanced Tree in C++</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
<li><a href='http://blog.tomtung.com/2007/07/ceoi99-etc-parity-game/' rel='bookmark' title='[CEOI99][POJ1733][URAL1003][VIJOS1112]Parity Game'>[CEOI99][POJ1733][URAL1003][VIJOS1112]Parity Game</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/07/noi06-happybirthday/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>[CEOI99][POJ1733][URAL1003][VIJOS1112]Parity Game</title>
		<link>http://blog.tomtung.com/2007/07/ceoi99-etc-parity-game/</link>
		<comments>http://blog.tomtung.com/2007/07/ceoi99-etc-parity-game/#comments</comments>
		<pubDate>Thu, 12 Jul 2007 08:12:44 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[并查集]]></category>
		<category><![CDATA[数据结构]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/07/ceoi99pku1733ural1003vijos1112parity-game/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/07/ceoi99-etc-parity-game/" title="[CEOI99][POJ1733][URAL1003][VIJOS1112]Parity Game"></a>Parity Game Time Limit:1000MS Memory Limit:65536K Description Now and then you play the following game with your friend. Your friend writes down a sequence consisting of zeroes and ones. You choose a continuous subsequence (for example the subsequence from the &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/07/ceoi99-etc-parity-game/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/06/size-balanced-tree-in-cpp/' rel='bookmark' title='Size Balanced Tree in C++'>Size Balanced Tree in C++</a></li>
<li><a href='http://blog.tomtung.com/2007/07/noi01-eat/' rel='bookmark' title='[NOI 01][POJ1182] 食物链'>[NOI 01][POJ1182] 食物链</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/07/ceoi99-etc-parity-game/" title="[CEOI99][POJ1733][URAL1003][VIJOS1112]Parity Game"></a><h2 style="text-align: center;"><span style="color: #0080ff;">Parity Game</span></h2>
<p style="text-align: center;"><span>Time Limit:1000MS</span></p>
<p style="text-align: center;"><span>Memory Limit:65536K</span></p>
<h3><span style="color: #0080ff;">Description</span></h3>
<p><span>Now and then you play the following game with your friend. Your friend writes down a sequence consisting of zeroes and ones. You choose a continuous subsequence (for example the subsequence from the third to the fifth digit inclusively) and ask him, whether this subsequence contains even or odd number of ones. Your friend answers your question and you can ask him about another subsequence and so on. Your task is to guess the entire sequence of numbers.</span></p>
<p><span>You suspect some of your friend&#8217;s answers may not be correct and you want to convict him of falsehood. Thus you have decided to write a program to help you in this matter. The program will receive a series of your questions together with the answers you have received from your friend. The aim of this program is to find the first answer which is provably wrong, i.e. that there exists a sequence satisfying answers to all the previous questions, but no such sequence satisfies this answer.</span></p>
<h3><span style="color: #0080ff;">Input</span></h3>
<p><span>The first line of input contains one number, which is the length of the sequence of zeroes and ones. This length is less or equal to 1000000000. In the second line, there is one positive integer which is the number of questions asked and answers to them. The number of questions and answers is less or equal to 5000. The remaining lines specify questions and answers. Each line contains one question and the answer to this question: two integers (the position of the first and last digit in the chosen subsequence) and one word which is either &#8216;even&#8217; or &#8216;odd&#8217; (the answer, i.e. the parity of the number of ones in the chosen subsequence, where &#8216;even&#8217; means an even number of ones and &#8216;odd&#8217; means an odd number).</span></p>
<h3><span style="color: #0080ff;">Output</span></h3>
<p><span>There is only one line in output containing one integer X. Number X says that there exists a sequence of zeroes and ones satisfying first X parity conditions, but there exists none satisfying X+1 conditions. If there exists a sequence of zeroes and ones satisfying all the given conditions, then number X should be the number of all the questions asked.</span></p>
<h3><span> </span><span style="color: #0080ff;">Example</span></h3>
<h4><span style="color: #0080ff;">Example 1:</span></h4>
<p><span>PARITY.IN</span></p>
<blockquote style="margin-right: 0px;" dir="ltr"><p><span>10<br />
5<br />
1 2 even<br />
3 4 odd<br />
5 6 even<br />
1 6 even<br />
7 10 odd</span></p></blockquote>
<p><span> </span><span>PARITY.OUT</span></p>
<blockquote style="margin-right: 0px;" dir="ltr">
<blockquote style="margin-right: 0px;" dir="ltr"><p><span> </span><span>3</span></p></blockquote>
</blockquote>
<h4><span style="color: #0080ff;">Example 2:</span></h4>
<p style="margin-right: 0px;" dir="ltr"><span>PARITY.IN</span></p>
<blockquote style="margin-right: 0px;" dir="ltr"><p><span>10<br />
5<br />
1 2 even<br />
1 4 even<br />
2 4 odd<br />
1 10 even<br />
3 10 even</span><span> </span></p></blockquote>
<p><span> </span><span>PARITY.OUT</span></p>
<blockquote style="margin-right: 0px;" dir="ltr">
<blockquote style="margin-right: 0px;" dir="ltr"><p><span>5</span></p></blockquote>
</blockquote>
<h3><span style="color: #0080ff;">Solution</span></h3>
<p>挖个坑先……要是比赛中的话我怎么可能想到这题是并查集呢！</p>
<p>终于搞定。</p>
<p>首先有一步关键转化要做。我们同时处理一个范围内数字1的个数的奇偶比较麻烦。如果a到b之间有n个1，那么可以看成0到b之间1的个数减去0到a-1之间1的个数为n。我们假想知道了所有1的位置，那么0到任意i之间1的个数就是确定的。也就是说，通过这种转化，数据范围内每个数都对应了一个确定的数，它表示0到此数之间数字1的个数。</p>
<p>而我们事实上并不知道这些数确切是什么。我们知道的是一些数对之间奇偶性的关系。而这种关系是可以传递转移的。我们需要判断在给出关系的过程中是否出现矛盾。</p>
<p>于是就可以用并查集来解决这个问题了。它与我前面做过的<a href="http://blog.sina.com.cn/u/4a443fd7010009c2" target="_blank">食物链</a>相比，本质上是一样的，但是处理起来还简单。你可以参照那个题的题解来理解这个题。</p>
<p>除了这步转化以外，对我而言处理起来比较麻烦的就是离散化了。这题串最大长度为10亿，而仅仅给出最多5千条询问，明显不离散化是不行了。我用的Hash。Hash和并查集的联合我从前是没搞过的。</p>
<p>P.S.这题其实是个骗分的好题（ACMer们别打我。。。）。不离散化最少70分。如果内存放宽一点就有80分。即使奇偶搞反了都有50分……总之怎么着都能得分的。CEOI的标程好像不是用并查集做的，但是要慢一些。就不管它了。</p>
<p>时空开销：</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/parity-game-vijos.png" src="http://upload.tomtung.com/img/parity-game-vijos.png" alt="" width="603" height="325" /></p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/parity-game-poj.png" src="http://upload.tomtung.com/img/parity-game-poj.png" alt="" width="623" height="41" /></p>
<p>URAL的数据ms有问题，就再不管了。</p>
<p>源码：</p>
<pre class="brush:c++">#include &lt;fstream&gt;
#include &lt;string&gt;
using namespace std;
const long P=29989;
const string odd="odd";
long N,K;
struct elem{
	long key,rank;
	bool p_r;//false means same
	elem *next,*p;
	elem(long _key){key=_key,rank=0,p=this,p_r=false,next=NULL;}
}*set[P];
elem* Hash_Search(long key){
	elem* p=set[key%P];
	while(p&amp;&amp;p-&gt;key!=key)	p=p-&gt;next;
	return p;
}
void Hash_Insert(elem* toAdd){
	long h = toAdd-&gt;key%P;
	toAdd-&gt;next=set[h];
	set[h]=toAdd;
}
elem *Set_Find(elem* A, bool &amp;ch_r){
	if(A-&gt;p!=A){
		A-&gt;p=Set_Find(A-&gt;p,A-&gt;p_r);
		ch_r=(ch_r!=A-&gt;p_r);
	}
	return A-&gt;p;
}
bool istrue(long a,long b, bool flag){
	bool tmp=0;
	elem *A=Hash_Search(a),*B=Hash_Search(b);
	if(!A)	A = new elem(a),Hash_Insert(A);
	if(!B)	B = new elem(b),Hash_Insert(B);
	if(Set_Find(A,tmp)!=Set_Find(B,tmp)) return true;
	if(!flag) return A-&gt;p_r==B-&gt;p_r;
	else return A-&gt;p_r!=B-&gt;p_r;
}
void Set_Union(long a,long b, bool flag){
	elem *A=Hash_Search(a),*B=Hash_Search(b);
	if(!A)	A = new elem(a),Hash_Insert(A);
	else A=Set_Find(A,flag);
	if(!B)	B = new elem(b),Hash_Insert(B);
	else B=Set_Find(B,flag);
	if(A==B)	return;
	if(A-&gt;rank&lt;B-&gt;rank)	A-&gt;p=B,A-&gt;p_r=flag;
	else{
		B-&gt;p=A,B-&gt;p_r=flag;
		if(A-&gt;rank==B-&gt;rank)	A-&gt;rank++;
	}
}
int main(){
	ifstream fin("PARITY.IN");
	ofstream fout("PARITY.OUT");
	fin &gt;&gt; N &gt;&gt; K;
	unsigned long a,b,ans=0;
	string parity;
	for(ans=1;ans&lt;=K;ans++){
		fin &gt;&gt; a &gt;&gt; b &gt;&gt; parity;
		bool flag = parity==odd;//false means same
		if(istrue(a-1,b,flag))	Set_Union(a-1,b,flag);
		else break;
	}
	fout &lt;&lt; --ans &lt;&lt; endl;
	fin.close(),fout.close();
	return 0;
}
</pre>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/06/size-balanced-tree-in-cpp/' rel='bookmark' title='Size Balanced Tree in C++'>Size Balanced Tree in C++</a></li>
<li><a href='http://blog.tomtung.com/2007/07/noi01-eat/' rel='bookmark' title='[NOI 01][POJ1182] 食物链'>[NOI 01][POJ1182] 食物链</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/07/ceoi99-etc-parity-game/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>[vijos P1092] 全排列</title>
		<link>http://blog.tomtung.com/2007/07/vijos-p1092/</link>
		<comments>http://blog.tomtung.com/2007/07/vijos-p1092/#comments</comments>
		<pubDate>Tue, 10 Jul 2007 15:07:12 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[组合数学]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/07/vijos-p1092-%e5%85%a8%e6%8e%92%e5%88%97/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/07/vijos-p1092/" title="[vijos P1092] 全排列"></a>描述 Description 输入两个自然数m,n 1&#60;=n&#60;=20，1&#60;=m&#60;=n! 输出n个数的第m种全排列。 如 ： 输入 3 1 输出 1 2 3 输入格式 Input Format 在一行中输入n m 输出格式 Output Format 一个数列,既n个数的第m种排列 每两个数之间空1格 样例输入 Sample Input 3 2 样例输出 Sample Output 1 2 3 时间限制 Time Limitation 各个测试点1s 题解 &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/07/vijos-p1092/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/03/usaco-ordered-fractions/' rel='bookmark' title='[USACO]Ordered Fractions'>[USACO]Ordered Fractions</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ioi-01-mobile-phones/' rel='bookmark' title='[IOI 01] Mobile Phones'>[IOI 01] Mobile Phones</a></li>
<li><a href='http://blog.tomtung.com/2007/02/usaco-friday-the-13th/' rel='bookmark' title='[USACO]Friday the Thirteenth'>[USACO]Friday the Thirteenth</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/07/vijos-p1092/" title="[vijos P1092] 全排列"></a><p><span style="font-size: 12px;"><strong><span style="color: #ff9900;">描述 Description</span></strong></span></p>
<p><span style="font-size: 12px; color: #006666;">输入两个自然数m,n 1&lt;=n&lt;=20，1&lt;=m&lt;=n!</span></p>
<p><span style="font-size: 12px; color: #006666;">输出n个数的第m种全排列。</span></p>
<p><span style="font-size: 12px; color: #006666;">如 ：</span></p>
<p><span style="font-size: 12px; color: #006666;">输入 3 1</span></p>
<p><span style="font-size: 12px;"> </span><span style="color: #006666;">输出 1 2 3</span></p>
<p><span style="font-size: 12px; color: #ff9900;"><strong>输入格式 Input Format</strong></span></p>
<p><span style="font-size: 12px; color: #006666;">在一行中输入n m</span></p>
<p><span style="font-size: 12px; color: #ff9900;"><strong>输出格式 Output Format</strong></span></p>
<p><span style="font-size: 12px; color: #006666;">一个数列,既n个数的第m种排列</span></p>
<p><span style="font-size: 12px;"> </span><span style="color: #006666;">每两个数之间空1格</span></p>
<p><span style="font-size: 12px;"><strong><span style="color: #ff9900;">样例输入 Sample Input</span></strong></span></p>
<p>3 2</p>
<p><span style="font-size: 12px;"><strong><span style="color: #ff9900;">样例输出 Sample Output</span></strong></span><br />
1 2 3<br />
<span style="font-size: 12px;"><strong><span style="color: #ff9900;">时间限制 Time Limitation</span></strong></span></p>
<p><span style="font-size: 12px; color: #006666;">各个测试点1s</span></p>
<p><span style="font-size: 12px; color: #ff9900;"><strong>题解 Solution</strong></span></p>
<p><span style="font-size: 12px; color: #006666;">简单的康托展开应用。借以复习之。</span></p>
<pre class="brush:c++">#include &lt;iostream&gt;
using namespace std;
int main(){
	int n;
	long long m,f[21]=
{1LL,1LL,2LL,6LL,24LL,120LL,720LL,5040LL,40320LL,362880LL,3628800LL,39916800LL,
479001600LL,6227020800LL,87178291200LL,1307674368000LL,20922789888000LL,
355687428096000LL,6402373705728000LL,121645100408832000LL,2432902008176640000LL};
	bool num[20]={0};//[i]:mark for number i+1
	cin &gt;&gt; n &gt;&gt; m;
	m--;
	for(int i=n-1;i&gt;=0;m%=f[i--]){
		int j = m/f[i], k=-1;
		//cout &lt;&lt; m &lt;&lt; ' ' &lt;&lt; j &lt;&lt; endl;
		while(j&gt;=0)
			if(!num[++k])	j--;
		num[k]=true;
		cout &lt;&lt; k+1 &lt;&lt; ' ';
	}
	return 0;
}
</pre>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/03/usaco-ordered-fractions/' rel='bookmark' title='[USACO]Ordered Fractions'>[USACO]Ordered Fractions</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ioi-01-mobile-phones/' rel='bookmark' title='[IOI 01] Mobile Phones'>[IOI 01] Mobile Phones</a></li>
<li><a href='http://blog.tomtung.com/2007/02/usaco-friday-the-13th/' rel='bookmark' title='[USACO]Friday the Thirteenth'>[USACO]Friday the Thirteenth</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/07/vijos-p1092/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>[NOI 02] 银河英雄传说</title>
		<link>http://blog.tomtung.com/2007/07/noi02-galaxy/</link>
		<comments>http://blog.tomtung.com/2007/07/noi02-galaxy/#comments</comments>
		<pubDate>Mon, 09 Jul 2007 14:48:27 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[并查集]]></category>
		<category><![CDATA[数据结构]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/07/noi-02-%e9%93%b6%e6%b2%b3%e8%8b%b1%e9%9b%84%e4%bc%a0%e8%af%b4/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/07/noi02-galaxy/" title="[NOI 02] 银河英雄传说"></a>银河英雄传说 时限：2s 【问题描述】 公元五八○一年，地球居民迁移至金牛座α第二行星，在那里发表银河联邦创立宣言，同年改元为宇宙历元年，并开始向银河系深处拓展。 宇宙历七九九年，银河系的两大军事集团在巴米利恩星域爆发战争。泰山压顶集团派宇宙舰队司令莱因哈特率领十万余艘战舰出征，气吞山河集团点名将杨威利组织麾下三万艘战舰迎敌。 杨威利擅长排兵布阵，巧妙运用各种战术屡次以少胜多，难免恣生骄气。在这次决战中，他将巴米利恩星域战场划分成30000列，每列依次编号为1, 2, …, 30000。之后，他把自己的战舰也依次编号为1, 2, …, 30000，让第i号战舰处于第i列(i = 1, 2, …, 30000)，形成“一字长蛇阵”，诱敌深入。这是初始阵形。当进犯之敌到达时，杨威利会多次发布合并指令，将大部分战舰集中在某几列上，实施密集攻击。合并指令为M i j，含义为让第i号战舰所在的整个战舰队列，作为一个整体（头在前尾在后）接至第j号战舰所在的战舰队列的尾部。显然战舰队列是由处于同一列的一个或多个战舰组成的。合并指令的执行结果会使队列增大。 然而，老谋深算的莱因哈特早已在战略上取得了主动。在交战中，他可以通过庞大的情报网络随时监听杨威利的舰队调动指令。 在杨威利发布指令调动舰队的同时，莱因哈特为了及时了解当前杨威利的战舰分布情况，也会发出一些询问指令：C i j。该指令意思是，询问电脑，杨威利的第i号战舰与第j号战舰当前是否在同一列中，如果在同一列中，那么它们之间布置有多少战舰。 作为一个资深的高级程序设计员，你被要求编写程序分析杨威利的指令，以及回答莱因哈特的询问。 最终的决战已经展开，银河的历史又翻过了一页…… 【输入文件】 输入文件galaxy.in的第一行有一个整数T（1&#60;=T&#60;=500,000），表示总共有T条指令。 以下有T行，每行有一条指令。指令有两种格式： M i j ：i和j是两个整数（1&#60;=i , j&#60;=30000），表示指令涉及的战舰编号。该指令是莱因哈特窃听到的杨威利发布的舰队调动指令，并且保证第i号战舰与第j号战舰不在同一列。 C i j ：i和j是两个整数（1&#60;=i , j&#60;=30000），表示指令涉及的战舰编号。该指令是莱因哈特发布的询问指令。 &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/07/noi02-galaxy/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/07/noi01-eat/' rel='bookmark' title='[NOI 01][POJ1182] 食物链'>[NOI 01][POJ1182] 食物链</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ioi-01-mobile-phones/' rel='bookmark' title='[IOI 01] Mobile Phones'>[IOI 01] Mobile Phones</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/07/noi02-galaxy/" title="[NOI 02] 银河英雄传说"></a><h3 style="text-align: center;">银河英雄传说</h3>
<p style="text-align: center;">时限：2s</p>
<p>【问题描述】</p>
<p>公元五八○一年，地球居民迁移至金牛座α第二行星，在那里发表银河联邦创立宣言，同年改元为宇宙历元年，并开始向银河系深处拓展。</p>
<p>宇宙历七九九年，银河系的两大军事集团在巴米利恩星域爆发战争。泰山压顶集团派宇宙舰队司令莱因哈特率领十万余艘战舰出征，气吞山河集团点名将杨威利组织麾下三万艘战舰迎敌。</p>
<p>杨威利擅长排兵布阵，巧妙运用各种战术屡次以少胜多，难免恣生骄气。在这次决战中，他将巴米利恩星域战场划分成30000列，每列依次编号为1, 2, …, 30000。之后，他把自己的战舰也依次编号为1, 2, …, 30000，让第i号战舰处于第i列(i = 1, 2, …, 30000)，形成“一字长蛇阵”，诱敌深入。这是初始阵形。当进犯之敌到达时，杨威利会多次发布合并指令，将大部分战舰集中在某几列上，实施密集攻击。<span style="text-decoration: underline;">合并指令为M i j，含义为让第i号战舰所在的整个战舰队列，作为一个整体（头在前尾在后）接至第j号战舰所在的战舰队列的尾部。</span>显然战舰队列是由处于同一列的一个或多个战舰组成的。合并指令的执行结果会使队列增大。</p>
<p>然而，老谋深算的莱因哈特早已在战略上取得了主动。在交战中，他可以通过庞大的情报网络随时监听杨威利的舰队调动指令。</p>
<p>在杨威利发布指令调动舰队的同时，莱因哈特为了及时了解当前杨威利的战舰分布情况，也会发出一些询问指令：<span style="text-decoration: underline;">C i j。该指令意思是，询问电脑，杨威利的第i号战舰与第j号战舰当前是否在同一列中，如果在同一列中，那么它们之间布置有多少战舰。</span></p>
<p>作为一个资深的高级程序设计员，你被要求编写程序分析杨威利的指令，以及回答莱因哈特的询问。</p>
<p>最终的决战已经展开，银河的历史又翻过了一页……</p>
<p>【输入文件】</p>
<p>输入文件galaxy.in的第一行有一个整数T（1&lt;=T&lt;=500,000），表示总共有T条指令。</p>
<p>以下有T行，每行有一条指令。指令有两种格式：</p>
<ol style="margin-right: 0px;" dir="ltr">
<li>M i j ：i和j是两个整数（1&lt;=i , j&lt;=30000），表示指令涉及的战舰编号。该指令是莱因哈特窃听到的杨威利发布的舰队调动指令，并且保证第i号战舰与第j号战舰不在同一列。</li>
<li>C i j ：i和j是两个整数（1&lt;=i , j&lt;=30000），表示指令涉及的战舰编号。该指令是莱因哈特发布的询问指令。</li>
</ol>
<p>【输出文件】</p>
<p>输出文件为galaxy.out。你的程序应当依次对输入的每一条指令进行分析和处理：</p>
<p>如果是杨威利发布的舰队调动指令，则表示舰队排列发生了变化，你的程序要注意到这一点，但是不要输出任何信息；</p>
<p>如果是莱因哈特发布的询问指令，你的程序要输出一行，仅包含一个整数，表示在同一列上，第i号战舰与第j号战舰之间布置的战舰数目。如果第i号战舰与第j号战舰当前不在同一列上，则输出-1。</p>
<p>【题解】<br />
并查集。把每个战舰队列作为一个集合。两个战舰队列的合并就可以看作两个集合的合并。<br />
这里我们需要处理集合内元素间的关系：得到两个元素之间的元素个数。把所有元素按顺序储存需要的时候再数显然是不划算的。我们可以为每个节点维护一个dist域，记录此元素到到父节点的距离。显然如果要求两个元素i、j间的距离，我们需要先通过路径压缩使得他们的父节点统一为根，然后只要|dist[i]-dist[j]|-1就行了。<br />
但是在合并两个集合时怎么维护dist呢？我们这里再引入一个size域，仅当i为战舰队列的头头时才有效。它表示集合中元素的个数。这样，在做合并时，就很容易维护dist了。问题解决。</p>
<p>代码：</p>
<pre class="brush:c++">#include &lt;cstdio&gt;
#include &lt;cstdlib&gt;
using namespace std;
struct Set_elem{ int p,size,dist; }set[30000+1];
int Root(int i){
	if(set[i].p!=i){
		int root=Root(set[i].p);
		set[i].dist+=set[set[i].p].dist;
		set[i].p=root;
	}
	return set[i].p;
}
int main(){
	for(int i=1;i&lt;=30000;i++)	set[i].p=i,set[i].size=1;
	FILE *fin = fopen("galaxy.in","r"), *fout = fopen("galaxy.out","w");
	int T,i,j,ir,jr;	char oper;
	for(fscanf(fin,"%d\n",&amp;T);T&gt;0;T--){
		fscanf(fin,"%c %d %d\n",&amp;oper,&amp;i,&amp;j);
		ir=Root(i), jr=Root(j);
		switch(oper){
			case 'M':
				set[ir].p=jr;
				set[ir].dist=set[jr].size;
				set[jr].size+=set[ir].size;
				break;
			case 'C':
				fprintf(fout,"%d\n",ir==jr?abs(set[i].dist-set[j].dist)-1:-1);
		}
	}
	fclose(fin),fclose(fout);
	return 0;
}
</pre>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/07/noi01-eat/' rel='bookmark' title='[NOI 01][POJ1182] 食物链'>[NOI 01][POJ1182] 食物链</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ioi-01-mobile-phones/' rel='bookmark' title='[IOI 01] Mobile Phones'>[IOI 01] Mobile Phones</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/07/noi02-galaxy/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[NOI 01][POJ1182] 食物链</title>
		<link>http://blog.tomtung.com/2007/07/noi01-eat/</link>
		<comments>http://blog.tomtung.com/2007/07/noi01-eat/#comments</comments>
		<pubDate>Sun, 08 Jul 2007 14:21:28 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[并查集]]></category>
		<category><![CDATA[数据结构]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/07/noi-01pku-1182-%e9%a3%9f%e7%89%a9%e9%93%be-7-15-updated/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/07/noi01-eat/" title="[NOI 01][POJ1182] 食物链"></a>食物链 时限：3s 空间限制：64MB eat.pas/c/cpp 动物王国中有三类动物A,B,C，这三类动物的食物链构成了有趣的环形。A吃B， B吃C，C吃A。 现有N个动物，以1－N编号。每个动物都是A,B,C中的一种，但是我们并不知道它到底是哪一种。 有人用两种说法对这N个动物所构成的食物链关系进行描述： 第一种说法是“1 X Y”，表示X和Y是同类。 第二种说法是“2 X Y”，表示X吃Y。 此人对N个动物，用上述两种说法，一句接一句地说出K句话，这K句话有的是真的，有的是假的。当一句话满足下列三条之一时，这句话就是假话，否则就是真话。 1.当前的话与前面的某些真的话冲突，就是假话； 2.当前的话中X或Y比N大，就是假话； 3.当前的话表示X吃X，就是假话。 你的任务是根据给定的N（1&#60;=N&#60;=50,000）和K句话（0&#60;=K&#60;=100,000），输出假话的总数。 输入文件（eat.in） 第一行是两个整数N和K，以一个空格分隔。 以下K行每行是三个正整数 D，X，Y，两数之间用一个空格隔开，其中D表示说法的种类。 若D=1，则表示X和Y是同类。 若D=2，则表示X吃Y。 输出文件（eat.out） 只有一个整数，表示假话的数目。 输入样例 输入文件  对7句话的分析 100 7 1 101 1  假话 2 1 2  真话 2 2 3  真话 &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/07/noi01-eat/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
<li><a href='http://blog.tomtung.com/2007/06/size-balanced-tree-in-cpp/' rel='bookmark' title='Size Balanced Tree in C++'>Size Balanced Tree in C++</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ioi-01-mobile-phones/' rel='bookmark' title='[IOI 01] Mobile Phones'>[IOI 01] Mobile Phones</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/07/noi01-eat/" title="[NOI 01][POJ1182] 食物链"></a><p style="text-align: center;">
<p style="text-align: center;"><span style="font-size: 22px;"><strong>食物链</strong></span></p>
<p style="text-align: center;">时限：3s</p>
<p style="text-align: center;">空间限制：64MB</p>
<p style="text-align: center;">eat.pas/c/cpp</p>
<p>动物王国中有三类动物A,B,C，这三类动物的食物链构成了有趣的环形。A吃B， B吃C，C吃A。</p>
<p>现有N个动物，以1－N编号。每个动物都是A,B,C中的一种，但是我们并不知道它到底是哪一种。</p>
<p>有人用两种说法对这N个动物所构成的食物链关系进行描述：</p>
<p>第一种说法是“1 X Y”，表示X和Y是同类。</p>
<p>第二种说法是“2 X Y”，表示X吃Y。</p>
<p>此人对N个动物，用上述两种说法，一句接一句地说出K句话，这K句话有的是真的，有的是假的。当一句话满足下列三条之一时，这句话就是假话，否则就是真话。</p>
<p dir="ltr">
<p dir="ltr">1.当前的话与前面的某些真的话冲突，就是假话；</p>
<p dir="ltr">2.当前的话中X或Y比N大，就是假话；</p>
<p dir="ltr">3.当前的话表示X吃X，就是假话。</p>
<p dir="ltr">
<p>你的任务是根据给定的N（1&lt;=N&lt;=50,000）和K句话（0&lt;=K&lt;=100,000），输出假话的总数。</p>
<h3>输入文件（eat.in）</h3>
<p>第一行是两个整数N和K，以一个空格分隔。</p>
<p>以下K行每行是三个正整数 D，X，Y，两数之间用一个空格隔开，其中D表示说法的种类。</p>
<p>若D=1，则表示X和Y是同类。</p>
<p>若D=2，则表示X吃Y。</p>
<p>输出文件（eat.out）</p>
<p>只有一个整数，表示假话的数目。</p>
<h3>输入样例</h3>
<p><span style="font-family: 宋体;">输入文件  对7句话的分析<br />
100 7<br />
1 101 1  假话<br />
2 1 2  真话<br />
2 2 3  真话<br />
2 3 3   假话<br />
1 1 3  假话<br />
2 3 1  真话<br />
1 5 5  真话</span></p>
<h3>输出样例</h3>
<p>3</p>
<h3>题解</h3>
<p>郁闷死了……这本来是个弱题，我也想着两下搞定算了，结果从昨天下午搞到今晚才搞定。关系想不清楚就会越写越乱。</p>
<p>显然并查集。把已知的每一类动物看成一个集合。当我们得到（或推断出）两只动物是同类的信息时，我们需要合并它们所在的集合。</p>
<p>但是当我们处理捕食关系时怎么办呢？这就需要处理各个集合间的关系了。我这里使用了两个额外的域分别指向它的天敌和猎物集合中的一个元素（名字分别是killer和food……可见我英语很烂，给变量起名字也不在行）。每三个相关的集合构成一个关系组。我们就可以通过对这两个域的维护来处理一个集合与关系组内另外两个集合的关系了。</p>
<p>注意，我们不能一上来得到一组关系就说“好，那你就是A，你就是B吧”。因为我们在得到两个关系组中某两个集合的关系（包括同类和捕食）时需要合并两个关系组，而这时很明显会挂得很惨。我们只能维护各个关系组内的关系，在合并组时才不会出错。当关系组合并时，两个关系组将合二为一，我们就需要分别合并关系组内对应集合，并且维护它们的那两个额外的域。</p>
<p>在维护这两个域时需要想清楚，有没有漏掉的。不清楚就开写，只能像我一样越搞越乱。</p>
<p>我把文件IO改成stdio后交poj后得到的时空开销：</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/poj-1182-1.png" src="http://upload.tomtung.com/img/poj-1182-1.png" alt="" width="551" height="82" /></p>
<p>修改后还算清晰的代码：</p>
<pre class="brush:c++">#include &lt;cstdio&gt;
using namespace std;
struct Set{	long rank,p,food,killer; }set[50001];
long lie_sum,N,K;
long root(long x){
	if(set[x].p!=x)	set[x].p=root(set[x].p);
	return set[x].p;
}
long unite(long x, long y){
	x=root(x),y=root(y);
	if(x==y||x==0||y==0)	return x!=0?x:y;
	if(set[x].rank&gt;set[y].rank)	set[y].p=x;
	else{
		set[x].p=y;
		if(set[x].rank==set[y].rank)	set[y].rank++;
	}
	set[x].food=set[y].food=unite(set[x].food,set[y].food);
	set[x].killer=set[y].killer=unite(set[x].killer,set[y].killer);
	return set[x].p;
}
void eat(long x, long y){
	x=root(x),y=root(y);
	if(root(set[x].food)==y)	return;
	set[y].killer=unite(x,set[y].killer);
	set[x].food=unite(y,root(set[x].food));
	set[x].killer=set[y].food=unite(set[x].killer,set[y].food);
	long z=set[x].killer;
	if(z!=0){
		set[z].killer=y;
		set[z].food=x;
	}
}
bool istrue(long oper, long x, long y){
	if(x&gt;N||y&gt;N)	return false;
	x=root(x),y=root(y);
	long xk=root(set[x].killer),xf=root(set[x].food);
	switch(oper){
		case 1:	return(x==0||y==0||x==y||xk!=y&amp;&amp;xf!=y);
		case 2:	return(x!=y&amp;&amp;(xk==0||xk!=y));
	}
}
int main(){
	FILE *fin=fopen("eat.in","r"),*fout=fopen("eat.out","w");
	fscanf(fin,"%ld%ld",&amp;N,&amp;K);
	for(long i=1;i&lt;=N;i++)	set[i].p=i;
	for(long i=1,oper,x,y;i&lt;=K;i++){
		fscanf(fin,"%ld%ld%ld",&amp;oper,&amp;x,&amp;y);
		if(istrue(oper,x,y))
			switch(oper){
				case 1:	unite(x,y);	break;
				case 2:	eat(x,y);
			}
		else lie_sum++;
	}
	fprintf(fout,"%ld\n",lie_sum);
	fclose(fin),fclose(fout);
	return 0;
}
</pre>
<p>下面是另外一种解法，同样使用了并查集。这是我在思考<a href="http://blog.tomtung.com/2007/07/ceoi99-etc-parity-game/">Parity Game</a>时得到启发想出来的。我们上面处理三种动物关系时是通过处理各类动物组成的集合之间的关系来进行的。这种方法的实质其实是并查集“套”并查集。这种方法在关系较为简单时还有效，但如果关系较复杂呢？显然会因过于复杂而力不从心。</p>
<p>这就需要我下面要说的这种方法了。</p>
<p>其实很简单。只要稍稍变换一下思路就行了。我们避免处理集合间的关系，而是把它处理成集合内部的问题。我们把所有发生关系的动物看成一个集合，它们不需要是同类的。因为我们知道，这样形成的整个集合中各个动物间的关系都是密切相关的，任何一个动物身份的确定都会使得整个集合确定下来。我们为了维持这种关系，只需维护每个动物与其父节点之间的捕食关系就行了(通过简单维护一个p_r域实现)。而任意两个在同一集合中的动物都可以通过根节点联系起来。路径压缩也很好实现。当两个动物发生关系时，我们只需要合并两个集合就可以处理所有的关系了。就这么简单。</p>
<p>时空开销（都比第一种方法小）：</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/poj-1182-2.png" src="http://upload.tomtung.com/img/poj-1182-2.png" alt="" width="594" height="83" /></p>
<p>代码：</p>
<pre class="brush:c++">
//#define NDEBUG
#include &lt;iostream&gt;
#include &lt;cstdio&gt;
#include &lt;cstdlib&gt;
#include &lt;cassert&gt;
using namespace std;
const long max_n = 50000;
long N,K,lie_sum;
struct set_elem{
	long p,rank;
	long p_r;//the relationship between the node and its father
	//1 means equal, 2 mean it eat it's father,
	//3 means its father eats it
}set[max_n];
long Find(long x,long &amp;ch_r){
	if(set[x].p!=x){
		set[x].p=Find(set[x].p,set[x].p_r);
		if(ch_r==1)	ch_r=set[x].p_r;
		else if(ch_r!=set[x].p_r) ch_r=1;
		else ch_r=(ch_r==2?3:2);
	}
	return set[x].p;
}
void Union(long oper,long x,long y){
	//we should be sure that x and y are not in one same set
	assert(oper==1||oper==2||oper==3);
	y=Find(y,oper);
	if(oper!=1) oper=(oper==2?3:2);
	x=Find(x,oper);
	assert(x!=y);
	if(set[x].rank&gt;set[y].rank){
		set[y].p=x;
		set[y].p_r=oper;
	}else{
		set[x].p=y;
		if(oper!=1) oper=(oper==2?3:2);
		set[x].p_r=oper;
		if(set[x].rank==set[y].rank) set[y].rank++;
	}

}
int main(){
	FILE *fin = fopen("eat.in","r"),*fout = fopen("eat.out","w");
	fscanf(fin,"%ld%ld",&amp;N,&amp;K);
	for(int i=1;i&lt;=N;i++)	set[i].p=i; //initialize the set
	for(long i=1,oper,x,y,tmp;i&lt;=K;i++){
		fscanf(fin,"%ld%ld%ld",&amp;oper,&amp;x,&amp;y);
		if(x&gt;N||y&gt;N){
			//cout &lt;&lt; i &lt;&lt; endl;
			lie_sum++;
			continue;
		}
		tmp=1;Find(y,tmp);
		tmp=1;Find(x,tmp);
		if(set[x].p!=set[y].p) Union(oper,x,y);
		else if(oper==1&amp;&amp;set[x].p_r==set[y].p_r) continue;
		else if(oper==2&amp;&amp;
              (set[x].p_r==2&amp;&amp;set[y].p_r==1||
               set[x].p_r==1&amp;&amp;set[y].p_r==3||
               set[x].p_r==3&amp;&amp;set[y].p_r==2)
             ) continue;
		else {
			//cout &lt;&lt; i &lt;&lt; endl;
			lie_sum++;
		}
		/*
		if(istrue(oper,x,y))	Union(short(oper),x,y);
		else lie_sum++;
		*/
	}
	fprintf(fout,"%ld\n",lie_sum);
	fclose(fin),fclose(fout);
	system("pause");
	return 0;
}
</pre>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
<li><a href='http://blog.tomtung.com/2007/06/size-balanced-tree-in-cpp/' rel='bookmark' title='Size Balanced Tree in C++'>Size Balanced Tree in C++</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ioi-01-mobile-phones/' rel='bookmark' title='[IOI 01] Mobile Phones'>[IOI 01] Mobile Phones</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/07/noi01-eat/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[POI IV] Monochromatic triangles</title>
		<link>http://blog.tomtung.com/2007/07/poi-iv-monochromatic-triangles/</link>
		<comments>http://blog.tomtung.com/2007/07/poi-iv-monochromatic-triangles/#comments</comments>
		<pubDate>Thu, 05 Jul 2007 08:29:32 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[组合数学]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/07/poi-iv-monochromatic-triangles/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/07/poi-iv-monochromatic-triangles/" title="[POI IV] Monochromatic triangles"></a>IV Olimpiada Informatyczna 1996/1997 Task: TRO Author: Wojciech Guzicki Monochromatic triangles III stage contest Source file TRO.??? (e.g. PAS,C, CPP) Executable file TRO.EXE Input file TRO.IN Output file TRO.OUT There are n points given in a space. There are no &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/07/poi-iv-monochromatic-triangles/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/06/zoj1610-count-the-colors/' rel='bookmark' title='[ZOJ1610]Count the Colors'>[ZOJ1610]Count the Colors</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
<li><a href='http://blog.tomtung.com/2007/07/poi-vii-promotion/' rel='bookmark' title='[POI VII] Promotion'>[POI VII] Promotion</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/07/poi-iv-monochromatic-triangles/" title="[POI IV] Monochromatic triangles"></a><div>
<table border="0" width="90%">
<tbody>
<tr>
<td colspan="2" valign="center">
<h3 id="konkurs"><span>IV Olimpiada Informatyczna 1996/1997</span></h3>
</td>
</tr>
</tbody>
</table>
</div>
<div>
<hr /></div>
<div>
<table border="0" cellspacing="0" cellpadding="0" width="90%">
<tbody>
<tr>
<td align="left">
<div id="zid"><span style="font-size: 14px;"><strong>Task: TRO</strong></span></div>
</td>
<td>
<div id="autor"><span style="font-size: 14px;"><strong> Author: Wojciech Guzicki</strong></span></div>
</td>
</tr>
</tbody>
</table>
</div>
<div id="zadanie"><span style="font-size: 14px;"><strong>Monochromatic triangles</strong></span></div>
<div>
<hr /></div>
<div>
<table width="90%">
<tbody>
<tr>
<td id="etap"><span>III stage contest</span></td>
<td id="data" align="right"><span> </span></td>
</tr>
</tbody>
</table>
</div>
<div>
<table border="0">
<tbody>
<tr>
<td><span>Source file</span></td>
<td><span><tt>TRO.???</tt> (e.g. <tt>PAS</tt>,<tt>C</tt>, <tt>CPP</tt>)</span></td>
</tr>
<tr>
<td><span>Executable file</span></td>
<td><tt><span>TRO.EXE</span></tt></td>
</tr>
<tr>
<td><span>Input file</span></td>
<td><tt><span>TRO.IN</span></tt></td>
</tr>
<tr>
<td><span>Output file</span></td>
<td><tt><span>TRO.OUT</span></tt></td>
</tr>
</tbody>
</table>
</div>
<p><span>There are n points given in a space. There are no three points, such that they lie on the same straight line. Each pair of points is connected by a segment coloured red or black. Each triangle, whose sides have the same colour is called a <strong>monochromatic triangle</strong>. We are given a list of all red segments and we want to find the number of all monochromatic triangles.</span></p>
<h3><span>Task</span></h3>
<div><span>Write a program that:</span></div>
<ul>
<li><span>reads from the text file TRO.IN the following data: the number of points, the number of red segments and their list,</span></li>
<li><span>finds the number of monochromatic triangles,</span></li>
<li><span>writes the result to the text file TRO.OUT.</span></li>
</ul>
<h3><span>Input</span></h3>
<div><span>In the first line of the text file TRO.IN there is one integer n, 3 &lt;= n &lt;= 1000, which equals the number of points.</span></div>
<p><span>In the second line of the same file there is one integer m, 0 &lt;= m &lt;= 250000, which equals the number of red segments.</span></p>
<p><span>In each of the following m lines there are two integers p and k separated by a single space, 1 &lt;= p &lt; k &lt;= n. They are numbers of vertices which are ends of a red segment.</span></p>
<h3><span>Output</span></h3>
<div><span>In the first line of the text file TRO.OUT there should be written one integer &#8211; the number of monochromatic triangles.</span></div>
<h3><span>Example</span></h3>
<div><span>For the text file TRO.IN:</span></div>
<pre> <span>6  9 1 2 2 3 2 5 1 4 1 6 3 4 4 5 5 6 3 6</span></pre>
<div><span>the correct solution is the text file TRO.OUT</span></div>
<pre> <span>2</span></pre>
<h3><span>Solution</span></h3>
<p>按照ghost的建议开始看一些组合数学的东西了，挺难但是也很有趣啊~</p>
<p>这道经典题就作为我做这类题目的开始吧。</p>
<pre class="brush:c++">
#include &lt;cstdio&gt;
using namespace std;
int main(){
	FILE *fin = fopen("TRO.IN","r"),*fout = fopen("TRO.OUT","w");
	long n,//the number of points
		m,//the number of red segments
		red[1001]={0},//the number of red segments connected to each point
		mono_sum=0;//the sum of monochromatic triangles
	fscanf(fin,"%ld%ld",&amp;n,&amp;m);
	mono_sum=(n-2)*(n-1)*n/6;
	for(long p,k;m&gt;0;m--,red[p]++,red[k]++)	fscanf(fin,"%ld%ld",&amp;p,&amp;k);
	for(int i=1;i&lt;=n;i++)	mono_sum-=(red[i]*(n-red[i]-1)/2);
	fprintf(fout,"%ld\n",mono_sum);
	fclose(fin);fclose(fout);
	return 0;
}
</pre>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/06/zoj1610-count-the-colors/' rel='bookmark' title='[ZOJ1610]Count the Colors'>[ZOJ1610]Count the Colors</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
<li><a href='http://blog.tomtung.com/2007/07/poi-vii-promotion/' rel='bookmark' title='[POI VII] Promotion'>[POI VII] Promotion</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/07/poi-iv-monochromatic-triangles/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[POI VII] Promotion</title>
		<link>http://blog.tomtung.com/2007/07/poi-vii-promotion/</link>
		<comments>http://blog.tomtung.com/2007/07/poi-vii-promotion/#comments</comments>
		<pubDate>Tue, 03 Jul 2007 15:14:03 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[数据结构]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/07/poi-vii-promotion/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/07/poi-vii-promotion/" title="[POI VII] Promotion"></a>VII Olimpiada Informatyczna 1999/2000 Task: PRO Author: Tomasz Waleń Promotion III stage contest Great Bytelandish net of supermarkets asked you for writing a program simulating costs of the promotion being prepared. The promotion has to obey the following rules: A &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/07/poi-vii-promotion/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/02/usaco-mixing-milk/' rel='bookmark' title='[USACO]Mixing Milk'>[USACO]Mixing Milk</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
<li><a href='http://blog.tomtung.com/2007/06/zoj1610-count-the-colors/' rel='bookmark' title='[ZOJ1610]Count the Colors'>[ZOJ1610]Count the Colors</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/07/poi-vii-promotion/" title="[POI VII] Promotion"></a><div>
<table border="0" width="90%">
<tbody>
<tr>
<td colspan="2" valign="center">
<div id="konkurs"><span style="font-size: 18px;"><strong>VII Olimpiada Informatyczna 1999/2000</strong></span></div>
</td>
</tr>
</tbody>
</table>
</div>
<div>
<hr style="FONT-FAMILY:" /></div>
<div>
<table border="0" cellspacing="0" cellpadding="0" width="90%">
<tbody>
<tr>
<td align="left">
<div id="zid"><span><strong>Task: PRO</strong></span></div>
</td>
<td>
<div id="autor"><span><strong> Author: Tomasz Waleń</strong></span></div>
</td>
</tr>
</tbody>
</table>
</div>
<div id="zadanie"><span><strong>Promotion</strong></span></div>
<div>
<hr style="FONT-FAMILY:" /></div>
<div>
<table width="90%">
<tbody>
<tr>
<td id="etap"><span>III stage contest</span></td>
<td id="data" align="right"><span> </span></td>
</tr>
</tbody>
</table>
</div>
<p><span>Great Bytelandish net of supermarkets asked you for writing a program simulating costs of the promotion being prepared.</span></p>
<p><span>The promotion has to obey the following rules:</span></p>
<ul>
<li><span>A customer, who wants to participate in the promotion, writes on the bill, paid by himself, his personal details and throws it to a special ballot box.</span></li>
<li><span>At the end of every day of the promotion, two bills are taken out from the ballot box:</span>
<ul>
<li><span>the first bill amounting to the greatest sum is chosen,</span></li>
<li><span>then the bill amounting to the least sum is chosen;</span></li>
</ul>
<p><span>The customer, who has paid the greatest bill, gets a money prize equal to the difference between the sum on his bill and the sum on the bill amounting to the least sum.</span></li>
<li><span>To avoid multiple prizes for one purchase, both bills selected accordingly to the above rules, do not return to the ballot box, but all remaining bills still participate in promotion.</span></li>
</ul>
<p><span>Turnovers of the supermarket are very big, thus an assumption can be made, that at the end of every day, before taking out bills amounting to the greatest and the least sum, there are at least 2 bills in the ballot box.</span></p>
<p><span>Your task is to compute on the basis of information about prices on bills thrown to the ballot box on each day of promotion, what will be the total cost of prizes during the whole promotion.</span></p>
<h3><span>Task</span></h3>
<p><span>Write a program, which:</span></p>
<ul>
<li><span>reads from the text file <tt style="FONT-FAMILY:">PRO.IN</tt> a list of prices on bills thrown to the ballot box on each day of the promotion,</span></li>
<li><span>computes the total cost of prizes paid in consecutive days of promotion,</span></li>
<li><span>writes the result to the text file <tt style="FONT-FAMILY:">PRO.OUT</tt>.</span></li>
</ul>
<h3><span>Input</span></h3>
<p><span>The first line of the text file <tt style="FONT-FAMILY:">PRO.IN</tt> contains one positive integer <em style="FONT-FAMILY:">n</em>, where 1 &lt;= <em style="FONT-FAMILY:">n</em> &lt;= 5000, which is the duration of promotion in days.</span></p>
<p><span>Each of the next <em style="FONT-FAMILY:">n</em> lines consists of a sequence of non-negative integers separated by single spaces. Numbers in the <em style="FONT-FAMILY:">(i+1)</em>-th line of the file represent prices on bills thrown to the ballot box on the <em style="FONT-FAMILY:">i</em>-th day of promotion. The first integer in the line is <em style="FONT-FAMILY:">k</em>, 0 &lt;= <em style="FONT-FAMILY:">k</em> &lt;= 10^5, the number of bills from the day, and the next <em style="FONT-FAMILY:">k</em> numbers are positive integers standing for the prices on bills; none of these numbers is greater than <em style="FONT-FAMILY:">10^6</em>.</span></p>
<p><span>The total number of bills thrown to the ballot box during the whole promotion does not exceed <em style="FONT-FAMILY:">10^6</em>.</span></p>
<h3><span>Output</span></h3>
<p><span>The text file <tt style="FONT-FAMILY:">PRO.OUT</tt> should contain exactly one integer, which is equal to the total cost of prizes paid during the whole promotion.</span></p>
<h3><span>Example</span></h3>
<div><span>For the input file <tt style="FONT-FAMILY:">PRO.IN</tt>:</span></div>
<pre> <span>5 3 1 2 3 2 1 1 4 10 5 5 1 0 1 2</span></pre>
<pre> <span>the correct answer is the output file <tt style="FONT-FAMILY:">PRO.OUT</tt> </span></pre>
<pre> <span>19</span></pre>
<h3><span>Solution</span></h3>
<p>这是一个坑，为了催促我快点搞定这题……这破题已经搞了好几天了，已经写了5个版本，用了sbt、静态bst、堆，现在正在调试的是第6个版本，应该也是最后一个版本了……我的效率太低，太低……</p>
<p>没心再写了，反正最后已经不TLE了……</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/poi-vii-promotion.jpg" src="http://upload.tomtung.com/img/poi-vii-promotion.jpg" alt="" width="350" height="173" /></p>
<p>写了7遍还是没写过Ghost……但是还是学到了不少东西的。这题本来是出现在一个线段树论文中作为例题的，我就想，用bst不是更方便么。然后就写了个SBT，稍微改进了下Delete过程，然后就出现了1号夸张的结果。Ghost写了个treap比我快n多，发现原来是因为他合并了相同的节点。改进后效果非常明显，得到了2号代码。但是仍然不够快，就写了3号的静态BST，应该和线段树的速度差不多的，但仍然不够快。说明那个论文里说用线段树解是扯的……</p>
<p>CQF大牛说要用堆解这题。Ghost牛解释说要同时维护一个大根堆和一个小根堆，并通过维护指针来实现在从一个堆中取出最值后能在另一个堆中同时移除这个值。这样就可以同时支持取最大和最小了。据此我写出了4号代码。下面我解释下维护过程：</p>
<p>首先对指针的维护要确保对于一个堆中的节点能直接找到另一个堆中具有相同关键字的节点，它的实现其实很方便，这里就不说了。每次插入一个节点时，只要在每个堆中都像普通堆一样进行维护就行了，只是同时要维护指针而已。而取最值也不麻烦。比如要取最大值，对于大根堆的维护只是和平常一样就行了。至于小根堆，由于我们取走的是最大值，那它一定小于等于小根堆的最后一个节点，这样我们只要用减小小根堆中某节点值的方法来维护它就行了。取最小值则与此对称。这样我就写出了5号代码。</p>
<p>但还是不够快。Ghost的一个优化又是合并相同节点。由于数据范围不大，又都是整数而无需离散化，那直接就可以通过稍微修改一下指针域并加上计数域count来支持O(1)查找和计数修改。这样就得到了6号代码。可见合并相同节点是BST、Heap等ds中一种很有效的优化手段。它可以降低树的高度，减少节点数目，从而减少旋转次数、heapify次数等等。</p>
<p>剩下的就是纯粹的代码优化了。始终搞不过Ghost，郁闷……</p>
<p>代码不帖了。</p>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/02/usaco-mixing-milk/' rel='bookmark' title='[USACO]Mixing Milk'>[USACO]Mixing Milk</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
<li><a href='http://blog.tomtung.com/2007/06/zoj1610-count-the-colors/' rel='bookmark' title='[ZOJ1610]Count the Colors'>[ZOJ1610]Count the Colors</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/07/poi-vii-promotion/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[IOI 01] Mobile Phones</title>
		<link>http://blog.tomtung.com/2007/06/ioi-01-mobile-phones/</link>
		<comments>http://blog.tomtung.com/2007/06/ioi-01-mobile-phones/#comments</comments>
		<pubDate>Thu, 28 Jun 2007 08:13:03 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[数据结构]]></category>
		<category><![CDATA[虚二叉树]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/06/ioi-01-mobile-phones/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/06/ioi-01-mobile-phones/" title="[IOI 01] Mobile Phones"></a>IOI 2001 Tampere Finland DAY-1 mobiles Mobile phones PROBLEM Suppose that the fourth generation mobile phone base stations in the Tampere area operate as follows. The area is divided into squares. The squares form an S´S matrix with the rows &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/06/ioi-01-mobile-phones/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
<li><a href='http://blog.tomtung.com/2007/06/uva-258-mirror-maze/' rel='bookmark' title='[Uva #258] Mirror Maze'>[Uva #258] Mirror Maze</a></li>
<li><a href='http://blog.tomtung.com/2007/06/poi-ix-railways/' rel='bookmark' title='[POI IX] Railways'>[POI IX] Railways</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/06/ioi-01-mobile-phones/" title="[IOI 01] Mobile Phones"></a><div><span><span style="text-decoration: underline;"><a href="http://photo.blog.sina.com.cn/showpic.html#blogid=4a443fd70100094r&amp;url=http://static12.photo.sina.com.cn/orignal/4a443fd7e361e1374633b" target="_blank"></a></span></span></div>
<div style="text-align: center;"><span>IOI 2001 Tampere Finland<br />
DAY-1 mobiles</span></div>
<h1 style="text-align: center;"><span><a href="http://photo.blog.sina.com.cn/showpic.html#blogid=4a443fd70100094r&amp;url=http://static12.photo.sina.com.cn/orignal/4a443fd7e361e1374633b" target="_blank"></a></span><span> Mobile phones</span></h1>
<h3><span>PROBLEM</span></h3>
<p><span>Suppose that the fourth generation mobile phone base stations in the Tampere area operate as follows. The area is divided into squares. The squares form an S´S matrix with the rows and columns numbered from 0 to S-1. Each square contains a base station. The number of active mobile phones inside a square can change because a phone is moved from a square to another or a phone is switched on or off. At times, each base station reports the change in the number of active phones to the main base station along with the row and the column of the matrix.</span></p>
<p><span>Write a program, which receives these reports and answers queries about the current total number of active mobile phones in any rectangle-shaped area.<br />
</span></p>
<h3><span>INPUT AND OUTPUT</span></h3>
<p><span>The input is read from standard input as integers and the answers to the queries are written to standard output as integers. The input is encoded as follows. Each input comes on a separate line, and consists of one instruction integer and a number of parameter integers according to the following table.</span></p>
<p><span> </span></p>
<p><a href="http://upload.tomtung.com/img/ioi-01-mobile-phones-input.png"><img class="alignnone" title="http://upload.tomtung.com/img/ioi-01-mobile-phones-input.png" src="http://upload.tomtung.com/img/ioi-01-mobile-phones-input.png" alt="" width="475" height="168" /></a></p>
<p><span> <a href="file:///F:/My%20Documents/My%20Pictures/tmp/mobiles_io.PNG" target="_blank"></a><a href="file:///F:/My%20Documents/My%20Pictures/tmp/mobiles_io.PNG" target="_blank"></a><a href="file:///F:/My%20Documents/My%20Pictures/tmp/mobiles_io.PNG" target="_blank"></a></span></p>
<h3><span> </span><span>CONSTRAINTS</span></h3>
<p><img class="alignnone" title="http://upload.tomtung.com/img/ioi-01-mobile-phones-constrains.png" src="http://upload.tomtung.com/img/ioi-01-mobile-phones-constrains.png" alt="" width="380" height="146" /></p>
<p><span>Out of the 20 inputs, 16 are such that the table size is at most 512×512.</span></p>
<p><span> </span><span style="color: #ff0000;"><span style="text-decoration: underline;"><span><strong>NOTE:</strong> The web test facility feeds your input file to your program’s standard input.<br />
</span></span></span></p>
<h3><span>SOLUTION</span></h3>
<p>挺经典的一道数据结构。简单模拟肯定会挂，估计过一两个，但没试过；如果只对x维护二分树结构（我第一次就是这么做的），时间复杂度为O(nlogn)，能过6个点；只有对x和y都维护二分树结构才能AC。很郁闷的是我用的虚BST又比标程的树状数组慢。我的时间消耗（这个评测系统ms不支持卡空间）：</p>
<blockquote><p>Problem: F:\My Documents\My CPP Files\Other\mobile\test\data\mobile.ini<br />
mobiles_1.in = 5.0 (0.02s)<br />
mobiles_2.in = 5.0 (0.03s)<br />
mobiles_3.in = 5.0 (0.02s)<br />
mobiles_4.in = 5.0 (0.09s)<br />
mobiles_5.in = 5.0 (0.25s)<br />
mobiles_6.in = 5.0 (0.14s)<br />
mobiles_7.in = 5.0 (0.28s)<br />
mobiles_8.in = 5.0 (0.41s)<br />
mobiles_9.in = 5.0 (0.55s)<br />
mobiles_10.in = 5.0 (0.55s)<br />
mobiles_11.in = 5.0 (0.53s)<br />
mobiles_12.in = 5.0 (0.56s)<br />
mobiles_13.in = 5.0 (0.59s)<br />
mobiles_14.in = 5.0 (0.58s)<br />
mobiles_15.in = 5.0 (0.59s)<br />
mobiles_16.in = 5.0 (0.59s)<br />
mobiles_17.in = 5.0 (0.63s)<br />
mobiles_18.in = 5.0 (0.64s)<br />
mobiles_19.in = 5.0 (0.66s)<br />
mobiles_20.in = 5.0 (0.67s)</p>
<p>Score = 100.0<br />
TimeUsed = 8.38<br />
AllScore = 100.0</p>
<p>Summary:<br />
UserID = tomtung_modified</p>
<p>Score = 100.0/100.0<br />
TimeUsed = 8.38</p></blockquote>
<p>双重嵌套虚BST代码：</p>
<pre class="brush:c++">/*
PROG: mobiles
LANG: C++
*/
#include &lt;iostream&gt;
#include &lt;fstream&gt;
#include &lt;cstdio&gt;
#include &lt;algorithm&gt;
#define FILE_IO
using namespace std;
#ifdef FILE_IO
   FILE *fin = fopen("mobiles.in","r"),
      *fout = fopen("mobiles.out","w");
#else
   FILE *fin = stdin, *fout=stdout;
#endif
long S,Less[1024][1024];
long num[1024][1024];//This array is useful when debugging,
                                         //but doesn't affect the output
void Increase(long x, long y, long A){
   long lx=0,rx=S-1,nowx;
   while(1){
      nowx=((lx+rx)&gt;&gt;1);
      if(x&lt;=nowx){
         long ly=0,ry=S-1,nowy;
         while(1){
            nowy=((ly+ry)&gt;&gt;1);
            if(y&lt;=nowy)   Less[nowx][nowy]+=A;
            if(y==nowy)   break;
            else if(y&lt;nowy)   ry=nowy-1;
            else ly=nowy+1;
         }
      }
      if(x==nowx)   break;
      else if(x&lt;nowx)   rx=nowx-1;
      else lx=nowx+1;
   }
}
long Sum(long x, long y){
   if(x&lt;0||y&lt;0)   return 0;
   long ans=0;
   long lx=0,rx=S-1,nowx;
   while(1){
      nowx=((lx+rx)&gt;&gt;1);
      if(x&gt;=nowx){
         long ly=0,ry=S-1,nowy;
         while(1){
            nowy=((ly+ry)&gt;&gt;1);
            if(y&gt;=nowy)   ans+=Less[nowx][nowy];
            if(y==nowy)   break;
            else if(y&lt;nowy)   ry=nowy-1;
            else ly=nowy+1;
         }
      }
      if(x==nowx)   return ans;
      else if(x&lt;nowx)   rx=nowx-1;
      else lx=nowx+1;
   }
}
/*
void output_array(void){
   cout &lt;&lt; "nums:" &lt;&lt; endl;
   for(int i=0;i&lt;S;i++){
      for(int j=0;j&lt;S;j++)
         cout &lt;&lt; num[i][j];
      cout &lt;&lt; endl;
   }
   cout &lt;&lt; endl;
   /*
   cout &lt;&lt; "less:" &lt;&lt; endl;
   for(int i=0;i&lt;S;i++){
      for(int j=0;j&lt;S;j++)
         cout &lt;&lt; Less[i][j];
      cout &lt;&lt; endl;/
   cout &lt;&lt; "line_sum:" &lt;&lt; endl;
   for(int i=0;i&lt;S;i++){
      for(int j=0;j&lt;S;j++)
         cout &lt;&lt; line_sum(i,j);
      cout &lt;&lt; endl;
   }
   cout &lt;&lt;endl &lt;&lt; "------------------------------------------------" &lt;&lt; endl;
}
*/
int main()
{
   long i,p1,p2,p3,p4;
   while(1){
      fscanf(fin,"%ld ",&amp;i);
      //cout &lt;&lt; i &lt;&lt; ' ';
      switch(i){
         case 0:
            fscanf(fin,"%ld\n",&amp;S);
            //cout &lt;&lt; S &lt;&lt; endl;
            break;
         case 1:
            fscanf(fin,"%ld %ld %ld\n",&amp;p1,&amp;p2,&amp;p3);
            //cout &lt;&lt; p1 &lt;&lt; ' ' &lt;&lt; p2 &lt;&lt; ' ' &lt;&lt; p3 &lt;&lt; endl;
            Increase(p1,p2,p3);
            break;
         case 2:
            fscanf(fin,"%ld %ld %ld %ld\n",&amp;p1,&amp;p2,&amp;p3,&amp;p4);
            //cout &lt;&lt; p1 &lt;&lt; ' ' &lt;&lt; p2 &lt;&lt; ' ' &lt;&lt; p3 &lt;&lt; ' ' &lt;&lt; p4 &lt;&lt; endl;
            fprintf(fout,"%ld\n",Sum(p3,p4)-Sum(p3,p2-1)-Sum(p1-1,p4)+Sum(p1-1,p2-1));
            break;
         case 3:
            goto outloop;
      }
      //cout &lt;&lt; endl;output_array();
   }
   outloop:
   fflush (fout);
   fclose(fin);
   fclose(fout);
   return 0;
}
</pre>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
<li><a href='http://blog.tomtung.com/2007/06/uva-258-mirror-maze/' rel='bookmark' title='[Uva #258] Mirror Maze'>[Uva #258] Mirror Maze</a></li>
<li><a href='http://blog.tomtung.com/2007/06/poi-ix-railways/' rel='bookmark' title='[POI IX] Railways'>[POI IX] Railways</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/06/ioi-01-mobile-phones/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[POI IX] Railways</title>
		<link>http://blog.tomtung.com/2007/06/poi-ix-railways/</link>
		<comments>http://blog.tomtung.com/2007/06/poi-ix-railways/#comments</comments>
		<pubDate>Mon, 25 Jun 2007 05:20:46 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[数据结构]]></category>
		<category><![CDATA[线段树]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/06/poi-ix-railways/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/06/poi-ix-railways/" title="[POI IX] Railways"></a>IX Olimpiada Informatyczna 2001/2002 Task: kol Author: Tomasz Waleń Railways I stage contest Byteotian State Railways decided to keep up with the times and introduce to their offer an InterCity connection. Because of lack of efficient engines, clean carriages and &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/06/poi-ix-railways/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/06/zoj1610-count-the-colors/' rel='bookmark' title='[ZOJ1610]Count the Colors'>[ZOJ1610]Count the Colors</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
<li><a href='http://blog.tomtung.com/2007/06/size-balanced-tree-in-cpp/' rel='bookmark' title='Size Balanced Tree in C++'>Size Balanced Tree in C++</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/06/poi-ix-railways/" title="[POI IX] Railways"></a><div>
<table border="0" width="90%">
<tbody>
<tr>
<td colspan="2" valign="center">
<div id="konkurs"><span style="font-size: 18px;"><strong>IX Olimpiada Informatyczna 2001/2002</strong></span></div>
</td>
</tr>
</tbody>
</table>
<hr />
<table border="0" cellspacing="0" cellpadding="0" width="90%">
<tbody>
<tr>
<td align="left">
<div id="zid"><span style="font-size: 16px;"><strong>Task: kol</strong></span></div>
</td>
<td>
<div id="autor"><span style="font-size: 16px;"><strong> Author: Tomasz Waleń</strong></span></div>
</td>
</tr>
</tbody>
</table>
<div id="zadanie"><span style="font-size: 16px;"><strong>Railways</strong></span></div>
<hr />
<table width="90%">
<tbody>
<tr>
<td id="etap"><span>I stage contest</span></td>
<td id="data" align="right"><span> </span></td>
</tr>
</tbody>
</table>
<p><span>Byteotian State Railways decided to keep up with the times and introduce to their offer an InterCity connection. Because of lack of efficient engines, clean carriages and straight tracks it was possible to establish only one such a connection. The lack of any computer system for seat reservation was another obstacle. Writing the main part of this system is your task.</span></p>
<p><span>For simplicity we assume that the InterCity connection runs through <em>c</em> cities numbered successively from 1 to <em>c</em> (the starting city has the number 1, and <em>c</em> is the number of the ending city). There are <em>s</em> seats in the train and transporting more passengers between any two successive stations is not allowed.</span></p>
<p><span>The computer system is to receive successive requests and determine whether they may be fulfilled. A request is accepted when in the given section of the railway line there is enough vacant seats in the train. Otherwise it is rejected. Partial accepting of a request is not allowed, e.g. for a part of a route or for less passengers. After accepting a request, the number of vacant seats in the train is updated. The requests are processed successively in the order of coming.</span></p>
<h3><span>Task</span></h3>
<p><span>Write a program which:</span></p>
<ul>
<li><span>reads from the text file <tt>kol.in</tt> the description of the connection and a list of requested reservations,</span></li>
<li><span>computes which requests will be accepted, and which will be rejected,</span></li>
<li><span>writes to the text file <tt>kol.out</tt> the answers to all the requests.</span></li>
</ul>
<h3><span>Input</span></h3>
<p><span>In the first line of the text file <tt>kol.in</tt> there are three integers <em>c</em>, <em>s</em> and <em>r</em> (1&lt;=<em>c</em>&lt;=60 000, 1&lt;=<em>s</em>&lt;=60 000, 1&lt;=<em>r</em>&lt;=60 000) separated by single spaces. The numbers denote respectively: the number of cities on the railway line, the number of seats in the train, and the number of requests. In the following <em>r</em> lines there are consecutive requests written. In the line of number <em>i</em>+1 there is the <em>i</em>-th request described. The description consists of three integers <em>o</em>, <em>d</em> and <em>n</em> (1&lt;=<em>o</em>&lt;<em>d</em>&lt;=<em>c</em>, 1&lt;=<em>n</em>&lt;=<em>s</em>) separated by single spaces. They denote: the number of the station of origin, the number of the destination station and the requested number of seats, respectively.</span></p>
<h3><span>Output</span></h3>
<p><span>Your program should write <em>r</em> lines to the text file <tt>kol.out</tt>. In the <em>i</em>-th line there should be exactly one character:</span></p>
<ul>
<li><span><tt>T</tt> (for &#8220;yes&#8221;) &#8211; if the <em>i</em>-th request is accepted,</span></li>
<li><span><tt>N</tt> (for &#8220;no&#8221;) &#8211; otherwise.</span></li>
</ul>
</div>
<h3><span>Solution</span></h3>
<p><span style="text-decoration: line-through;">又是一道线段树。挖个坑先 ^_^</span></p>
<p>终于过了……反复不过竟然是因为数据用的太小了（unsigned short），后来改成long就AC了……一直觉得应该没问题啊，结果还是溢出了。看来像unsigned啊、short啊这种关键字还是慎用为妙。</p>
<p>从这个题可以学到线段树通过标记来减少不必要修改的思想——其实上一道zju的就是这样的。</p>
<p>代码：</p>
<pre class="brush:c++">
#include &lt;iostream&gt;
#include &lt;cstdlib&gt;
#include &lt;cstdio&gt;
#include &lt;cassert&gt;
using namespace std;
FILE *fin = fopen("kol.in","r"),
	*fout = fopen("kol.out","w");
const long A=1,B=60000;
long C,S,R;//城市数目、座位总数、请求数

struct STNode{
	long a,b,lch,rch,avail,sold;
	STNode(long _a, long _b){
		a=_a;
		b=_b;
		avail=S;
		sold=0;
		lch=rch=0;
	}
	STNode(void){}
}STree[(B-A)&lt;&lt;1];

void ST_Build(long a, long b){
	static unsigned i=1;
	unsigned now=i++;
	STree[now]=STNode(a,b);
	if(b-a&gt;1){
		STree[now].lch=i;
		ST_Build(a,(a+b)&gt;&gt;1);
		STree[now].rch=i;
		ST_Build((a+b)&gt;&gt;1,b);
	}
}

void ST_Clear(long i){	//清空节点i的sold域
	if(i==0)	return;
	STree[STree[i].lch].sold+=STree[i].sold;
	STree[STree[i].rch].sold+=STree[i].sold;
	STree[i].avail-=STree[i].sold;
	STree[i].sold=0;
}
bool ST_Check(long a, long b, long s, long i){
	if(STree[i].sold&gt;0)	ST_Clear(i);
	if(a&lt;=STree[i].a&amp;&amp;STree[i].b&lt;=b)	return (STree[i].avail&gt;=s);
	else{
		bool flag1=true,flag2=true;
		if(a&lt;((STree[i].a+STree[i].b)&gt;&gt;1))	flag1=ST_Check(a,b,s,STree[i].lch);
		if(b&gt;((STree[i].a+STree[i].b)&gt;&gt;1))	flag2=ST_Check(a,b,s,STree[i].rch);
		return (flag1&amp;&amp;flag2);
	}
}
void ST_Delete(long a,long b, long s, long i){
	if(STree[i].sold&gt;0)	ST_Clear(i);
	if(a&lt;=STree[i].a&amp;&amp;STree[i].b&lt;=b){
		assert(STree[i].avail&gt;=s);
		STree[i].avail-=s;
		STree[STree[i].lch].sold+=s;
		STree[STree[i].rch].sold+=s;
	}else{
		if(a&lt;((STree[i].a+STree[i].b)&gt;&gt;1))	ST_Delete(a,b,s,STree[i].lch);
		else	ST_Clear(STree[i].lch);
		if(b&gt;((STree[i].a+STree[i].b)&gt;&gt;1))	ST_Delete(a,b,s,STree[i].rch);
		else	ST_Clear(STree[i].rch);
		STree[i].avail=min(STree[STree[i].lch].avail,STree[STree[i].rch].avail);
	}
}

int main()
{
	fscanf(fin,"%d %d %d\n",&amp;C,&amp;S,&amp;R);
	ST_Build(A,B);
	for(int r=0,o,d,n;r&lt;R;r++){
		fscanf(fin,"%d %d %d\n",&amp;o,&amp;d,&amp;n);
		if(ST_Check(o,d,n,1)){
			fprintf(fout,"T\n");
			ST_Delete(o,d,n,1);
		}else	fprintf(fout,"N\n");
	}
	fclose(fin);
	fclose(fout);
	return 0;
}
</pre>
<p>P.S.不知道是不是我rp用完了，反正现在cena也不给我好好干活了。只要测pas的程序一律显示“崩溃（访问无效内存）”，重装cena、fp都没用，郁闷。就搞了两个评测系统，一个是HWD的，感觉很烦琐，不知道怎么评测C的程序；一个就是这个雅礼ZYF的系统，直接评测exe，好像不支持交互。下面是此程序生成的成绩单，我比标程慢……</p>
<blockquote>
<h1>成绩单</h1>
<div id="listing" style="cursor: auto;">
<h2><span style="font-size: 20px;"><strong>编号：</strong> standard； <strong>总得分：</strong> 130； <strong>总耗时：</strong> 1.21</span></h2>
<p><span style="font-size: 14px;"> </span><span style="font-size: 12px;"><strong>试题名称：</strong> Problem A； <strong>测试例数：</strong> 13； <strong>本题得分：</strong> 130.0； <strong>本题耗时：</strong> 1.21</span></p>
<table border="1">
<thead>
<tr>
<td width="100"><strong>例号</strong></td>
<td width="100"><strong>用时</strong></td>
<td width="500"><strong>评测结果</strong></td>
<td width="100"><strong>得分</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>0.02</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>2</td>
<td>0.02</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>3</td>
<td>0.02</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>4</td>
<td>0.02</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>5</td>
<td>0.02</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>6</td>
<td>0.36</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>7</td>
<td>0.03</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>8</td>
<td>0.08</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>9</td>
<td>0.02</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>10</td>
<td>0.09</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>11</td>
<td>0.11</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>12</td>
<td>0.03</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>13</td>
<td>0.39</td>
<td>正确</td>
<td>10</td>
</tr>
</tbody>
</table>
<hr />
<h2><span style="font-size: 16px;"> </span><span style="font-size: 18px;"> </span><span style="font-size: 20px;"><strong>编号：</strong> tomtung； <strong>总得分：</strong> 130； <strong>总耗时：</strong> 1.61</span></h2>
<p><span style="font-size: 12px;"><strong>试题名称：</strong> Problem A； <strong>测试例数：</strong> 13； <strong>本题得分：</strong> 130.0； <strong>本题耗时：</strong> 1.61</span></p>
<table border="1">
<thead>
<tr>
<td width="100"><strong>例号</strong></td>
<td width="100"><strong>用时</strong></td>
<td width="500"><strong>评测结果</strong></td>
<td width="100"><strong>得分</strong></td>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>0.03</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>2</td>
<td>0.03</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>3</td>
<td>0.03</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>4</td>
<td>0.03</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>5</td>
<td>0.03</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>6</td>
<td>0.44</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>7</td>
<td>0.05</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>8</td>
<td>0.09</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>9</td>
<td>0.09</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>10</td>
<td>0.09</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>11</td>
<td>0.16</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>12</td>
<td>0.06</td>
<td>正确</td>
<td>10</td>
</tr>
<tr>
<td>13</td>
<td>0.48</td>
<td>正确</td>
<td>10</td>
</tr>
</tbody>
</table>
</div>
</blockquote>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/06/zoj1610-count-the-colors/' rel='bookmark' title='[ZOJ1610]Count the Colors'>[ZOJ1610]Count the Colors</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
<li><a href='http://blog.tomtung.com/2007/06/size-balanced-tree-in-cpp/' rel='bookmark' title='Size Balanced Tree in C++'>Size Balanced Tree in C++</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/06/poi-ix-railways/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[ZOJ1610]Count the Colors</title>
		<link>http://blog.tomtung.com/2007/06/zoj1610-count-the-colors/</link>
		<comments>http://blog.tomtung.com/2007/06/zoj1610-count-the-colors/#comments</comments>
		<pubDate>Sun, 24 Jun 2007 14:37:27 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[数据结构]]></category>
		<category><![CDATA[线段树]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/06/zju1610count-the-colors/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/06/zoj1610-count-the-colors/" title="[ZOJ1610]Count the Colors"></a>Count the Colors Time limit: 1 Seconds   Memory limit: 32768K Total Submit: 2977   Accepted Submit: 1000 Painting some colored segments on a line, some previously painted segments may be covered by some the subsequent ones. Your task is counting the &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/06/zoj1610-count-the-colors/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1183-brackets-sequence/' rel='bookmark' title='[01ACM NEEuropean][URAL1183]Brackets sequence'>[01ACM NEEuropean][URAL1183]Brackets sequence</a></li>
<li><a href='http://blog.tomtung.com/2007/03/usaco-ordered-fractions/' rel='bookmark' title='[USACO]Ordered Fractions'>[USACO]Ordered Fractions</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/06/zoj1610-count-the-colors/" title="[ZOJ1610]Count the Colors"></a><h1 style="text-align: center;">Count the Colors</h1>
<div>
<hr /></div>
<p style="text-align: center;"><span> </span><span style="color: green;">Time limit:</span> 1 Seconds   <span style="color: green;">Memory limit:</span> 32768K<br />
<span style="color: green;">Total Submit:</span> 2977   <span style="color: green;">Accepted Submit:</span> 1000</p>
<div>
<hr /></div>
<div><span>Painting some colored segments on a line, some previously painted segments may be covered by some the subsequent ones.</span></div>
<p><span>Your task is counting the segments of different colors you can see at last.</span></p>
<p><span><strong>Input<br />
</strong><br />
The first line of each data set contains exactly one integer n, 1 &lt;= n &lt;= 8000, equal to the number of colored segments.</span></p>
<p><span>Each of the following n lines consists of exactly 3 nonnegative integers separated by single spaces:</span></p>
<p><span> x1 x2 c</span></p>
<p><span> x1 and x2 indicate the left endpoint and right endpoint of the segment, c indicates the color of the segment.</span></p>
<p><span>All the numbers are in the range [0, 8000], and they are all integers.</span></p>
<p><span>Input may contain several data set, process to the end of file.</span></p>
<p><span><strong>Output</strong></span></p>
<p><span> Each line of the output should contain a color index that can be seen from the top, following the count of the segments of this color, they should be printed according to the color index.</span></p>
<p><span>If some color can&#8217;t be seen, you shouldn&#8217;t print it.</span></p>
<p><span>Print a blank line after every dataset.</span></p>
<p><span><strong>Author:</strong> Standlove</span></p>
<p><span><strong>Source:</strong> <em>ZOJ Monthly, May 2003</em></span></p>
<p><span> </span></p>
<p><span> </span></p>
<p><strong><span>Solution:</span></strong></p>
<p>第一个用线段树过的题目啊~~呵呵。基础题目，虽然对我来说并不简单。在处理最后数线段条数的问题上颇费了些周折，先是问大牛，结果没搞懂，就自力更生地写出来了。。。</p>
<p>话说特别巧，我刚好是第1000个过这道题的人，哈哈</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/zoj1610-1.jpg" src="http://upload.tomtung.com/img/zoj1610-1.jpg" alt="" width="379" height="242" /></p>
<p>我的时空消耗：</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/zoj1610-2.jpg" src="http://upload.tomtung.com/img/zoj1610-2.jpg" alt="" width="591" height="252" /></p>
<p>代码（发现我越来越不爱写注释了）：</p>
<pre class="brush:c++">
#include &lt;cstdio&gt;
#include &lt;cassert&gt;
#include &lt;cstring&gt;
#define NDEBUG
//#define FILE_IO
using namespace std;
#ifdef FILE_IO
	FILE *fin = fopen("INPUT.TXT","r");
	FILE *fout= fopen("OUTPUT.TXT","w");
#else
	FILE *fin = stdin;
	FILE *fout= stdout;
#endif
const unsigned short min_a=0, max_b=8000;
unsigned short N;
struct STNode{
	unsigned short a,b,lch,rch;
	short color;	//-1为无色，-2为杂色
	STNode(unsigned short __a, unsigned short __b){
		a=__a;
		b=__b;
		color=-1;
		lch=rch=0;
	}
	STNode(void){;}
};
STNode STree[(max_b-min_a)*2];
unsigned short i;
void ST_Build(unsigned short a, unsigned short b){
	unsigned now=i++;
	STree[now]=STNode(a,b);
	if(b-a&gt;1){
		STree[now].lch=i;
		ST_Build(a,(a+b)&gt;&gt;1);
		STree[now].rch=i;
		ST_Build((a+b)&gt;&gt;1,b);
	}
}
void ST_Insert(unsigned short i,unsigned short a, unsigned short b, short color){
	assert(a&lt;b);
	if(STree[i].color==color)	return;
	if(a&lt;=STree[i].a&amp;&amp;STree[i].b&lt;=b)	STree[i].color=color;
	else{
		if(STree[i].color!=-2&amp;&amp;STree[i].color!=-1){
			STree[STree[i].lch].color=STree[i].color;
			STree[STree[i].rch].color=STree[i].color;
		}
		STree[i].color=-2;
		unsigned short m=((STree[i].a+STree[i].b)&gt;&gt;1);
		if(a&lt;m)	ST_Insert(STree[i].lch,a,b,color);
		if(b&gt;m)	ST_Insert(STree[i].rch,a,b,color);
	}
}
unsigned colors[8001];
void SgCount(unsigned short i, short &amp;a_color, short &amp;b_color){
	//在在color中增加以i节点为根的ST中各色的线段数目
	//并返回最左、最右端的颜色分别为a_color和b_color
	if(STree[i].color!=-2){
		a_color=b_color=STree[i].color;
		if(STree[i].color!=-1)	colors[STree[i].color]++;
	}
	else{
		short m1_color,m2_color;
		SgCount(STree[i].lch,a_color,m1_color);
		SgCount(STree[i].rch,m2_color,b_color);
		if(m1_color==m2_color&amp;&amp;m1_color!=-1)
			colors[m1_color]--;
	}
}
int main()
{
	while(fscanf(fin,"%d\n",&amp;N)==1){
		i=1;
		ST_Build(min_a,max_b);
		for(int i=0,a,b,color;i&lt;N;i++){
			fscanf(fin,"%d %d %d\n",&amp;a,&amp;b,&amp;color);
			ST_Insert(1,a,b,color);
		}
		memset(colors,'\0',sizeof(colors));
		short a_color,b_color;
		SgCount(1,a_color,b_color);
		for(int i=0;i&lt;=8000;i++)
			if(colors[i]!=0)	fprintf(fout,"%d %d\n",i,colors[i]);
		fprintf(fout,"\n");
	}
	return 0;
}
</pre>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/06/ural1028-stars/' rel='bookmark' title='[URAL1028]Stars (Using SBT, SBST, VBST)'>[URAL1028]Stars (Using SBT, SBST, VBST)</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1183-brackets-sequence/' rel='bookmark' title='[01ACM NEEuropean][URAL1183]Brackets sequence'>[01ACM NEEuropean][URAL1183]Brackets sequence</a></li>
<li><a href='http://blog.tomtung.com/2007/03/usaco-ordered-fractions/' rel='bookmark' title='[USACO]Ordered Fractions'>[USACO]Ordered Fractions</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/06/zoj1610-count-the-colors/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[URAL1028]Stars (Using SBT, SBST, VBST)</title>
		<link>http://blog.tomtung.com/2007/06/ural1028-stars/</link>
		<comments>http://blog.tomtung.com/2007/06/ural1028-stars/#comments</comments>
		<pubDate>Sat, 23 Jun 2007 16:24:24 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[Size Balanced Tree]]></category>
		<category><![CDATA[数据结构]]></category>
		<category><![CDATA[虚二叉树]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/06/ural1028stars-using-sbt-sbst-vbst/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/06/ural1028-stars/" title="[URAL1028]Stars (Using SBT, SBST, VBST)"></a>1028. Stars Time Limit: 0.25 second Memory Limit: 16 MB Astronomers often examine star maps where stars are represented by points on a plane and each star has Cartesian coordinates. Let the level of a star be an amount of &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/06/ural1028-stars/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/06/size-balanced-tree-in-cpp/' rel='bookmark' title='Size Balanced Tree in C++'>Size Balanced Tree in C++</a></li>
<li><a href='http://blog.tomtung.com/2007/05/noi99-chess/' rel='bookmark' title='[NOI99]棋盘分割(Chessboard Division)'>[NOI99]棋盘分割(Chessboard Division)</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1183-brackets-sequence/' rel='bookmark' title='[01ACM NEEuropean][URAL1183]Brackets sequence'>[01ACM NEEuropean][URAL1183]Brackets sequence</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/06/ural1028-stars/" title="[URAL1028]Stars (Using SBT, SBST, VBST)"></a><p style="text-align: center;"><span style="font-size: 24px; color: #0080ff;">1028. Stars</span></p>
<p style="text-align: center;"><span>Time Limit: 0.25 second<br />
Memory Limit: 16 MB<br />
</span></p>
<p><span>Astronomers often examine star maps where stars are represented by points on a plane and each star has Cartesian coordinates. Let the level of a star be an amount of the stars that are not higher and not to the right of the given star. Astronomers want to know the distribution of the levels of the stars.</span></p>
<p style="text-align: center;"><span><img class="aligncenter" title="http://upload.tomtung.com/img/ural1028-stars.png" src="http://upload.tomtung.com/img/ural1028-stars.png" alt="" width="191" height="147" /><br />
</span></p>
<p><span>For example, look at the map shown on the figure above. Level of the star number 5 is equal to 3 (it&#8217;s formed by three stars with a numbers 1, 2 and 4). And the levels of the stars numbered by 2 and 4 are 1. At this map there are only one star of the level 0, two stars of the level 1, one star of the level 2, and one star of the level 3.</span></p>
<p><span> You are to write a program that will count the amounts of the stars of each level on a given map.</span></p>
<h3 class="subtitle"><span style="color: #0080ff;">Input</span></h3>
<p><span>The first line of the input contains a number of stars <em>N</em> (1 ≤ <em>N</em> ≤ 15000). The following <em>N</em> lines describe coordinates of stars (two integers <em>X</em> and <em>Y</em> per line separated by a space, 0 ≤ <em>X</em>,<em>Y</em> ≤ 32000). There can be only one star at one point of the plane. Stars are listed in ascending order of <em>Y</em> coordinate. Stars with equal <em>Y</em> coordinates are listed in ascending order of <em>X</em> coordinate.</span></p>
<h3 class="subtitle"><span style="color: #0080ff;">Output</span></h3>
<p><span>The output should contain <em>N</em> lines, one number per line. The first line contains amount of stars of the level 0, the second does amount of stars of the level 1 and so on, the last line contains amount of stars of the level <em>N</em></span></p>
<h3 class="subtitle"><span style="color: #0080ff;">Solution</span></h3>
<p class="subtitle">挺不错的一道用来练DS的题：很多DS都能用。我这里用了SBT(Size Balanced Tree)、静态BST和虚BST。树状数组也可以做，但是我不会~~~ 本来看好多人都说用线段树的也想试试，但是发现ST写起来无论在时空消耗还是编程复杂度上都没有优势……莫非是我搞垃圾了？哪位帮忙放个ST让我瞻仰下，多谢……</p>
<p>下面是代码。没有做速度的比较，因为没有可比性：SBT用了动态储存，VBST用了排序，SBST则是直接建树。这样速度无法体现DS的优劣，比较就没有必要了。</p>
<p>SBT版（与普通SBT不同，旋转和插入都需要维护less）：</p>
<pre class="brush:c++">/*
  Name: URAL#1028. Stars
  Author: 巨菜逆铭(Tom Tung)
  Date: 20-06-07 16:50
  Description: Using Size Balanced Tree
*/
#include &lt;cstdlib&gt;
#include &lt;cstdio&gt;
#include &lt;cassert&gt;
#define NDEBUG
#ifndef NDEBUG
  FILE *fin=fopen("INPUT.TXT","r");
  FILE *fout=fopen("OUTPUT.TXT","w");
#else
  FILE *fin=stdin;
  FILE *fout=stdout;
#endif
using namespace std;
struct SBTNode{
	SBTNode *ch[2];
	long key;
	unsigned long size,less;
	SBTNode(long _key,unsigned long _size);
}NIL=SBTNode(0,0);
typedef SBTNode *SBTree;
SBTNode::SBTNode(long _key,unsigned long _size=1){
	ch[0]=ch[1]=&amp;NIL;
	size=_size;
	key=_key;
	less=1;
}
inline void SBT_Rotate(SBTree &amp;x, bool flag){
	SBTNode *y=x-&gt;ch[!flag];
	assert(y!=&amp;NIL&amp;&amp;x!=&amp;NIL);
	x-&gt;ch[!flag]=y-&gt;ch[flag];
	y-&gt;ch[flag]=x;
	y-&gt;size=x-&gt;size;
	x-&gt;size=x-&gt;ch[0]-&gt;size+x-&gt;ch[1]-&gt;size+1;
	if(!flag)	y-&gt;less+=x-&gt;less;
	else	x-&gt;less-=y-&gt;less;
	assert(x-&gt;less&gt;0);
	x=y;
}
void SBT_Maintain(SBTree &amp;T,bool flag){
	if(T-&gt;ch[flag]-&gt;ch[flag]-&gt;size&gt;T-&gt;ch[!flag]-&gt;size)
		SBT_Rotate(T,!flag);
	else if(T-&gt;ch[flag]-&gt;ch[!flag]-&gt;size&gt;T-&gt;ch[!flag]-&gt;size){
		SBT_Rotate(T-&gt;ch[flag],flag);
		SBT_Rotate(T,!flag);
	}
	else return;
	SBT_Maintain(T-&gt;ch[0],0);
	SBT_Maintain(T-&gt;ch[1],1);
	SBT_Maintain(T,0);
	SBT_Maintain(T,1);
}

unsigned lev;
void SBT_Insert(SBTree &amp;T, long key){
	if(T==&amp;NIL){
		T=new SBTNode(key);
		return;
	}
	if(key&gt;=T-&gt;key)	lev+=(T-&gt;less);
	if(key&lt;=T-&gt;key)	T-&gt;less++;
	if(key==T-&gt;key)	return;
	T-&gt;size++;
	SBT_Insert(T-&gt;ch[key&gt;T-&gt;key],key);
	assert(lev&lt;15000);
	SBT_Maintain(T,key&gt;T-&gt;key);
}

int main(){
	unsigned N=0,level[15000]={0};
	SBTree T=&amp;NIL;
	fscanf(fin,"%u",&amp;N);
	for(int i=1,x,y;i&lt;=N;i++){
		fscanf(fin,"%u%u",&amp;x,&amp;y);
		lev=0;
		SBT_Insert(T,x);
		level[lev]++;
	}
	for(int i=0;i&lt;N;i++)	fprintf(fout,"%d\n",level[i]);
	fclose(fin);
	fclose(fout);
	return 0;
}
</pre>
<p>VBST版：</p>
<pre class="brush:c++">/*
  Name: URAL#1028. Stars
  Author: 巨菜逆铭(Tom Tung)
  Date: 20-06-07 13:55
  Description: Using Virtual Binary Search Tree
*/
#include &lt;cstdlib&gt;
#include &lt;cstring&gt;
#include &lt;cstdio&gt;
#define NDEBUG
#ifndef NDEBUG
  FILE *fin=fopen("INPUT.TXT","r");
  FILE *fout=fopen("OUTPUT.TXT","w");
#else
  FILE *fin=stdin;
  FILE *fout=stdout;
#endif
using namespace std;
unsigned N,x[15001],y,T[15001],LESS[15001],level[15000];
int cmp(const void *a, const void *b){return *(int*)a-*(int*)b;}
void Ins(unsigned xi){
	unsigned l=1,r=N,lev=0,m;
	while(l&lt;r){
		m=(l+r)/2;
		if(T[m]&lt;=xi)	lev+=LESS[m];
		if(xi&lt;=T[m])	LESS[m]++;
		if(xi==T[m])	break;
		else if(xi&lt;T[m])	r=m-1;
		else	l=m+1;
	}
	level[lev]++;
}
int main(){
	fscanf(fin,"%u",&amp;N);
	for(int i=1;i&lt;=N;i++)	fscanf(fin,"%u%u",&amp;x[i],&amp;y);
	memcpy(T,x,sizeof(x));
	qsort(T+1,N,sizeof(T[1]),cmp);
	for(int i=1;i&lt;=N;i++)	Ins(x[i]);
	for(int i=0;i&lt;N;i++)	fprintf(fout,"%d\n",level[i]);
	fclose(fin);
	fclose(fout);
	return 0;
}
</pre>
<p>SBST版：</p>
<pre class="brush:c++">/*
  Name: URAL#1028. Stars
  Author: 巨菜逆铭(Tom Tung)
  Date: 20-06-07 17:20
  Description: Using Static Binary Search Tree
*/
#include &lt;cstdlib&gt;
#include &lt;cstdio&gt;
#include &lt;cassert&gt;
#define NDEBUG
#ifndef NDEBUG
  FILE *fin=fopen("INPUT.TXT","r");
  FILE *fout=fopen("OUTPUT.TXT","w");
#else
  FILE *fin=stdin;
  FILE *fout=stdout;
#endif
using namespace std;
unsigned N,SBSTree[32002],LESS[32002],level[15000];
void SBST_Build(unsigned int k){
	if(k&gt;32001)	return;
	static unsigned p=0;
	SBST_Build(k&lt;&lt;1);
	SBSTree[k]=p++;
	SBST_Build((k&lt;&lt;1)+1);
}
void SBST_Insert(unsigned x)
{
	unsigned now=1,lev=0;
	while(1){
		if(SBSTree[now]&lt;=x)	lev+=LESS[now];
		if(x&lt;=SBSTree[now])	LESS[now]++;
		if(SBSTree[now]&gt;x)	now&lt;&lt;=1;
		else if(SBSTree[now]&lt;x)	(now&lt;&lt;=1)++;
		else break;
	}
	level[lev]++;
}
int main()
{
	SBST_Build(1);
	fscanf(fin,"%u",&amp;N);
	for(unsigned i=1,x,y;i&lt;=N;i++){
		fscanf(fin,"%u%u",&amp;x,&amp;y);
		SBST_Insert(x);
	}
	for(int i=0;i&lt;N;i++)	fprintf(fout,"%d\n",level[i]);
	fclose(fin);
	fclose(fout);
	return 0;
}
</pre>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/06/size-balanced-tree-in-cpp/' rel='bookmark' title='Size Balanced Tree in C++'>Size Balanced Tree in C++</a></li>
<li><a href='http://blog.tomtung.com/2007/05/noi99-chess/' rel='bookmark' title='[NOI99]棋盘分割(Chessboard Division)'>[NOI99]棋盘分割(Chessboard Division)</a></li>
<li><a href='http://blog.tomtung.com/2007/06/ural1183-brackets-sequence/' rel='bookmark' title='[01ACM NEEuropean][URAL1183]Brackets sequence'>[01ACM NEEuropean][URAL1183]Brackets sequence</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/06/ural1028-stars/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Size Balanced Tree in C++</title>
		<link>http://blog.tomtung.com/2007/06/size-balanced-tree-in-cpp/</link>
		<comments>http://blog.tomtung.com/2007/06/size-balanced-tree-in-cpp/#comments</comments>
		<pubDate>Tue, 19 Jun 2007 06:07:37 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[Size Balanced Tree]]></category>
		<category><![CDATA[数据结构]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/06/size-balanced-tree-in-c-6%e6%9c%8818%e6%97%a5-%e7%ae%80%e7%89%88-%e2%80%94%e2%80%94%e6%9c%80%e5%90%8e%e6%9b%b4%e6%96%b007-7-2/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/06/size-balanced-tree-in-cpp/" title="Size Balanced Tree in C++"></a>修改是在上一个版本的基础上进行的，从ghost那学了两个小技巧（是什么呢？呵呵~），一下子使得维护部分的代码量减少了一半，并且减少了烦琐的判空，这样代码的实际长度就在90行左右了，“宽度”也减小了不少。代码中有的地方注明了是“维护p”，那是由于有的时候维护父指针p(arent)会方便些。如果不需要，直接删掉这些语句就行了。当然，还有其它一些小改动。 觉得如果没有错误的话就没有必要再继续改动了。再改动也不可能改变它的时间复杂度，顶多是优化一下常数，尽早开始背吧。 当然如果你发现了程序中哪些不合理的地方导致了速度变慢也请指出，谢谢…… （为什么我的sbt会比ghost的treap慢呢，旋转的次数也比他的少多了啊，郁闷~） 1. 修改了Delete的函数原型说明：它与后面的Delete函数算法描述部分的不一致。 2. 删除了Insert部分中关于具有相同关键字节点插入子树的左右说明：它显然是不对的（我竟然一直没意识到直至我的ural1028SBT版用rank后 WA掉），因为一Maintain原来插入的位置就有可能变化。 ——2007年6月21日更新 加入了退化版SBT的说明（在Maintain处）。经测试在一般数据下退化版的确实要快——这在OI比赛中也许很实用。 ——2007年7月2日更新 小更新。完善了部分注释，并且把delete里的两句做了优化，使之更短更快。修改了rank，把每次对于i和size关系的判断（它事实上只需要进行一次）写到了注释里，要求i&#60;=T-&#62;size，否则每次都要判断实在太慢了。把.cc的扩展名改成了.h，因为我默写的时候都是把SBT默写到一个.h文件里，然后用另外一个写好的cpp文件调用它来检查对错。这样也许会方便些吧。 ——2007年7月6日更新 /**************************************** * SBT.h * * Fri Jul 18 20:40:08 2007 * Copyright 2007 巨菜逆铭 * ******************************************/ #include &#60;iostream&#62; #include &#60;cassert&#62; using namespace std; //————SBT的储存结构———— struct SBTNode{ &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/06/size-balanced-tree-in-cpp/">继续阅读 &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/06/size-balanced-tree-in-cpp/" title="Size Balanced Tree in C++"></a><p>修改是在上一个版本的基础上进行的，从ghost那学了两个小技巧（是什么呢？呵呵~），一下子使得维护部分的代码量减少了一半，并且减少了烦琐的判空，这样代码的实际长度就在90行左右了，“宽度”也减小了不少。代码中有的地方注明了是“维护p”，那是由于有的时候维护父指针p(arent)会方便些。如果不需要，直接删掉这些语句就行了。当然，还有其它一些小改动。</p>
<p>觉得如果没有错误的话就没有必要再继续改动了。再改动也不可能改变它的时间复杂度，顶多是优化一下常数，尽早开始背吧。</p>
<p>当然如果你发现了程序中哪些不合理的地方导致了速度变慢也请指出，谢谢……</p>
<p>（为什么我的sbt会比ghost的treap慢呢，旋转的次数也比他的少多了啊，郁闷~）</p>
<blockquote><p>1.  修改了Delete的函数原型说明：它与后面的Delete函数算法描述部分的不一致。<br />
2. 删除了Insert部分中关于具有相同关键字节点插入子树的左右说明：它显然是不对的（我竟然一直没意识到直至我的ural1028SBT版用rank后 WA掉），因为一Maintain原来插入的位置就有可能变化。</p>
<p>——2007年6月21日更新</p>
<p>加入了退化版SBT的说明（在Maintain处）。经测试在一般数据下退化版的确实要快——这在OI比赛中也许很实用。</p>
<p>——2007年7月2日更新</p>
<p>小更新。完善了部分注释，并且把delete里的两句做了优化，使之更短更快。修改了rank，把每次对于i和size关系的判断（它事实上只需要进行一次）写到了注释里，要求i&lt;=T-&gt;size，否则每次都要判断实在太慢了。把.cc的扩展名改成了.h，因为我默写的时候都是把SBT默写到一个.h文件里，然后用另外一个写好的cpp文件调用它来检查对错。这样也许会方便些吧。</p>
<p>——2007年7月6日更新</p></blockquote>
<pre class="brush:c++">/****************************************
*                 SBT.h
*
*       Fri Jul 18 20:40:08 2007
*       Copyright  2007  巨菜逆铭
*
******************************************/

#include &lt;iostream&gt;
#include &lt;cassert&gt;
using namespace std;

//————SBT的储存结构————
struct SBTNode{
    SBTNode *ch[2],*p;    //ch[0]、ch[1]分别为左右孩子，p为双亲
    long key;    //这里省略了卫星数据域
    unsigned long size;
    SBTNode(long _key,unsigned long _size);
}NIL=SBTNode(0,0);
typedef SBTNode *SBTree;
SBTNode::SBTNode(long _key,unsigned long _size=1){ //构造函数：未考虑卫星数据
    ch[0]=ch[1]=p=&amp;NIL;
    size=_size;
    key=_key;
}

//————SBT基本操作函数原型说明————
SBTNode *SBT_Search(SBTree T,long key);
    //在T中中寻找关键字为key的结点
    //若能找到则返回指向它的指针，否则返回NULL
void SBT_Insert(SBTree &amp;T, SBTNode* x);
    //将节点x插入树中
SBTNode *SBT_Delete(SBTree &amp;T, long key);
    //从以T为根的SBT中删除一个关键字为key的结点并返回“实际”被删除结点的指针
    //如果树中没有一个这样的结点，删除搜索到的最后一个结点并返回其指针
SBTNode *SBT_Pred(SBTree T, long key);
    //返回指向关键字为key的节点在T的中序遍历中的直接前趋的指针
    //要求T中必须有关键字为key的节点
SBTNode *SBT_Succ(SBTree T,long key);
    //返回指向关键字为key的节点在T的中序遍历中的直接后继的指针
    //要求T中必须有关键字为key的节点
SBTNode *SBT_Select(SBTree T, unsigned long i);
    //从树T中找到关键字第i小的结点并返回其指针
unsigned long SBT_Rank(SBTree T, long key);
    //返回关键字为key的节点在树T中的秩
    //若不存在此节点则返回0

//————SBT的修复操作的算法描述————
inline void SBT_Rotate(SBTree &amp;x,bool flag){
    SBTNode *y=x-&gt;ch[!flag];
    assert(x!=&amp;NIL&amp;&amp;y!=&amp;NIL);
    //维护p
    y-&gt;p=x-&gt;p;
    x-&gt;p=y;
    if(y-&gt;ch[flag]!=&amp;NIL) y-&gt;ch[flag]-&gt;p=x;
    //维护x和y的ch[]
    x-&gt;ch[!flag]=y-&gt;ch[flag];
    y-&gt;ch[flag]=x;
    //维护size
    y-&gt;size=x-&gt;size;
    x-&gt;size=x-&gt;ch[0]-&gt;size+x-&gt;ch[1]-&gt;size+1;
    //维护原x父节点的ch[]
    x=y;
}
void SBT_Maintain(SBTree &amp;T,bool flag){
    //维护操作的核心：保持
    if(T-&gt;ch[flag]-&gt;ch[flag]-&gt;size&gt;T-&gt;ch[!flag]-&gt;size)    //情况1
        SBT_Rotate(T,!flag);
    //此函数内剩余代码被注释掉后SBT将在插入“人”字型代码时退化
    //但是对于一般数据将会更快
    else if(T-&gt;ch[flag]-&gt;ch[!flag]-&gt;size&gt;T-&gt;ch[!flag]-&gt;size){ //情况2
        SBT_Rotate(T-&gt;ch[flag],flag);
        SBT_Rotate(T,!flag);
    }
    else return;    //无需修复
    SBT_Maintain(T-&gt;ch[0],0),SBT_Maintain(T-&gt;ch[1],1);    //修复左右子树
    SBT_Maintain(T,0),SBT_Maintain(T,1);    //修复整棵树
}

//————SBT基本操作的算法描述————
SBTNode *SBT_Search(SBTree T,long key){
    //在T中中寻找关键字为key的结点
    //若能找到则返回指向它的指针，否则返回NULL
    return T==&amp;NIL||T-&gt;key==key?T:SBT_Search(T-&gt;ch[key&gt;T-&gt;key],key);
}

void SBT_Insert(SBTree &amp;T, SBTNode* x){
    //将节点x插入树中
    if(T==&amp;NIL)    T=x;
    else{
        T-&gt;size++;
        x-&gt;p=T;    //维护p
        SBT_Insert(T-&gt;ch[x-&gt;key&gt;T-&gt;key],x);
        SBT_Maintain(T,x-&gt;key&gt;T-&gt;key);
    }
}

SBTNode *SBT_Delete(SBTree &amp;T, long key){
    //从以T为根的SBT中删除一个关键字为key的结点并返回“实际”被删除结点的指针
    //如果树中没有一个这样的结点，删除搜索到的最后一个结点并返回其指针
    if(T==&amp;NIL)    return &amp;NIL;
    T-&gt;size--;
    if(T-&gt;key==key||T-&gt;ch[key&gt;T-&gt;key]==&amp;NIL){
        SBTNode *toDel;
        if(T-&gt;ch[0]==&amp;NIL||T-&gt;ch[1]==&amp;NIL){
            toDel=T;
            T=T-&gt;ch[T-&gt;ch[1]!=&amp;NIL];
            if(T!=&amp;NIL)    T-&gt;p=toDel-&gt;p;    //维护p
        }else{
            toDel=SBT_Delete(T-&gt;ch[1],key-1);
            T-&gt;key=toDel-&gt;key;
        }
        return toDel;
    }
    else return SBT_Delete(T-&gt;ch[key&gt;T-&gt;key],key);
}

SBTNode *SBT_Pred(SBTree T, long key){
    //返回指向拥有比key小的最大关键字的节点的指针
    if(T==&amp;NIL)    return &amp;NIL;
    if(key&lt;=T-&gt;key)    return SBT_Pred(T-&gt;ch[0],key);
    else{
        SBTNode *pred=SBT_Pred(T-&gt;ch[1],key);
        return (pred!=&amp;NIL?pred:T);
    }
}

SBTNode *SBT_Succ(SBTree T,long key){
    //返回指向拥有比key大的最小关键字的节点的指针
    if(T==&amp;NIL)    return &amp;NIL;
    if(key&gt;=T-&gt;key)    return SBT_Succ(T-&gt;ch[1],key);
    else{
        SBTNode *succ= SBT_Succ(T-&gt;ch[0],key);
        return(succ!=&amp;NIL?succ:T);
    }
}

SBTNode *SBT_Select(SBTree T, unsigned long i){
    //从树T中找到关键字第i小的结点并返回其指针
    //要求i&lt;=T-&gt;size
    unsigned long r = T-&gt;ch[0]-&gt;size+1;
    if(i==r)    return T;
    else return SBT_Select(T-&gt;ch[i&gt;r],i&gt;r?i-r:i);
}

unsigned long SBT_Rank(SBTree T, long key){
    //返回关键字为key的节点在树T中的秩
    //若不存在此节点则返回0
    if(T==&amp;NIL)    return 0;
    if(T-&gt;key==key)    return T-&gt;ch[0]-&gt;size+1;
    else if(key&lt;T-&gt;key)    return SBT_Rank(T-&gt;ch[0],key);
    else{
        unsigned long r=SBT_Rank(T-&gt;ch[1],key);
        return r==0?0:r+T-&gt;ch[0]-&gt;size+1;
    }
}
</pre>

]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/06/size-balanced-tree-in-cpp/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>诡异的事：关于C/C++输入输出</title>
		<link>http://blog.tomtung.com/2007/06/c-cpp-io-odd-behavior/</link>
		<comments>http://blog.tomtung.com/2007/06/c-cpp-io-odd-behavior/#comments</comments>
		<pubDate>Fri, 15 Jun 2007 09:58:20 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[C++]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/06/%e8%af%a1%e5%bc%82%e7%9a%84%e4%ba%8b%ef%bc%9a%e5%85%b3%e4%ba%8ecc%e8%be%93%e5%85%a5%e8%be%93%e5%87%ba-6-17%e6%9b%b4%e6%96%b0/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/06/c-cpp-io-odd-behavior/" title="诡异的事：关于C/C++输入输出"></a>今天做题做不进去，就想着学了C的输入输出吧，毕竟我一直使用C++的stream太慢了，一般考数据结构的题都是上百万的数据需要输入输出。记得看到一道题直接注明类似“给使用C++同学的提示：极限数据中使用流将比使用stdio.h中的输入输出函数多花1s时间”。这样学习C的stdio 就显得很必要了。但是今天出现了诡异的问题。 我们看到，慢得出名的stream竟然比C的I/O快？！反复运行都是这个结果，只是程度有所不同……而且不仅仅是这个程序，我把cashier的输入输出改了以后速度也没提高。太诡异了…… 下面帖出代码，大家帮忙看看是不是我写得不对 &#8211; - #include &#60;iostream&#62; #include &#60;fstream&#62; #include &#60;cstdio&#62; #include &#60;ctime&#62; using namespace std; int main() { printf("用C函数输出1000,0000个数花费时间为："); clock_t b=clock(); FILE *out = fopen("test.txt","w"); for(unsigned i=0;i&#60;10000000;i++) fprintf(out,"%d ",i); fclose(out); printf("%dms\n",clock()-b); cout &#60;&#60; "用stream输出1000,0000个数花费时间为："; clock_t a = clock(); &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/06/c-cpp-io-odd-behavior/">继续阅读 &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/06/c-cpp-io-odd-behavior/" title="诡异的事：关于C/C++输入输出"></a><div>
<div>今天做题做不进去，就想着学了C的输入输出吧，毕竟我一直使用C++的stream太慢了，一般考数据结构的题都是上百万的数据需要输入输出。记得看到一道题直接注明类似“给使用C++同学的提示：极限数据中使用流将比使用stdio.h中的输入输出函数多花1s时间”。这样学习C的stdio 就显得很必要了。但是今天出现了诡异的问题。</div>
<div><img class="alignnone" title="http://upload.tomtung.com/img/c-cpp-io-odd-behavior-win.jpg" src="http://upload.tomtung.com/img/c-cpp-io-odd-behavior-win.jpg" alt="" width="371" height="120" /></div>
<div>我们看到，慢得出名的stream竟然比C的I/O快？！反复运行都是这个结果，只是程度有所不同……而且不仅仅是这个程序，我把cashier的输入输出改了以后速度也没提高。太诡异了……</div>
<div>下面帖出代码，大家帮忙看看是不是我写得不对 &#8211; -</div>
<pre class="brush:c++">#include &lt;iostream&gt;
#include &lt;fstream&gt;
#include &lt;cstdio&gt;
#include &lt;ctime&gt;
using namespace std;
int main()
{
        printf("用C函数输出1000,0000个数花费时间为：");
        clock_t b=clock();
        FILE *out = fopen("test.txt","w");
        for(unsigned i=0;i&lt;10000000;i++)       fprintf(out,"%d ",i);
        fclose(out);
        printf("%dms\n",clock()-b);

        cout &lt;&lt; "用stream输出1000,0000个数花费时间为：";
        clock_t a = clock();
        ofstream fout("test.txt");
        for(unsigned i=0;i&lt;10000000;i++)       fout &lt;&lt; i &lt;&lt; ' ';
        fout.close();
        cout &lt;&lt; clock()-a &lt;&lt; "ms" &lt;&lt; endl;

        printf("用C函数读入1000,0000个数花费时间为：");
        clock_t c = clock();
        FILE *in = fopen("test.txt","r");
        for(unsigned i=0,j;i&lt;10000000;i++)  fscanf(in,"%u ",&amp;j);
        fclose(in);
        printf("%dms\n",clock()-c);

        printf("用stream读入1000,0000个数花费时间为：");
        clock_t d = clock();
        ifstream fin("test.txt");
        for(unsigned i=0,j;i&lt;10000000;i++)  fin &gt;&gt; j;
        fin.close();
        cout &lt;&lt; clock()-d &lt;&lt; "ms\n";

        system("pause");
}
</pre>
<div>update at 6.17:</div>
<div>下面是我今天(6.17)日在ubuntu下用anjuta&amp;g++做的测试情况。（忘了指明，上面的测试是在winXP下用devc++&amp;g++做的。）</div>
<div>几乎完全一样的代码（结果的显示格式稍做了变化），但结果完全不同。fstream的劣势很明显了。</div>
<p><img class="alignnone" title="http://upload.tomtung.com/img/c-cpp-io-odd-behavior-linux.png" src="http://upload.tomtung.com/img/c-cpp-io-odd-behavior-linux.png" alt="" width="589" height="442" /></p>
<div>鱼牛本来的解释是“我们平常说stream慢是指iostream，不是fstream”，看来也不尽然了。</div>
</div>

]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/06/c-cpp-io-odd-behavior/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>请假未遂</title>
		<link>http://blog.tomtung.com/2007/06/ask-for-leave/</link>
		<comments>http://blog.tomtung.com/2007/06/ask-for-leave/#comments</comments>
		<pubDate>Thu, 14 Jun 2007 15:31:23 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[NOI]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/06/%e8%af%b7%e5%81%87%e6%9c%aa%e9%81%82/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/06/ask-for-leave/" title="请假未遂"></a>想请假直至NOI结束，但学生处不批准，理由是害怕学习耽误，且计算机比赛含金量低（？！），说等会考完了以后再说。现在考虑是不是上半天学在家呆半天，这样OI、学习、看MM三不误~//怎么越来越向Matrix67看齐了我 &#8211; - 其实在做出请假的决定后自己都没来由地有些后悔。只希望各科老师不要再这么逼作业了，抄作业很浪费时间的。 今天数学补课班考试，发现自己什么都不会了，连等差数列的首项公差求和公式都是现推的。看来要看看文化课了。 对于NOI，我心里完全没有底。紧张、寂寞、焦虑，这些高考综合症的典型症状已经开始慢慢将我吞噬。“总是拿着微不足道的成就来骗自己，总是莫名其妙感到一阵的空虚。” 多希望这次能拿个铜赶紧走掉，真的不想再继续了。 可能你对下面的文章也感兴趣： 决定了 命运的冷笑话 明天省选<hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/05/determination/' rel='bookmark' title='决定了'>决定了</a></li>
<li><a href='http://blog.tomtung.com/2007/05/black-humour-from-god/' rel='bookmark' title='命运的冷笑话'>命运的冷笑话</a></li>
<li><a href='http://blog.tomtung.com/2007/05/province-team-selection-tomoro/' rel='bookmark' title='明天省选'>明天省选</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/06/ask-for-leave/" title="请假未遂"></a><div>想请假直至NOI结束，但学生处不批准，理由是害怕学习耽误，且计算机比赛含金量低（？！），说等会考完了以后再说。现在考虑是不是上半天学在家呆半天，这样OI、学习、看MM三不误~<span style="color: #ffffff;">//怎么越来越向Matrix67看齐了我 &#8211; -</span></div>
<div>其实在做出请假的决定后自己都没来由地有些后悔。只希望各科老师不要再这么逼作业了，抄作业很浪费时间的。</div>
<div>今天数学补课班考试，发现自己什么都不会了，连等差数列的首项公差求和公式都是现推的。看来要看看文化课了。</div>
<div>对于NOI，我心里完全没有底。紧张、寂寞、焦虑，这些高考综合症的典型症状已经开始慢慢将我吞噬。“总是拿着微不足道的成就来骗自己，总是莫名其妙感到一阵的空虚。”</div>
<div>多希望这次能拿个铜赶紧走掉，真的不想再继续了。</div>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/05/determination/' rel='bookmark' title='决定了'>决定了</a></li>
<li><a href='http://blog.tomtung.com/2007/05/black-humour-from-god/' rel='bookmark' title='命运的冷笑话'>命运的冷笑话</a></li>
<li><a href='http://blog.tomtung.com/2007/05/province-team-selection-tomoro/' rel='bookmark' title='明天省选'>明天省选</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/06/ask-for-leave/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>补考</title>
		<link>http://blog.tomtung.com/2007/06/retake-exam/</link>
		<comments>http://blog.tomtung.com/2007/06/retake-exam/#comments</comments>
		<pubDate>Sat, 09 Jun 2007 02:40:03 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[高三]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/06/%e8%a1%a5%e8%80%83/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/06/retake-exam/" title="补考"></a>明天早上十点，信息学会考补考。 我牛吧 &#8211; - //别来问我“到底怎么回事？”]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/06/retake-exam/" title="补考"></a><div>明天早上十点，信息学会考补考。</div>
<div>我牛吧 &#8211; -</div>
<div><span style="color: #ffffff;">//别来问我“到底怎么回事？”</span></div>

]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/06/retake-exam/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>[Uva #258] Mirror Maze</title>
		<link>http://blog.tomtung.com/2007/06/uva-258-mirror-maze/</link>
		<comments>http://blog.tomtung.com/2007/06/uva-258-mirror-maze/#comments</comments>
		<pubDate>Wed, 06 Jun 2007 12:58:47 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/06/uva-258-mirror-maze/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/06/uva-258-mirror-maze/" title="[Uva #258] Mirror Maze"></a>Mirror Maze In a galaxy far far away from our&#8217;s, there lived a team of scientists who invented a device that could kill all computervirusses that do terrible things to MS-DOS computers. This device could do its job in the &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/06/uva-258-mirror-maze/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/06/ural1183-brackets-sequence/' rel='bookmark' title='[01ACM NEEuropean][URAL1183]Brackets sequence'>[01ACM NEEuropean][URAL1183]Brackets sequence</a></li>
<li><a href='http://blog.tomtung.com/2007/05/museum-fire/' rel='bookmark' title='[IOI2000国家队原创题] 艺术馆的火灾'>[IOI2000国家队原创题] 艺术馆的火灾</a></li>
<li><a href='http://blog.tomtung.com/2007/05/poi-vi-musketeers/' rel='bookmark' title='[POI VI Stage 1 Problem 1] Musketeers'>[POI VI Stage 1 Problem 1] Musketeers</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/06/uva-258-mirror-maze/" title="[Uva #258] Mirror Maze"></a><div>
<p style="text-align: center;"><a id="SECTION0001000000000000000000" name="SECTION0001000000000000000000"><span style="font-size: 32px; color: #0099cc;">Mirror Maze</span></a></p>
<p><span> </span></p>
<p><span>In a galaxy far far away from our&#8217;s, there lived a team of scientists who invented a device that could kill all computervirusses that do terrible things to MS-DOS computers. This device could do its job in the entire universe because it used a magic laser beam. But, just like in all great devices, this device had a strange component in it. This component is a two-dimensional maze with black holes and mirrors in it. Nobody knew the reason for this component but the scientists said it was a crucial component.</span></p>
<p><span>This maze has two openings in it. One of these openings is in front of the magic laser. All the mirrors in the maze have two reflecting sides. These mirrors always make an angle of 45 degrees with the laser beam but they can be rotated over 90 degrees, so each mirror can be in 2 states only. The laser beam will be totally absorbed by a black hole. The laser beam may cross itself (with an angle of 90 degrees) in an empty place of the maze.</span></p>
<p><span>In this problem you are given several mazes (one at a time) in which you have to position the mirrors in such a way that the laser beam can travel from one entrance to the other. The border of each maze is marked by black holes (except for two places which are the two entrances of the maze). The mirrors in the given mazes will probably not have a correct angle to reflect a laser beam from one entrance of the maze to the other. The mirrors in the given mazes can be positioned in such a way that a laser beam can travel through the maze.</span></p>
<p><span>Your program must read the mazes from the input file and position the mirrors in it in such a way that a laser beam that enters through one entrance exits through the other.</span></p>
<h2><a id="SECTION0001001000000000000000" name="SECTION0001001000000000000000"><span style="color: #0070e8;">Input</span></a></h2>
<p><span>The input for your program is a textflle. This file contains severas mazes. A specification of a single maze is given by the following description:</span></p>
<ul>
<li><span>First a line that contains two positive integers (say <em>M</em> and <em>N</em>) separated by one space which specify the number of columns and the number of rows (in that order) of the maze to come. These integers can have a value from 3 to 50 inclusive.</span></li>
<li><span>On the next <em>N</em> lines follows the maze with the mirrors and black holes. Mirrors are given by a <code>\</code> (backslash) or a <tt>/</tt> (divide). Here <code>\</code> and <tt>/</tt> correspond to the 2 states of a mirror. Black holes are given by <tt>*</tt> (star). Empty places in the maze are given by dots.</span></li>
</ul>
<p><span>The last line of the input file is given by `<tt>-1 -1</tt>&#8216;.</span></p>
<h2><a id="SECTION0001002000000000000000" name="SECTION0001002000000000000000"><span style="color: #0070e8;">Output</span></a></h2>
<p><span>The output file is a textfile which contains the resulting mazes. The mazes in the output file must be separated from each other by one empty line.</span></p>
<h2><a id="SECTION0001003000000000000000" name="SECTION0001003000000000000000"><span style="color: #0070e8;">Solution</span></a></h2>
<p>AC的第一道Uva，庆祝下……</p>
<p>这题的算法是DFS。但我并没有直接用lrj书上说的拆结点的方法，而是换了一种方式。首先预处理地图中镜子的信息，记录它们上下左右相邻的镜子都是哪些，并且记录哪两个镜子是和出、如口相邻的（它们都可以作为搜索的起点）。我们知道光路的走向到达镜子处时如何变化仅仅取决于两个因素：入射方向和镜子的状态。这样我们就根据一个出、如口的位置确定第一面镜子的入射方向，然后我们顺着往下走就行，如果不通就回溯并改变镜子状态，直到找到一个可行解时输出就行了。有一点要注意的是光可能两次经过同一面镜子的不同面（同面显然不可能）。这样回溯时就要注意了：这么镜子是否已经被光经过了？如果是，那么它就一定不能再被翻了。还有就是出口直接对入口不需要镜子这种特殊情况的处理。</p>
<p>uva的测评好严格啊……我开始交了n多次竟然一直ce找不到原因。结果在论坛里发了帖子问了才知道（p.s.uva的管理员很尽职啊，回答迅速准确友好），原来我用了memset却没#include&lt;cstring&gt;，所以错了（这是我第一次意识到memset是string.h里面的东西）。而这个在我的winxp和ubuntu下面用g++都能编译通过……要是noi的评测也来这么一手岂不是很囧……</p>
<p>下面是代码。长度不短、速度不快、空间消耗不小，因此仅供参考：</p>
<pre class="brush:c++">
#include &lt;iostream&gt;
#include &lt;cassert&gt;
#include &lt;cstring&gt;
using namespace std;
int M,N;
char map[51][51];
int mirror_sum;//镜子的个数
int mirror_num[51][51];//[x][y]:(x,y)处镜子的编号；出/入口为-1
struct Mirror{
	short neighbor[4];	//4个方向上的相邻镜子编号：0为不存在，-1为出/入口
	//关于方向的定义：0上，1左，2下，3右
	short x,y;//镜子坐标
}mirror[2500];
bool state[2500];//[i]:第[i]面镜子的状态：0为'/'，1为'\'

bool used[2500];
//used[i]:dfs中记录第i个镜子是否原来已经使用（即镜子能否翻转而不影响已知的光线路径？）
inline void get_next(int i,int j,int &amp;next_i,int &amp;next_j)
//dfs调用的子过程，根据当前镜子编号和入射方向确定下一面镜子的编号和入射方向
{
	int d[4];
	if(state[i]==0)//下-右，上-左
	{
		d[0]=1;d[1]=0;d[2]=3;d[3]=2;
	}
	else{	//上-右，下-左
		d[0]=3;d[1]=2;d[2]=1;d[3]=0;
	}
	next_j=(d[j]+2)%4;
	next_i=mirror[i].neighbor[d[j]];
}
bool dfs(int i, int j)	//射向第i面镜子，入射方向为j。若找到解则返回true
{
	if(i==-1)	return true;	//到达出口
	int next_i,next_j;
	bool used_i=used[i],flag=false;
	used[i]=true;
	get_next(i,j,next_i,next_j);
	if(next_i!=0)
		flag=dfs(next_i,next_j);
	if(flag)	return true;
	if(!used_i)
	{
		state[i]=!state[i];
		get_next(i,j,next_i,next_j);
		flag=dfs(next_i,next_j);
		if(flag)	return true;
		state[i]=!state[i];
	}
	used[i]=used_i;
	return false;
}

int main()
{
	int counter=0;
	while(1)
	{
		cin &gt;&gt; M &gt;&gt; N;
		if(M==-1&amp;&amp;N==-1)	break;	//判断输入结束
		if(counter++&gt;0)	cout &lt;&lt; endl;
		/*初始化*/
		mirror_sum=0;
		memset(mirror_num,'\0',sizeof(mirror_num));
		memset(mirror,'\0',sizeof(mirror));
		memset(used,'\0',sizeof(used));
		memset(state,'\0',sizeof(state));

		//读入数据
		for(int x=1;x&lt;=N;x++)
			for(int y=1;y&lt;=M;y++)
			{
				cin &gt;&gt; map[x][y];
				if(map[x][y]=='/'||map[x][y]=='\\')	//记录镜子的信息
				{
					mirror_sum++;
					mirror_num[x][y]=mirror_sum;
					mirror[mirror_sum].x=x;
					mirror[mirror_sum].y=y;
					state[mirror_sum]=(map[x][y]=='\\');
				}
				else if((x==N||y==M||x==1||y==1)&amp;&amp;map[x][y]=='.')	//记录出/入口的信息
					mirror_num[x][y]=-1;
			}

		//处理各镜子的相邻镜子信息
		const int dir_x[4]={-1,0,1,0},dir_y[4]={0,-1,0,1};
		int first_mirror=-1,first_dir=-1;
		for(int i=1,x,y;i&lt;=mirror_sum;i++)
			for(int d=0;d&lt;=3;d++)
			{
				if(mirror[i].neighbor[d]!=0)	continue;
				x=mirror[i].x,y=mirror[i].y;
				while(1)
				{
					x+=dir_x[d];
					y+=dir_y[d];
					assert(x&gt;=1&amp;&amp;x&lt;=N&amp;&amp;y&gt;=1&amp;&amp;y&lt;=M);
					if(map[x][y]=='*')	break;	//遇到黑洞
					if(mirror_num[x][y]==-1)	//遇到出/入口
					{
						mirror[i].neighbor[d]=-1;
						first_mirror=i;
						first_dir=d;
						break;
					}
					if(mirror_num[x][y]&gt;0)	//遇到镜子
					{
						mirror[i].neighbor[d]=mirror_num[x][y];
						mirror[mirror_num[x][y]].neighbor[(d+2)%4]=i;
						break;
					}
				}
			}

		if(first_mirror!=-1&amp;&amp;first_dir!=-1)
		{
			bool flag=dfs(first_mirror,first_dir);//搜索可行解
			assert(flag);
		}

		for(int i=1;i&lt;=N;i++)//输出可行解
		{
			for(int j=1;j&lt;=M;j++)
				if(mirror_num[i][j]==0||mirror_num[i][j]==-1)	cout &lt;&lt; map[i][j];
				else cout &lt;&lt; (state[mirror_num[i][j]]?'\\':'/');
			cout &lt;&lt; endl;
		}
	}
	return 0;
}
</pre>
<p>题外话：这题的题干实在没有上海ACM的那个救公主的故事编得好。这个故事仿佛一个小学生的蹩脚“科幻故事”，读起来实在让人比较郁闷&#8230;- -</p>
</div>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/06/ural1183-brackets-sequence/' rel='bookmark' title='[01ACM NEEuropean][URAL1183]Brackets sequence'>[01ACM NEEuropean][URAL1183]Brackets sequence</a></li>
<li><a href='http://blog.tomtung.com/2007/05/museum-fire/' rel='bookmark' title='[IOI2000国家队原创题] 艺术馆的火灾'>[IOI2000国家队原创题] 艺术馆的火灾</a></li>
<li><a href='http://blog.tomtung.com/2007/05/poi-vi-musketeers/' rel='bookmark' title='[POI VI Stage 1 Problem 1] Musketeers'>[POI VI Stage 1 Problem 1] Musketeers</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/06/uva-258-mirror-maze/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

