<?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/tag/%e8%a7%a3%e9%a2%98%e6%8a%a5%e5%91%8a/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>[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>[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>[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>[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 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 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>[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>
		<item>
		<title>[01ACM NEEuropean][URAL1183]Brackets sequence</title>
		<link>http://blog.tomtung.com/2007/06/ural1183-brackets-sequence/</link>
		<comments>http://blog.tomtung.com/2007/06/ural1183-brackets-sequence/#comments</comments>
		<pubDate>Fri, 01 Jun 2007 08:25:10 +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/01acm-neeuropeanural1183brackets-sequence/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/06/ural1183-brackets-sequence/" title="[01ACM NEEuropean][URAL1183]Brackets sequence"></a>Brackets sequence Time Limit: 1.0 second Memory Limit: 16 MB Let us define a regular brackets sequence in the following way: Empty sequence is a regular sequence. If S is a regular sequence, then (S) and [S] are both regular &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/06/ural1183-brackets-sequence/">继续阅读 &#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/02/usaco-arithmetic-progressions/' rel='bookmark' title='[USACO]Arithmetic Progressions'>[USACO]Arithmetic Progressions</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/06/ural1183-brackets-sequence/" title="[01ACM NEEuropean][URAL1183]Brackets sequence"></a><h2 style="text-align: center;"><span style="color: #0080ff;">Brackets sequence</span></h2>
<p style="text-align: center;"><span>Time Limit: 1.0 second</span></p>
<p style="text-align: center;"><span>Memory Limit: 16 MB</span></p>
<p><span>Let us define a regular brackets sequence in the following way:</span></p>
<ol>
<li><span>Empty sequence is a regular sequence.</span></li>
<li><span>If S is a regular sequence, then (S) and [S] are both regular sequences.</span></li>
<li><span>If A and B are regular sequences, then AB is a regular sequence.</span></li>
</ol>
<p><span>For example, all of the following sequences of characters are regular brackets sequences:</span></p>
<p><span><tt>()</tt>, <tt>[]</tt>, <tt>(())</tt>, <tt>([])</tt>, <tt>()[]</tt>, <tt>()[()]</tt></span></p>
<p><span> </span></p>
<p><span>And all of the following character sequences are not:</span></p>
<p><span><tt>(</tt>, <tt>[</tt>, <tt>)</tt>, <tt>)(</tt>, <tt>([)]</tt>, <tt>([(]</tt></span></p>
<p><span> </span></p>
<p><span>Some sequence of characters &#8216;(&#8216;, &#8216;)&#8217;, &#8216;[', and ']&#8216; is given. You are to find the shortest possible regular brackets sequence, that contains the given character sequence as a subsequence. Here, a string a<sub>1</sub>a<sub>2</sub>&#8230;a<sub>n</sub> is called a subsequence of the string b<sub>1</sub>b<sub>2</sub>&#8230;b<sub>m</sub>, if there exist such indices 1 ≤ i<sub>1</sub> &lt; i<sub>2</sub> &lt; &#8230; &lt; i<sub>n</sub> ≤ m, that a<sub>j</sub>=b<sub>i</sub><sub>j</sub> for all 1 ≤ j ≤ n.</span></p>
<h3 class="subtitle"><span style="color: #0080ff;">Input</span></h3>
<p><span>The input file contains at most 100 brackets (characters &#8216;(&#8216;, &#8216;)&#8217;, &#8216;[' and ']&#8216;) that are situated on a single line without any other characters among them.</span></p>
<h3 class="subtitle"><span style="color: #0080ff;">Output</span></h3>
<p><span>Write to the output file a single line that contains some regular brackets sequence that has the minimal possible length and contains the given sequence as a subsequence.</span></p>
<h3 class="subtitle"><span style="color: #0080ff;">Sample</span></h3>
<table style="width: 200px;" border="1" cellspacing="1" cellpadding="1">
<tbody>
<tr>
<td><span style="color: #0080ff;"> </span><span> <strong>Input</strong></span></td>
<td><span> </span><span style="color: #0080ff;"><strong>Ouput</strong></span></td>
</tr>
<tr>
<td><span> ([(]</span></td>
<td><span> ()[()]</span></td>
</tr>
</tbody>
</table>
<p class="subtitle"><span><strong>Problem Author:</strong> Andrew Stankevich<br />
<strong>Problem Source:</strong> 2001-2002 ACM Northeastern European Regional Programming Contest</span></p>
<h3 class="subtitle"><span style="color: #0080ff;">Solution</span></h3>
<p class="subtitle">终于过了这题……WA了两次。第一次是判断括号匹配的栈写错了，WA#9；第二次是没有考虑输入文件是空串的情况，WA#13并持续一周 -_-|||</p>
<p class="subtitle">而且这次也写垃圾了，长达80多行。好像原来的ural的内存限制是1000K？这样说我开一个100*100的string数组真是件奢侈的事情呢……</p>
<pre class="brush:c++">
#include &lt;iostream&gt;
#include &lt;fstream&gt;
#include &lt;string&gt;
#include &lt;cassert&gt;
using namespace std;
ifstream fin("bracket.in");
ofstream fout("bracket.out");
string S;
inline bool isregular(int i,int j)	//判断原串中的子串S[i...j]是否规则
{
	if(j&lt;i)	return true;
	else if(j==i)	return false;
	char stack[100];	int p=0;
	for(int k=i;k&lt;=j;k++)
	{
		if(p==0)
		{
			if(S[k]=='('||S[k]=='[')	stack[p++]=S[k];
			else return false;
		}
		else{
			if(( stack[p-1]=='('&amp;&amp;S[k]==')' )||( stack[p-1]=='['&amp;&amp;S[k]==']' ))	p--;
			else if(S[k]==')'||S[k]==']')	return false;
			else	stack[p++]=S[k];
		}
	}
	return (p==0);
}
string s[100][100];	//[i][j]：按照最少原则添加括号规则化S[i...j]后得到的串
string memo(int i, int j)	//记忆化搜索
{
	if(s[i][j]!="")	return s[i][j];
	if(isregular(i,j))	//若S[i...j]本身就已经是规则的
	{
		s[i][j].assign(S,i,j-i+1);
		return s[i][j];
	}
	if(i==j)	//若当前串仅由一个字符组成
	{
		if(S[i]=='('||S[i]==')')	s[i][j]="()";
		else if(S[i]=='['||S[i]==']')	s[i][j]="[]";
		else assert(0);
		return s[i][j];
	}
	string ans,tmp,tmp2;
	unsigned size=UINT_MAX;
	if(( S[i]=='('&amp;&amp;S[j]==')' )||( S[i]=='['&amp;&amp;S[j]==']' ))	//若S[i...j]首尾配对，则只需使S[i+1...j-1]规则就得到一个解
	{
		ans=S[i]+memo(i+1,j-1)+S[j];
		size=ans.size();
	}
	else if(( S[i]=='('&amp;&amp;S[j]!=')' )||( S[i]=='['&amp;&amp;S[j]!=']' ))	//S[i...j]首尾不配对，与以上类似
	{
		ans=S[i]+memo(i+1,j);
		if(S[i]=='(')	ans=ans+')';
		else if(S[i]=='[')	ans=ans+']';
		else assert(0);
		size=ans.size();
	}
	else if(( S[i]!='('&amp;&amp;S[j]==')' )||( S[i]!='['&amp;&amp;S[j]==']' ))
	{
		ans=memo(i,j-1)+S[j];
		if(S[j]==')')	ans='('+ans;
		else if(S[j]==']')	ans='['+ans;
		else assert(0);
		size=ans.size();
	}
	for(int k=i;k&lt;j;k++)	//规则化S[i...k]和S[k+1...j]后合并得到的串
		if(size&gt;memo(i,k).size()+memo(k+1,j).size())
		{
			ans=memo(i,k)+memo(k+1,j);
			size=memo(i,k).size()+memo(k+1,j).size();
		}
	return (s[i][j]=ans);
}
int main()
{
	fin &gt;&gt; S;
	if(S.size()==0)
	{
		fout &lt;&lt; endl;
		return 0;
	}
	fout &lt;&lt; memo(0,S.size()-1) &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/02/usaco-arithmetic-progressions/' rel='bookmark' title='[USACO]Arithmetic Progressions'>[USACO]Arithmetic Progressions</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/06/ural1183-brackets-sequence/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>[IOI2000国家队原创题] 艺术馆的火灾</title>
		<link>http://blog.tomtung.com/2007/05/museum-fire/</link>
		<comments>http://blog.tomtung.com/2007/05/museum-fire/#comments</comments>
		<pubDate>Wed, 30 May 2007 13:26:24 +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/05/ioi2000%e5%9b%bd%e5%ae%b6%e9%98%9f%e5%8e%9f%e5%88%9b%e9%a2%98-%e8%89%ba%e6%9c%af%e9%a6%86%e7%9a%84%e7%81%ab%e7%81%be/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/05/museum-fire/" title="[IOI2000国家队原创题] 艺术馆的火灾"></a>艺术馆的火灾 Fire of the Art Gallery IOI2000 National Training Team Originals Author: 张辰 背景描述 这幢古老的建筑是一个艺术馆，它珍藏着上百件绘画、雕塑以及其他艺术品，就连建筑本身也是一件艺术。但是，岁月并不在乎它的精致与美丽，时光在渐渐剥夺着这幢木屋的生命。终于，在一个月色昏暗的夜晚，它着火了。 艺术馆是一幢两层的小楼，每一层有N个房间，每个房间中收藏的艺术品的价值都可以用一个正整数来表示。下面是一个N=4的例子。 在这个例子中，二层楼的第四个房间中艺术品的价值最大，为60。而一层楼的第四个房间中艺术品的价值仅为20。 在消防队员火速赶到的时候，火势已经蔓延了整个建筑，消防队员们观察每个房间中的火势，将它们分别用一个正整数来表示。在上面的例子中，各房间中的火势可能如下。 你可以看到，二层楼的第四个房间中火势最强，为70。而一层楼的第三个房间中火势较弱，为20。 由于火情紧急，消防队员们准备使用一种新型的灭火器。这种灭火器只能发射K次，每次发射将覆盖一个矩形的区域（矩形的高度可以是1也可以是2）。它的威力巨大，所到之处不但火焰会被扑灭，就连同在一处的艺术品也难以幸免。因此，如何善用这种灭火器成了最大的问题。 一个例子：如果灭火器的一次发射覆盖了下图阴影所示的2×2矩形区域，那么这四个房间的火势和艺术品价值都将成为0。 任务说明 给出艺术馆每层的房间数N和灭火器的发射次数K，以及每个房间中艺术品的价值和火势，你需要决定灭火器每次应该怎样发射（也可以不发射），才能将这次火灾的损失降到最低限度。这个损失用你所摧毁的艺术品的总价值，加上剩余的火势总值（这些火焰将需要消防队员们亲身去扑灭）来衡量。 输入数据 输入文件的第一行有两个整数N（1 &#60;= N &#60;= 100）、K（1 &#60;= K &#60;= 10），分别表示艺术馆中每层的房间数和灭火器的发射次数。 接下来的两行每行有N个整数，其中第4-i行的第j个整数Vi,j表示的是第i层第j个房间中艺术品的价值（1 &#60;= i &#60;= 2，1 &#60;= j &#60;= N，1 &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/05/museum-fire/">继续阅读 &#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/03/usaco-ordered-fractions/' rel='bookmark' title='[USACO]Ordered Fractions'>[USACO]Ordered Fractions</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/05/museum-fire/" title="[IOI2000国家队原创题] 艺术馆的火灾"></a><p style="text-align: center;"><strong>艺术馆的火灾</strong></p>
<p style="text-align: center;"><strong>Fire of the Art Gallery</strong></p>
<p style="text-align: center;"><strong>IOI2000 National Training Team Originals</strong></p>
<p style="text-align: center;"><strong>Author:</strong> <strong>张辰</strong></p>
<p><strong>背景描述</strong></p>
<p>这幢古老的建筑是一个艺术馆，它珍藏着上百件绘画、雕塑以及其他艺术品，就连建筑本身也是一件艺术。但是，岁月并不在乎它的精致与美丽，时光在渐渐剥夺着这幢木屋的生命。终于，在一个月色昏暗的夜晚，它着火了。</p>
<p>艺术馆是一幢两层的小楼，每一层有N个房间，每个房间中收藏的艺术品的价值都可以用一个正整数来表示。下面是一个N=4的例子。</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/museum-fire-1.jpg" src="http://upload.tomtung.com/img/museum-fire-1.jpg" alt="" width="388" height="74" /></p>
<p>在这个例子中，二层楼的第四个房间中艺术品的价值最大，为60。而一层楼的第四个房间中艺术品的价值仅为20。</p>
<p>在消防队员火速赶到的时候，火势已经蔓延了整个建筑，消防队员们观察每个房间中的火势，将它们分别用一个正整数来表示。在上面的例子中，各房间中的火势可能如下。</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/museum-fire-2.jpg" src="http://upload.tomtung.com/img/museum-fire-2.jpg" alt="" width="382" height="78" /></p>
<p>你可以看到，二层楼的第四个房间中火势最强，为70。而一层楼的第三个房间中火势较弱，为20。</p>
<p>由于火情紧急，消防队员们准备使用一种新型的灭火器。这种灭火器只能发射K次，每次发射将覆盖一个矩形的区域（矩形的高度可以是1也可以是2）。它的威力巨大，所到之处不但火焰会被扑灭，就连同在一处的艺术品也难以幸免。因此，如何善用这种灭火器成了最大的问题。</p>
<p>一个例子：如果灭火器的一次发射覆盖了下图阴影所示的2×2矩形区域，那么这四个房间的火势和艺术品价值都将成为0。</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/museum-fire-3.jpg" src="http://upload.tomtung.com/img/museum-fire-3.jpg" alt="" width="376" height="73" /></p>
<p><strong>任务说明</strong></p>
<p>给出艺术馆每层的房间数N和灭火器的发射次数K，以及每个房间中艺术品的价值和火势，你需要决定灭火器每次应该怎样发射（也可以不发射），才能将这次火灾的损失降到最低限度。这个损失用你所摧毁的艺术品的总价值，加上剩余的火势总值（这些火焰将需要消防队员们亲身去扑灭）来衡量。</p>
<p><strong>输入数据</strong></p>
<p>输入文件的第一行有两个整数N（1 &lt;= N &lt;= 100）、K（1 &lt;= K &lt;= 10），分别表示艺术馆中每层的房间数和灭火器的发射次数。</p>
<p>接下来的两行每行有N个整数，其中第4-i行的第j个整数V<sub>i,j</sub>表示的是第i层第j个房间中艺术品的价值（1 &lt;= i &lt;= 2，1 &lt;= j &lt;= N，1 &lt;= V<sub>i,j</sub> &lt;= 10000）。</p>
<p>再接下来的两行每行也有N个整数，其中第6-i行的第j个整数F<sub>i,j</sub>表示的是第i层第j个房间中的火势（1 &lt;= i &lt;= 2，1 &lt;= j &lt;= N，1 &lt;= F<sub>i,j</sub> &lt;= 10000）。</p>
<p><strong>输出数据</strong></p>
<p>你的输出文件应该有K行，每行有四个整数L<sub>1</sub>、R<sub>1</sub>、L<sub>2</sub>、R<sub>2</sub>，表示你的灭火器的一次行动。如果灭火器这次不发射，那么这四个整数都为0；否则这次发射所覆盖的矩形区域的左下角是第L<sub>1</sub>层的第R<sub>1</sub>个房间，右上角是第L<sub>2</sub>层的第R<sub>2</sub>个房间。</p>
<p>注意：你的每次发射所覆盖的矩形区域必须位于小楼之内，并且矩形的面积至少为0。即1 &lt;= L<sub>1</sub> &lt;= L<sub>2</sub> &lt;= 2，1 &lt;= R<sub>1</sub> &lt;= R<sub>2</sub> &lt;= N。</p>
<p><strong>输入输出样例</strong></p>
<table border="1" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td width="178" valign="top">INPUT.TXT</td>
<td width="179" valign="top">OUTPUT.TXT</td>
<td width="229" valign="top">样例图示</td>
</tr>
<tr>
<td width="178" valign="top">4 2</p>
<p>40 50 30 60</p>
<p>30 30 40 20</p>
<p>50 40 50 70</p>
<p>40 50 20 30</td>
<td width="179" valign="top">1 1 1 2</p>
<p>2 3 2 4</td>
<td width="229" valign="top"><img class="alignnone" title="http://upload.tomtung.com/img/museum-fire-4.jpg" src="http://upload.tomtung.com/img/museum-fire-4.jpg" alt="" width="216" height="57" /></p>
<p>摧毁艺术品：30+60+30+30=150</p>
<p>剩余火势：50+40+20+30=140</p>
<p>最小损失：150+140=290</td>
</tr>
</tbody>
</table>
<p><strong>评分标准</strong></p>
<p>每个测试点都有一定的时间限制。你的程序只有在规定的时限内输出解，并且按照你的方案来发射灭火器可以将损失降到最小，你才会得到相应测试点的分值；否则这个测试点不得分。</p>
<p>注意：本题的解不一定是唯一的。即对于一个测试数据，可能有多种发射灭火器的方案都可以使损失达到最小。在这种情况下，你只要输出最优方案中的任意一种即可。</p>
<p><strong>解题报告</strong></p>
<p>自己独立做出来的好有成就感啊……^_^</p>
<p>做这题的过程还相当传奇。话说那天生物课没拿书，直接被老师赶出去到物理办公室门口站着。岂知那里却安静、光线明亮，清风吹拂，透过窗口可以看见墙上大片翠绿的藤蔓植物在风中生起层层波浪，让人心旷神怡，真是个绝佳的自习的所在。当时手里也没书，没事干，就想起了这道本来一看来源就已经准备直接看题解的题目，和老师要了纸笔，想啊想啊就把这题搞出来了。-_-|||</p>
<p>不扯了，下面说说这题的做法（和官方解法不太一样~）。</p>
<p>这题的模型可以看成是一个2*N的矩形表格中每格存有有序数对(V,F)，现在要从表格中选出一些格子，要求：</p>
<p style="margin-left: 40px;">1.（选出格子中的V）+（未选中格子中的F）值最小；</p>
<p style="margin-left: 40px;">2.这些格子能组成的最少完整矩形个数不大于定值K。</p>
<p>在第2个要求中我们强调“最少”和“完整”的矩形，这样才能得到最优解。因此，我们可以设法求出一种满足条件的格子选择，然后再按照最少原则用贪心算法划分矩形，且规定划分得到的矩形互不相交（因为相交时得到的解一定不优于此）。以样例数据为例，先选出(1,1)，(1,2)，(2,3)，(2,4)四个格子，然后再把它们组合成(1,1,1,2)和(2,3,2,4)两个完整矩形。这已经是这四个格子所能组成的最少矩形数目了。（下文中无特殊说明，提到的“矩形”都指满足“最少”和“完整”要求的。）</p>
<p>由于表格的高已经确定为2这个很小的值，我们可以根据每一列的损失数来划分阶段。我们用(i,j,k)来表示一个状态。其中i表示当前状态为第i列，用于阶段划分；k表示此次决策前已经得到的矩形个数，用以在状态转移时限制矩形的个数；j表示要决策列的前一列中的灭火器使用情况，用于在状态转移时更新矩形的数目（后面我们可以看到当前列的决策是否导致新矩形的产生完全取决于前一列的灭火器使用情况）。</p>
<p>选择不同的递推方向会导致不同的状态定义和转移方程。这里我们选择顺推，因为每次决策都会唯一对应一个状态后继，这样转移方程写起来会简单许多。</p>
<p>我们的状态定义为：dp(i,j,k)表示前i列已经有k个矩形、第i列的状态为j之后还能获得的最小损失。转移方程为：dp(i,j,k)=min{dp(i+1,jj,kk)+loss(i+1,jj)}。其中jj表示决策第i+1行灭火器的使用情况。kk为因此得到的矩形数目，即若决策jj导致了矩形个数的增加，则kk=k+1，否则kk=k。loss(i,j)为第i列灭火器使用情况为j时的损失。</p>
<p>每一列的灭火器使用情况（j的取值）有4种：0.楼上楼下都不使用灭火器；1.楼上使用；2.楼下使用；3.上下都用。如果我们画两列格子看看，就会发现矩形数目不增加当且仅当jj=0或j==jj。这样我们就可以通过转移方程求出解dp(0,0,0)。</p>
<p><span style="font-weight: bold;">——但是等等！</span></p>
<p><span style="font-weight: bold;"><br />
</span></p>
<p>这就是我第一次写时出现的疏忽（这让我第一次WA了4个点并调试了整整一个晚上）。我们没有注意到当j=3,jj=1（或2，这里以1为例）时可能有以下两种情况：</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/museum-fire-5.jpg" src="http://upload.tomtung.com/img/museum-fire-5.jpg" alt="" width="353" height="70" /></p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/museum-fire-6.jpg" src="http://upload.tomtung.com/img/museum-fire-6.jpg" alt="" width="352" height="70" /></p>
<p>其中前一种如我们所想，jj决策导致了矩形数目的增加，但是后一种情况却不是这样。虽然j=3，jj=1，满足矩形数目增加的条件，但是矩形数目实际并没有增加。这是由于更早些的决策已经使第i行的上下两层分属两个矩形。因此我们需要为上下都使用的决策留出两个j值以示区别。这部分细节请看代码。</p>
<p>这样得到的状态定义和转移方程明显满足最有子结构和无后效性。我们可以据此求出最小损失。</p>
<p>这道题并不要求输出最小损失，而是要求输出灭火器每次使用的覆盖范围。这我们就需要构造最优解。我的方法很朴素。在dp过程中维护一个数组build[i][j][k]，其值为状态(i,j,k)做出的最优决策jj。dp结束后，我们就可以递归地在一个bool数组上标记出那些使用了灭火器的格子。最后用贪心（细节见代码）把它们组合成矩形输出，这样问题就解决了。</p>
<p>这个算法思路比较简单，编程复杂度与官方解相当。至于时空复杂度，由于我没有系统学习过相关理论，不好乱说，ms是空间复杂度比官方解高，但是时间复杂度比官方解低？</p>
<pre class="brush:c++">
#include &lt;iostream&gt;
#include &lt;fstream&gt;
#include &lt;cassert&gt;
using namespace std;
ifstream fin("Input.txt");
ofstream fout("Output.txt");
int room_n,shoot_k,v[3][101],f[3][101];

/*dp(memoization)部分*/
int dp[101][5][11];//[i][j][k]:前i列已经最少有k个完整矩形、第i列的状态为j之后还能获得的最小损失
bool checked[101][5][11];
int memo(int i,int j, int k);
int loss(int i, int j);	//memo调用的子过程，返回第i行决策为j时的净损失

/*最优解的构造部分*/
int build[101][5][11];	//记录(i,j,k)状态下要得到最优解，i+1列的状态
bool room[3][101];
void paint(int i, int j, int k);//在room中标出状态(i,j,k)后的灭火器使用情况

/*最优解的输出部分*/
int counter;//记录发射次数
void output(int i);	//输出矩形

int main()
{
	/*输入部分*/
	fin &gt;&gt; room_n &gt;&gt; shoot_k;
	for(int i=2;i&gt;=1;i--)
		for(int j=1;j&lt;=room_n;j++)
			fin &gt;&gt; v[i][j];
	for(int i=2;i&gt;=1;i--)
		for(int j=1;j&lt;=room_n;j++)
			fin &gt;&gt; f[i][j];
	/*计算部分*/
	unsigned min_loss=memo(0,0,0);
	paint(0,0,0);

	/*输出部分*/
	for(int i=1;i&lt;=room_n;i++)	output(i);
	for(int j=shoot_k-counter;j&gt;=1;j--)
		fout &lt;&lt; "0 0 0 0\n";
	return 0;
}

//
int memo(int i,int j, int k)
{
	if(checked[i][j][k])	return dp[i][j][k];
	if(i==room_n)	return 0;
	dp[i][j][k]=INT_MAX;
	for(int jj=0,kk,ans;jj&lt;=3;jj++)	//枚举第i+1行状态
	{
		if(jj==3&amp;&amp;(j==4||j==1||j==2))	jj++;	//上下都用时的状态修正
		kk=k+!(jj==0||jj==j||j==4);
		if(kk&lt;=shoot_k)
			ans=memo(i+1,jj,kk)+loss(i+1,jj);
		else ans=INT_MAX;
		if(dp[i][j][k]&gt;ans)
		{
			dp[i][j][k]=ans;
			build[i][j][k]=jj;
		}
	}
	assert(dp[i][j][k]&lt;INT_MAX);
	checked[i][j][k]=true;
	return dp[i][j][k];
}
int loss(int i, int j)
{
/*状态j的定义:
	0：上层下层都不使用灭火器；
	1：上使用；2：下使用；3，4：上下都用
	其中3表示与此列相连的使用灭火器的房间最少组成1个完整矩形
	4表示与此列相连的使用灭火器的房间已经最少组成2个完整矩形*/
	switch(j)
	{
		case 0:
			return (f[1][i]+f[2][i]);	//上下都不用
		case 1:
			return(v[2][i]+f[1][i]);	//上用
		case 2:
			return(v[1][i]+f[2][i]);	//下用
		case 3:	//上下都用
		case 4:
			return(v[1][i]+v[2][i]);
	}
	assert(0);
}
//
void paint(int i, int j, int k)
{
	if(i==room_n)	return;
	assert(checked[i][j][k]);
	int jj=build[i][j][k];
	int kk=k+!(jj==0||jj==j||j==4);
	switch(jj)
	{
		case 0:
			break;	//上下都不用
		case 1:
			room[2][i+1]=1; break;	//上用
		case 2:
			room[1][i+1]=1; break;	//下用
		case 3:
		case 4:
			room[1][i+1]=room[2][i+1]=true; break;	//上下都用
		default:
			assert(0);
	}
	paint(i+1,jj,kk);
}
//
void output(int i)
{
	counter++;
	int ii;
	if(room[2][i]&amp;&amp;!room[1][i])	//上用
	{
		fout &lt;&lt; 2 &lt;&lt; ' ' &lt;&lt; i &lt;&lt; ' ';
		for(ii=i;ii&lt;=room_n&amp;&amp;room[2][ii];ii++)	room[2][ii]=0;
		fout &lt;&lt; 2 &lt;&lt; ' ' &lt;&lt; ii-1 &lt;&lt; endl;
	}
	else if(room[1][i]&amp;&amp;!room[2][i])	//下用
	{
		fout &lt;&lt; 1 &lt;&lt; ' ' &lt;&lt; i &lt;&lt; ' ';
		for(ii=i;ii&lt;=room_n&amp;&amp;room[1][ii];ii++)	room[1][ii]=0;
		fout &lt;&lt; 1 &lt;&lt; ' ' &lt;&lt; ii-1 &lt;&lt; endl;
	}
	else if(room[1][i]&amp;&amp;room[2][i])	//上下都用
	{
		fout &lt;&lt; 1 &lt;&lt; ' ' &lt;&lt; i &lt;&lt; ' ';
		for(ii=i;ii&lt;=room_n&amp;&amp;room[1][ii]&amp;&amp;room[2][ii];ii++)	room[1][ii]=room[2][ii]=0;
		fout &lt;&lt; 2 &lt;&lt; ' ' &lt;&lt; ii-1 &lt;&lt; endl;
	}
	else counter--;	//上下都不用
}
//
</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/03/usaco-ordered-fractions/' rel='bookmark' title='[USACO]Ordered Fractions'>[USACO]Ordered Fractions</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/05/museum-fire/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>[NOI97]积木游戏(Game)</title>
		<link>http://blog.tomtung.com/2007/05/noi97-game/</link>
		<comments>http://blog.tomtung.com/2007/05/noi97-game/#comments</comments>
		<pubDate>Sun, 27 May 2007 10:23:45 +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/05/noi97%e7%a7%af%e6%9c%a8%e6%b8%b8%e6%88%8fgame/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/05/noi97-game/" title="[NOI97]积木游戏(Game)"></a>积木游戏(NOI’97) SERCOI 最近设计了一种积木游戏。每个游戏者有N块编号依次为1 ，2，…，N的长方体积木。对于每块积木,它的三条不同的边分别称为”a边”、“b边”和”c边”，如下图所示： 游戏规则如下： 从N块积木中选出若干块，并将它们分成M（l&#60;=M&#60;=N）堆，称为第1堆，第2 堆…，第M堆。每堆至少有1块积木，并且第K堆中任意一块积木的编号要大于第K+1堆中任意一块积木的编号(2&#60;=K&#60;=M)。 对于每一堆积木,游戏者要将它们垂直摞成一根柱子,并要求满足下面两个条件： 除最顶上的一块积木外，任意一块积木的上表面同且仅同另一块积木的下表面接触，并且要求下面的积木的上表面能包含上面的积木的下表面，也就是说，要求下面的积木的上表面的两对边的长度分别大于等于上面的积木的两对边的长度。 对于任意两块上下表面相接触的积木，下面的积木的编号要小于上面的积木的编号。 最后，根据每人所摞成的M根柱子的高度之和来决出胜负。 请你编一程序，寻找一种摞积木的方案，使得你所摞成的M根柱子的高度之和最大。 输入输出 输入文件是INPUT.TXT。文件的第一行有两个正整数N和M（1&#60;=M&#60;=N&#60;=100），分别表示积木总数和要求摞成的柱子数。这两个数之间用一个空格符隔开。接下来N行依次是编号从1到N的N个积木的尺寸，每行有三个1至1000之间的整数，分别表示该积木a边,b边和c边的长度。同一行相邻两个数之间用一个空格符隔开。 输出文件是OUTPUT.TXT。文件只有一行，为一个整数，表示M根柱子的高度之和。 样例 INPUT.TXT 4 2 10 5 5 8 7 7 2 2 2 6 6 6 OUTPUT.TXT 24 题解 好郁闷，这题我竟然连写带调花了将近一天。。。看来我的基本功的确非常不够。 这题对我的启发还是非常大的。lrj书中讨论了递推方向的选择问题，这是我不曾考虑过的。我以前所有的dp题目都是逆推的，状态表示都是“到此情况下为止能获得的最大/最小值”，我觉得这样表示非常自然。但这题行不通。每一个状态都有很多前趋状态，转移方程写起来非常困难。若顺推则容易得多，因为顺推的情况下每一个状态只对应一个后继。但是顺推的思想让我非常不适应（可见我非常菜）。“此情况以后能获得的最值”，这种表示我仔细体会了很长时间才理解并适应，感觉受益匪浅。 当一个问题满足dp所需的一切但是方程写起来非常麻烦，不妨换个递推方向试试吧。 代码（若看不到请设法绕过gfw……orz）： #include &#60;fstream&#62; &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/05/noi97-game/">继续阅读 &#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/poi-vi-musketeers/' rel='bookmark' title='[POI VI Stage 1 Problem 1] Musketeers'>[POI VI Stage 1 Problem 1] Musketeers</a></li>
<li><a href='http://blog.tomtung.com/2007/02/usaco-mixing-milk/' rel='bookmark' title='[USACO]Mixing Milk'>[USACO]Mixing Milk</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/05/noi97-game/" title="[NOI97]积木游戏(Game)"></a><p style="text-align: center;"><strong>积木游戏(NOI’97)</strong></p>
<p>SERCOI 最近设计了一种积木游戏。每个游戏者有N块编号依次为1 ，2，…，N的长方体积木。对于每块积木,它的三条不同的边分别称为”a边”、“b边”和”c边”，如下图所示：</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/noi97-game-1.jpg" src="http://upload.tomtung.com/img/noi97-game-1.jpg" alt="" width="316" height="190" /></p>
<p>游戏规则如下：</p>
<ol>
<li> 从N块积木中选出若干块，并将它们分成M（l&lt;=M&lt;=N）堆，称为第1堆，第2 堆…，第M堆。每堆至少有1块积木，并且第K堆中任意一块积木的编号要大于第K+1堆中任意一块积木的编号(2&lt;=K&lt;=M)。</li>
<li> 对于每一堆积木,游戏者要将它们垂直摞成一根柱子,并要求满足下面两个条件：</li>
<li style="list-style-type: none;">
<ul>
<li> 除最顶上的一块积木外，任意一块积木的上表面同且仅同另一块积木的下表面接触，并且要求下面的积木的上表面能包含上面的积木的下表面，也就是说，要求下面的积木的上表面的两对边的长度分别大于等于上面的积木的两对边的长度。</li>
<li> 对于任意两块上下表面相接触的积木，下面的积木的编号要小于上面的积木的编号。</li>
</ul>
</li>
</ol>
<p>最后，根据每人所摞成的M根柱子的高度之和来决出胜负。</p>
<p>请你编一程序，寻找一种摞积木的方案，使得你所摞成的M根柱子的高度之和最大。</p>
<p><strong>输入输出</strong></p>
<p>输入文件是INPUT.TXT。文件的第一行有两个正整数N和M（1&lt;=M&lt;=N&lt;=100），分别表示积木总数和要求摞成的柱子数。这两个数之间用一个空格符隔开。接下来N行依次是编号从1到N的N个积木的尺寸，每行有三个1至1000之间的整数，分别表示该积木a边,b边和c边的长度。同一行相邻两个数之间用一个空格符隔开。</p>
<p>输出文件是OUTPUT.TXT。文件只有一行，为一个整数，表示M根柱子的高度之和。</p>
<p><strong>样例</strong></p>
<p>INPUT.TXT</p>
<p>4 2</p>
<p>10 5 5</p>
<p>8 7 7</p>
<p>2 2 2</p>
<p>6 6 6</p>
<p>OUTPUT.TXT</p>
<p>24</p>
<p><strong>题解</strong></p>
<p>好郁闷，这题我竟然连写带调花了将近一天。。。看来我的基本功的确非常不够。</p>
<p>这题对我的启发还是非常大的。lrj书中讨论了递推方向的选择问题，这是我不曾考虑过的。我以前所有的dp题目都是逆推的，状态表示都是“到此情况下为止能获得的最大/最小值”，我觉得这样表示非常自然。但这题行不通。每一个状态都有很多前趋状态，转移方程写起来非常困难。若顺推则容易得多，因为顺推的情况下每一个状态只对应一个后继。但是顺推的思想让我非常不适应（可见我非常菜）。“此情况以后能获得的最值”，这种表示我仔细体会了很长时间才理解并适应，感觉受益匪浅。</p>
<p>当一个问题满足dp所需的一切但是方程写起来非常麻烦，不妨换个递推方向试试吧。</p>
<p>代码（若看不到请设法绕过gfw……orz）：</p>
<pre class="brush:c++">#include &lt;fstream&gt;
using namespace std;
ifstream fin("Input.txt");
ofstream fout("Output.txt");
int N,M;
struct demension{
	unsigned a,b,c;
}demen[101];

int dp[101][101][101][3];
inline int h(int a, int k)
{
	//memo调用的子过程，返回积木a第k面朝上时的高度
	assert(a&gt;0&amp;&amp;a&lt;=N);
	switch(k)
	{
		case 0:	return demen[a].c;
		case 1:	return demen[a].b;
		case 2:	return demen[a].a;
	}
	assert(0);
}
inline bool canput(int a, int k, int aa, int kk)
{
	//memo调用的子过程，返回a的k面能否容纳aa的kk面
	int i,j,ii,jj;
	switch(k)
	{
		case 0:
			i=demen[a].a;
			j=demen[a].b;
			break;
		case 1:
			i=demen[a].a;
			j=demen[a].c;
			break;
		case 2:
			i=demen[a].b;
			j=demen[a].c;
			break;
		default:
			assert(0);
	}
	if(i&lt;j)	swap(i,j);
	switch(kk)
	{
		case 0:
			ii=demen[aa].a;
			jj=demen[aa].b;
			break;
		case 1:
			ii=demen[aa].a;
			jj=demen[aa].c;
			break;
		case 2:
			ii=demen[aa].b;
			jj=demen[aa].c;
			break;
		default:
			assert(0);
	}
	if(ii&lt;jj)	swap(ii,jj);
	return (i&gt;=ii&amp;&amp;j&gt;=jj);
	//这里的题意我开始理解错了，不需要j&gt;=ii。这个错误让我花了一下午调试
}
int memo(int i, int a, int b, int k)
{
	/*已经用前a块积木摆成了i根柱子，顶面积木b的的面k朝上
	之后还能获得的最大高度（决策是否使用a+1块积木、如何使用）*/
	if(dp[i][a][b][k]!=0)	return dp[i][a][b][k];
	if(a==N)	//边界条件
		if(i==M)	return 0;
		else	return INT_MIN;
	int ans=memo(i,a+1,b,k);	//不使用第a+1块积木
	if(i&lt;M)
		for(int kk=0;kk&lt;=2;kk++)
			if(ans&lt;memo(i+1,a+1,a+1,kk)+h(a+1,kk))
				ans=memo(i+1,a+1,a+1,kk)+h(a+1,kk);	//新起一堆
	if(i&gt;0)//这个条件的疏忽让我调试了一早上
		for(int kk=0;kk&lt;=2;kk++)
			if(canput(b,k,a+1,kk)&amp;&amp;ans&lt;memo(i,a+1,a+1,kk)+h(a+1,kk))
				ans=memo(i,a+1,a+1,kk)+h(a+1,kk);	//放在前一堆上
	dp[i][a][b][k]=ans;
	return ans;
}

int main()
{
	fin &gt;&gt; N &gt;&gt; M;
	for(int i=1;i&lt;=N;i++)
		fin &gt;&gt; demen[i].a &gt;&gt; demen[i].b &gt;&gt; demen[i].c;
	demen[0].a=demen[0].b=demen[0].c=1001;
	fout &lt;&lt; memo(0,0,0,0) &lt;&lt; endl;
	return 0;
}
</pre>
<p><a href="http://upload.tomtung.com/img/noi97-game-2.jpg"><img class="alignnone" title="http://upload.tomtung.com/img/noi97-game-2.jpg" src="http://upload.tomtung.com/img/noi97-game-2.jpg" alt="" width="558" height="201" /></a></p>

<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/poi-vi-musketeers/' rel='bookmark' title='[POI VI Stage 1 Problem 1] Musketeers'>[POI VI Stage 1 Problem 1] Musketeers</a></li>
<li><a href='http://blog.tomtung.com/2007/02/usaco-mixing-milk/' rel='bookmark' title='[USACO]Mixing Milk'>[USACO]Mixing Milk</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/05/noi97-game/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[NOI99]棋盘分割(Chessboard Division)</title>
		<link>http://blog.tomtung.com/2007/05/noi99-chess/</link>
		<comments>http://blog.tomtung.com/2007/05/noi99-chess/#comments</comments>
		<pubDate>Sat, 26 May 2007 15:08:13 +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/05/noi99%e6%a3%8b%e7%9b%98%e5%88%86%e5%89%b2chessboard-division/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/05/noi99-chess/" title="[NOI99]棋盘分割(Chessboard Division)"></a>棋盘分割（NOI99） Chessboard Division Chess.{pas&#124;bas&#124;c} Chess.exe 将一个８×８的棋盘进行如下分割：将原棋盘割下一块矩形棋盘并使剩下部分也是矩形，再将剩下的部分继续如此分割，这样割了(n-1)次后，连同最后剩下的矩形棋盘共有n块矩形棋盘。(每次切割都只能沿着棋盘格子的边进行) 原棋盘上每一格有一个分值，一块矩形棋盘的总分为其所含各格分值之和。现在需要把棋盘按上述规则分割成n块矩形棋盘，并使各矩形棋盘总分的均方差最小。 请编程对给出的棋盘及n，求出均方差σ的最小值。 输入 第1行为一个整数n(1 第2行至第9行每行为8个小于100的非负整数，表示棋盘上相应格子的分值。每行相邻两数之间用一个空格分隔。 输出 仅一个数，为σ（四舍五入精确到小数点后三位）。 样例输入 3 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/05/noi99-chess/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<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>
<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/02/usaco-arithmetic-progressions/' rel='bookmark' title='[USACO]Arithmetic Progressions'>[USACO]Arithmetic Progressions</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/05/noi99-chess/" title="[NOI99]棋盘分割(Chessboard Division)"></a><p style="text-align: center;"><span style="font-size: 24px;">棋盘分割（NOI99）<br />
</span>Chessboard Division<br />
Chess.{pas|bas|c}<br />
Chess.exe</p>
<p>将一个８×８的棋盘进行如下分割：将原棋盘割下一块矩形棋盘并使剩下部分也是矩形，再将剩下的部分继续如此分割，这样割了(n-1)次后，连同最后剩下的矩形棋盘共有n块矩形棋盘。(每次切割都只能沿着棋盘格子的边进行)</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/noi99-chess.png" src="http://upload.tomtung.com/img/noi99-chess.png" alt="" width="350" height="178" /></p>
<p>原棋盘上每一格有一个分值，一块矩形棋盘的总分为其所含各格分值之和。现在需要把棋盘按上述规则分割成n块矩形棋盘，并使各矩形棋盘总分的均方差最小。</p>
<p>请编程对给出的棋盘及n，求出均方差σ的最小值。</p>
<p><strong>输入</strong><br />
第1行为一个整数n(1<br />
第2行至第9行每行为8个小于100的非负整数，表示棋盘上相应格子的分值。每行相邻两数之间用一个空格分隔。</p>
<p><strong>输出</strong><br />
仅一个数，为σ（四舍五入精确到小数点后三位）。</p>
<p><strong>样例输入</strong><br />
3<br />
1 1 1 1 1 1 1 3<br />
1 1 1 1 1 1 1 1<br />
1 1 1 1 1 1 1 1<br />
1 1 1 1 1 1 1 1<br />
1 1 1 1 1 1 1 1<br />
1 1 1 1 1 1 1 1<br />
1 1 1 1 1 1 1 0<br />
1 1 1 1 1 1 0 3</p>
<p><strong>样例输出</strong><br />
1.633</p>
<p><strong>题解</strong></p>
<p>现在NOI的题做起来还是很费事……那个sigma的式子的化简则完全是参照lrj的牛书完成的，好失败……</p>
<p>中间调试了相当一段时间，都是由于些弱智错误……囧</p>
<pre class="brush:c++">
#include &lt;fstream&gt;
#include &lt;cassert&gt;
#include &lt;cmath&gt;
#include &lt;iomanip&gt;
using namespace std;
ifstream fin("chess.in");
ofstream fout("chess.out");
int N;

unsigned score[8][8][8][8];
bool scored[8][8][8][8];
unsigned get_score(int x1, int y1, int x2, int y2)
{
   assert(x1&lt;=x2&amp;&amp;y1&lt;=y2);
   if(scored[x1][y1][x2][y2])   return score[x1][y1][x2][y2];
   scored[x1][y1][x2][y2]=true;
   unsigned sum=0;
   for(int x=x1;x&lt;=x2;x++)
      for(int y=y1;y&lt;=y2;y++)
         sum+=score[x][y][x][y];
   score[x1][y1][x2][y2]=sum;
   return sum;
}

unsigned dp[8][8][8][8][15];
bool checked[8][8][8][8][15];
unsigned memo(int x1, int y1, int x2, int y2, int n)
{
   assert(x1&lt;=x2&amp;&amp;y1&lt;=y2);
   if(checked[x1][y1][x2][y2][n])
      return dp[x1][y1][x2][y2][n];
   checked[x1][y1][x2][y2][n]=true;
   if(n==1)
   {
      dp[x1][y1][x2][y2][n]
                =get_score(x1,y1,x2,y2)*get_score(x1,y1,x2,y2);
      return dp[x1][y1][x2][y2][n];
   }

   unsigned ans=INT_MAX;
   for(int x=x1,m;x&lt;x2;x++)
   {
      m=memo(x+1,y1,x2,y2,n-1);
      if(m&lt;INT_MAX)
         ans=min(ans,get_score(x1,y1,x,y2)*get_score(x1,y1,x,y2)
            +memo(x+1,y1,x2,y2,n-1));
      m=memo(x1,y1,x,y2,n-1);
      if(m&lt;INT_MAX)
         ans=min(ans,memo(x1,y1,x,y2,n-1)
            +get_score(x+1,y1,x2,y2)*get_score(x+1,y1,x2,y2));
   }
   for(int y=y1,m;y&lt;y2;y++)
   {
      m=memo(x1,y+1,x2,y2,n-1);
      if(m&lt;UINT_MAX)
         ans=min(ans,get_score(x1,y1,x2,y)*get_score(x1,y1,x2,y)
            +memo(x1,y+1,x2,y2,n-1));
      m=memo(x1,y1,x2,y,n-1);
      if(m&lt;UINT_MAX)
         ans=min(ans,memo(x1,y1,x2,y,n-1)
            +get_score(x1,y+1,x2,y2)*get_score(x1,y+1,x2,y2));
   }
   dp[x1][y1][x2][y2][n]=ans;
   return ans;
}

int main()
{
   fin &gt;&gt; N;
   for(int y=0;y&lt;=7;y++)
      for(int x=0;x&lt;=7;x++)
      {
         fin &gt;&gt; score[x][y][x][y];
         scored[x][y][x][y]=true;
      }
   double x_=double(get_score(0,0,7,7))/N;
   fout &lt;&lt; fixed &lt;&lt; setprecision(3)
      &lt;&lt; sqrt(double(memo(0,0,7,7,N))/N-x_*x_) &lt;&lt; endl;
   return 0;
}
</pre>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<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>
<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/02/usaco-arithmetic-progressions/' rel='bookmark' title='[USACO]Arithmetic Progressions'>[USACO]Arithmetic Progressions</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/05/noi99-chess/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[POI VI Stage 1 Problem 1] Musketeers</title>
		<link>http://blog.tomtung.com/2007/05/poi-vi-musketeers/</link>
		<comments>http://blog.tomtung.com/2007/05/poi-vi-musketeers/#comments</comments>
		<pubDate>Wed, 23 May 2007 13:24:56 +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/05/poi-vi-stage-1-problem-1-musketeers/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/05/poi-vi-musketeers/" title="[POI VI Stage 1 Problem 1] Musketeers"></a>POI VI Stage 1 Problem 1 Musketeers In the time of Louis XIII and his powerful minister cardinal Richelieu in the Full Barrel Inn n musketeers had consumed their meal and were drinking wine. Wine had not run short and &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/05/poi-vi-musketeers/">继续阅读 &#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/02/usaco-arithmetic-progressions/' rel='bookmark' title='[USACO]Arithmetic Progressions'>[USACO]Arithmetic Progressions</a></li>
<li><a href='http://blog.tomtung.com/2007/02/usaco-p58-the-clocks/' rel='bookmark' title='[USACO]The Clocks'>[USACO]The Clocks</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/05/poi-vi-musketeers/" title="[POI VI Stage 1 Problem 1] Musketeers"></a><h1 style="text-align: center;"><span>POI VI Stage 1 Problem 1</span></h1>
<h1 style="text-align: center;"><span>Musketeers</span></h1>
<p><span> </span></p>
<p><span>In the time of Louis XIII and his powerful minister cardinal Richelieu in the Full Barrel Inn n musketeers had consumed their meal and were drinking wine. Wine had not run short and therefore the musketeers were eager to quarrel, a drunken brawl broke out, in which each musketeer insulted all the others.</span></p>
<p><span> </span></p>
<p><span>A duel was inevitable. But who should fight who and in what order? They decided (for the first time since the brawl they had done something together) that they would stay in a circle and draw lots in order. A drawn musketeer fought against his neighbor to the right. A looser &#8220;quit the game&#8221; and to be more precise his corpse was taken away by servants. The next musketeer who stood beside the looser became the neighbor of a winner.</span></p>
<p><span> </span></p>
<p><span>After years, when historians read memories of the winner they realized that a final result depended in a crucial extent on the order of duels. They noticed that a fence practice had indicated, who against who could win a duel. It appeared that (in mathematical language) the relation &#8220;A wins B&#8221; was not transitive! It could happen that the musketeer A fought better than B, B better than C and C better than A. Of course, among three of them the first duel influenced the final result. If A and B fight as the first, C wins eventually. But if B and C fight as the first, A wins finally. Historians fascinated by their discovery decided to verify which musketeers could survive. The fate of France and the whole civilized Europe indeed depended on that!</span></p>
<p><span> </span></p>
<p><strong><span>Task</span></strong></p>
<p><span>N persons with consecutive numbers from 1 to n stay in a circle. They fight n-1 duels. In the first round one of these persons (e.g. with the number i) fights against its neighbor to the right, i.e. against the person numbered i+1 (or, if i=n, against the person numbered 1). A looser quits the game, and the circle is tighten so that the next person in order becomes a winner&#8217;s neighbor. We are given the table with possible duels results, in the form of a matrix. If Ai,j = 1 then the person with the number i always wins with the person j. If Ai,j = 0 the person i looses with j. We can say that the person k may win the game if there exists such a series of n-1 drawings, that k wins the final duel.</span></p>
<p><span> </span></p>
<p><span>Write a program which:</span></p>
<p><span>l        reads matrix A from the text file MUS.IN,</span></p>
<p><span>l        computes numbers of persons, who may win the game,</span></p>
<p><span>l        writes them into the text file MUS.OUT.</span></p>
<p><span> </span></p>
<p><strong><span>Input</span></strong></p>
<p><span>In the first line of the text file MUS.IN integer n which satisfies the inequality 3&lt;=n&lt;=100 is written. In each of the following n lines appears one word consisting of n digits 0 or 1. A digit on j-th position in i-th line denote Ai,j. Of course Ai,j = 1 &#8211; Aj,i, for i&lt;&gt;j. We assume that Ai,i = 1, for each i.</span></p>
<p><span> </span></p>
<p><strong><span>Output</span></strong></p>
<p><span>In the first line of the output file MUS.OUT there should be written m &#8211; the number of persons, who may win the game. In the following m lines numbers of these persons should be written in ascending order, one number in each line.</span></p>
<p><span> </span></p>
<p><strong><span>Sample Input</span></strong></p>
<p><span>7</span></p>
<p><span>1111101</span></p>
<p><span>0101100</span></p>
<p><span>0111111</span></p>
<p><span>0001101</span></p>
<p><span>0000101</span></p>
<p><span>1101111</span></p>
<p><span>0100001</span></p>
<p><span> </span></p>
<p><strong><span>Sample Output</span></strong></p>
<p><span>3</span></p>
<p><span>1</span></p>
<p><span>3</span></p>
<p><span>6</span></p>
<p><span> </span></p>
<p><strong><span>Remark</span></strong></p>
<p><span>The order of duels: 1-2, 1-3, 5-6, 7-1, 4-6, 6-1 gives a final victory to the per also check that only two persons more (1 and 3) may win the game.</span></p>
<p><span> </span></p>
<p><strong><span>Solution</span></strong></p>
<p>调了好几遍，环有时总是处理不好，郁闷……</p>
<p>代码：</p>
<pre class="brush:c++">
/*
ID: tom.tun1
PROG: mus
LANG: C++
*/
#include &lt;iostream&gt;
#include &lt;fstream&gt;
using namespace std;
ifstream fin("mus.in");
ofstream fout("mus.out");
bool checked[101][101],dp[101][101],win[101][101];
int N;
bool memo(int i, int j)
{
	if(checked[i][j])	return dp[i][j];
	int ii=i,jj=j;
	if(i==N)	ii=0;
	if(ii&gt;=j)	jj+=N;
	if(jj-ii==1)	return(dp[i][j]=checked[i][j]=1);
	for(int kk=ii+1,k;kk&lt;jj&amp;&amp;!dp[i][j];kk++)
	{
		if(kk&gt;N)	k=kk-N;
		else k=kk;
		dp[i][j]=	memo(i,k)&amp;&amp;memo(k,j)
					&amp;&amp;(win[i][k]||win[j][k]);
	}
	checked[i][j]=true;
	return dp[i][j];
}
int main()
{
	fin &gt;&gt; N;
	for(int i=1;i&lt;=N;i++)
	{
		char buf=fin.get();
		for(int j=1;j&lt;=N;j++)
			win[i][j]=((buf=fin.get())=='1');
	}

	int ans[100],ans_num=0;
	for(int i=1;i&lt;=N;i++)
		if(memo(i,i))	ans[ans_num++]=i;
	fout &lt;&lt; ans_num &lt;&lt; endl;
	for(int i=0;i&lt;ans_num;i++)
		fout &lt;&lt; ans[i] &lt;&lt; endl;
	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/02/usaco-arithmetic-progressions/' rel='bookmark' title='[USACO]Arithmetic Progressions'>[USACO]Arithmetic Progressions</a></li>
<li><a href='http://blog.tomtung.com/2007/02/usaco-p58-the-clocks/' rel='bookmark' title='[USACO]The Clocks'>[USACO]The Clocks</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/05/poi-vi-musketeers/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>[USACO]Ordered Fractions</title>
		<link>http://blog.tomtung.com/2007/03/usaco-ordered-fractions/</link>
		<comments>http://blog.tomtung.com/2007/03/usaco-ordered-fractions/#comments</comments>
		<pubDate>Sat, 03 Mar 2007 07:50:56 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[USACO]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/03/usaco-p64-ordered-fractions/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/03/usaco-ordered-fractions/" title="[USACO]Ordered Fractions"></a>Ordered Fractions Consider the set of all reduced fractions between 0 and 1 inclusive with denominators less than or equal to N. Here is the set when N = 5: 0/1 1/5 1/4 1/3 2/5 1/2 3/5 2/3 3/4 4/5 &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/03/usaco-ordered-fractions/">继续阅读 &#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/02/usaco-arithmetic-progressions/' rel='bookmark' title='[USACO]Arithmetic Progressions'>[USACO]Arithmetic Progressions</a></li>
<li><a href='http://blog.tomtung.com/2007/02/usaco-milking-cows/' rel='bookmark' title='[USACO]Milking Cows / [Vijos1165]火烧赤壁'>[USACO]Milking Cows / [Vijos1165]火烧赤壁</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/03/usaco-ordered-fractions/" title="[USACO]Ordered Fractions"></a><p style="text-align: center;"><strong>Ordered Fractions</strong></p>
<p>Consider the set of all reduced fractions between 0 and 1 inclusive with denominators less than or equal to N.<br />
Here is the set when N = 5:</p>
<pre> 0/1 1/5 1/4 1/3 2/5 1/2 3/5 2/3 3/4 4/5 1/1
</pre>
<p>Write a program that, given an integer N between 1 and 160 inclusive, prints the fractions in order of increasing magnitude.</p>
<h3>PROGRAM NAME: frac1</h3>
<h3>INPUT FORMAT</h3>
<p>One line with a single integer N.</p>
<h3>SAMPLE INPUT (file frac1.in)</h3>
<pre> 5
</pre>
<h3>OUTPUT FORMAT</h3>
<p>One fraction per line, sorted in order of magnitude.</p>
<h3>SAMPLE OUTPUT (file frac1.out)</h3>
<pre> 0/1 1/5 1/4 1/3 2/5 1/2 3/5 2/3 3/4 4/5 1/1</pre>
<h3>SOLUTION</h3>
<p>这是一道很好过的题目。我的方法是穷举所有可能的分数，然后排序后输出。就这种最直观最笨的方法也不会超时。代码很好写，这里就略去了。</p>
<p>下面是我在usaco的官方Analysis上看到的解法，它利用递归<strong>直接生成分数并输出，没有互质数判断，没有排序</strong>。真是非常漂亮——虽然其中数学原理我还是有点搞不懂。比如：它怎么保证每次得到的分数分子分母都互质的？两个分数分子分母分别相加的到的分数，为什么它的值一定介于两者之间？希望能有达人仔细解释。</p>
<p>下面是我翻译自usaco的：</p>
<blockquote><p>下面是来自Russ的超快解法：</p>
<p>我们发现可以把0/1和1/1分别作为起点和终点，然后通过增大分子和分母来递归地生成中间的点。</p>
<p><img class="alignnone" title="http://upload.tomtung.com/img/usaco-ordered-fractions.jpg" src="http://upload.tomtung.com/img/usaco-ordered-fractions.jpg" alt="" width="547" height="90" /></p>
<p>每个分数都是由它左边和右边的分数生成的。这种想法有助于在递归过深时跳出。</p>
<pre class="brush:c++">#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;assert.h&gt;

int n;
FILE *fout;

/* 打印在n1/d1和n2/d2之间且分母小于等于n的分数*/
void
genfrac(int n1, int d1, int n2, int d2)
{
  if(d1+d2 &gt; n)  /*跳出递归*/
  return;

  genfrac(n1,d1, n1+n2,d1+d2);
  fprintf(fout, "%d/%d\n", n1+n2, d1+d2);
  genfrac(n1+n2,d1+d2, n2,d2);
}

void
main(void)
{
  FILE *fin;

  fin = fopen("frac1.in", "r");
  fout = fopen("frac1.out", "w");
  assert(fin != NULL &amp;&amp; fout != NULL);

  fscanf(fin, "%d", &amp;n);

  fprintf(fout, "0/1\n");
  genfrac(0,1, 1,1);
  fprintf(fout, "1/1\n");
}
</pre>
</blockquote>
<p>P.S.据查，这个解法利用了法里数列及其相关性质。详细信息参见我找到的资料：<a href="http://www.wikilib.com/wiki/%E6%B3%95%E9%87%8C%E6%95%B0%E5%88%97" target="_blank">资料1</a> <a href="http://mathdb.org/resource_sharing/number_theory/se_farey.pdf" target="_blank">资料2</a></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/02/usaco-arithmetic-progressions/' rel='bookmark' title='[USACO]Arithmetic Progressions'>[USACO]Arithmetic Progressions</a></li>
<li><a href='http://blog.tomtung.com/2007/02/usaco-milking-cows/' rel='bookmark' title='[USACO]Milking Cows / [Vijos1165]火烧赤壁'>[USACO]Milking Cows / [Vijos1165]火烧赤壁</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/03/usaco-ordered-fractions/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[USACO]Arithmetic Progressions</title>
		<link>http://blog.tomtung.com/2007/02/usaco-arithmetic-progressions/</link>
		<comments>http://blog.tomtung.com/2007/02/usaco-arithmetic-progressions/#comments</comments>
		<pubDate>Wed, 21 Feb 2007 08:43:43 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[USACO]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/02/usaco-p31-arithmetic-progressions/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/02/usaco-arithmetic-progressions/" title="[USACO]Arithmetic Progressions"></a>Arithmetic Progressions An arithmetic progression is a sequence of the form a, a+b, a+2b, &#8230;, a+nb where n=0,1,2,3,&#8230; . For this problem, a is a non-negative integer and b is a positive integer. Write a program that finds all arithmetic &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/02/usaco-arithmetic-progressions/">继续阅读 &#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/02/usaco-p58-the-clocks/' rel='bookmark' title='[USACO]The Clocks'>[USACO]The Clocks</a></li>
<li><a href='http://blog.tomtung.com/2007/02/usaco-milking-cows/' rel='bookmark' title='[USACO]Milking Cows / [Vijos1165]火烧赤壁'>[USACO]Milking Cows / [Vijos1165]火烧赤壁</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/02/usaco-arithmetic-progressions/" title="[USACO]Arithmetic Progressions"></a><h1 style="text-align: center;"><strong>Arithmetic Progressions</strong></h1>
<p>An arithmetic progression is a sequence of the form a, a+b, a+2b, &#8230;, a+nb where n=0,1,2,3,&#8230; . For this problem, a is a non-negative integer and b is a positive integer.</p>
<p>Write a program that finds all arithmetic progressions of length n in the set S of bisquares. The set of bisquares is defined as the set of all integers of the form p<sup>2</sup> + q<sup>2</sup> (where p and q are non-negative integers).</p>
<h3>TIME LIMIT: 5 secs</h3>
<h3>PROGRAM NAME: ariprog</h3>
<h3>INPUT FORMAT</h3>
<table border="1">
<tbody>
<tr>
<td>Line 1:</td>
<td>N (3 &lt;= N &lt;= 25), the length of progressions for which to search</td>
</tr>
<tr>
<td>Line 2:</td>
<td>M (1 &lt;= M &lt;= 250), an upper bound to limit the search to the bisquares with 0 &lt;= p,q &lt;= M.</td>
</tr>
</tbody>
</table>
<h3>SAMPLE INPUT (file ariprog.in)</h3>
<pre> 5 7</pre>
<h3>OUTPUT FORMAT</h3>
<p>If no sequence is found, a singe line reading `NONE&#8217;. Otherwise, output one or more lines, each with two integers: the first element in a found sequence and the difference between consecutive elements in the same sequence. The lines should be ordered with smallest-difference sequences first and smallest starting number within those sequences first.</p>
<p>There will be no more than 10,000 sequences.</p>
<h3>SAMPLE OUTPUT (file ariprog.out)</h3>
<pre> 1 4 37 4 2 8 29 8 1 12 5 12 13 12 17 12 5 20 2 24</pre>
<h3>SOLUTION</h3>
<p>这道题就是简单的穷举，但是搞不好就超时。下面是星牛的解析：</p>
<blockquote><p>这道题就是暴力搜索，时限是5s，方法是很简单的：枚举所有的可能解，没有剪枝的。</p>
<p>但是在编程细节上要注意，很多时候你的程序复杂度没有问题，但常数过大就决定了你的超时（比如说，你比别人多赋值一次，这在小数据时根本没有区别，但对于1个运行5s的大数据，你可能就要用10s甚至更多）。</p>
<p>具体来说，预处理把所有的bisquare算出来，用bene[i]记录i是否是bisquare，另外为了加速，用list记录所有的 bisquare（除去中间的空位置，这在对付大数据时很有用），list中的数据要有序。</p>
<p>然后枚举list中的数，把较小的作为起点，两数的差作为公差，接下来就是用bene判断是否存在n个等差数，存在的话就存入path中，最后排序输出。</p>
<p>运行时间：case 8 超时 （case 7 4.xs）</p>
<p>我一度曾抱怨Pascal的代码不够高效，想用c++编，但用不惯，于是又拐回Pascal来。</p>
<p>费时最多的地方是枚举list中的数，所以对这个地方的代码加一些小修改，情况就会不一样：</p>
<p>1.在判断是否存在n个等差数时，从末尾向前判断（这个不是主要的）。</p>
<p>2.在枚举list中的数时，假设为i,j，那么如果list[i]+(list[j]-list[i])×(n-1)&gt;lim（lim是最大可能的bisquare），那么对于之后的j肯定也是大于lim的，所以直接break掉。（这个非常有效）</p>
<p>AC，最大数据1.4xs。</p></blockquote>
<p>事实上我第一次写就和这个完全一样。但是就是AC不了：第八个点开始时间就超过5s了……狂ft了一阵，试着加了些优化，还是不行，晕啊~~明明和人家的做法一样怎么就是不过呢？莫非数据改了？还是rob这会子在测评机上玩魔兽，导致速度变慢？</p>
<p>百思不得其解后，我找了个牛的源码仔细看了看，恍然大悟。星牛所说的利用 list[i]+(list[j]-list[i])×(n-1)&gt;lim 进行剪枝应该是放在每次check之前。如果已经大于最大值了，则直接跳出循环，对下一个首项进行穷举。而我则是放到check之内，仅仅避免了当前枚举的首项和第二项中的计算，没有想到再继续在首项不变的情况下枚举第二项已经没有意义了。</p>
<p>就是这样一个小小的差别，导致了高达4s的差距。真是细节决定成败！</p>
<p>源码：</p>
<pre class="brush:c++">/*
ID:tom.tun1
PROG:ariprog
LANG:C++
*/
#include &lt;iostream&gt;
#include &lt;fstream&gt;
#include &lt;ctime&gt;
using namespace std;
ifstream fin("ariprog.in");
ofstream fout("ariprog.out");
int prog_len, max_num;
bool set[125001] =
{
  0
};
int bsq[31375], bsq_num = 0, max_bsq;
int ans_num = 0;
struct Answer
{
  int a;
  int b;
}ans[40000];
int
cmp(const void* a, const void* b)
{
  return (*(int *) a - *(int *) b);
}
int
cmp2(const void* a, const void* b)
{
  struct Answer* aa = (Answer*) a;
  struct Answer* bb = (Answer*) b;
  if ((aa-&gt;b) != (bb-&gt;b))
      return((aa-&gt;b) - (bb-&gt;b));
  else
      return((aa-&gt;a) - (bb-&gt;a));
}
void
check(int a, int b)/*给出首项a和公差b，检查等差数列长度并处理*/
{
  for (int i = 0; i &lt; prog_len / 2 + 1; i++)
      if (!set[a + b * i] || !set[a + b * (prog_len - 1 - i)])
          return;
  ans[ans_num].a = a;
  ans[ans_num].b = b;
  ans_num++;
}
int
main()
{
  fin &gt;&gt; prog_len &gt;&gt; max_num;
  max_bsq = max_num * max_num * 2;
  for (int p = 0; p &lt;= max_num; p++)
      for (int q = p; q &lt;= max_num; q++)
        {
          int n = p* p + q* q;
          if (!set[n])
            {
              set[n] = true;
              bsq[bsq_num++] = n;
            }
        }
  qsort(bsq, bsq_num, sizeof(bsq[0]), cmp);
  for (int i = 0; i &lt; bsq_num - prog_len + 1; i++)
      for (int j = i + 1; j &lt; bsq_num; j++)
        {
          int a = bsq[i], b = bsq[j] - bsq[i];
          if (a + b * (prog_len - 1) &gt; max_bsq)
              break;
          check(a, b);
        }
  if (ans_num == 0)
      fout &lt;&lt; "NONE" &lt;&lt; endl;
  else
    {
      qsort(ans, ans_num, sizeof(ans[0]), cmp2);
      for (int i = 0; i &lt; ans_num; i++)
          fout &lt;&lt; ans[i].a &lt;&lt; ' ' &lt;&lt; ans[i].b &lt;&lt; endl;
    }
  return 0;
}
</pre>

<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/02/usaco-p58-the-clocks/' rel='bookmark' title='[USACO]The Clocks'>[USACO]The Clocks</a></li>
<li><a href='http://blog.tomtung.com/2007/02/usaco-milking-cows/' rel='bookmark' title='[USACO]Milking Cows / [Vijos1165]火烧赤壁'>[USACO]Milking Cows / [Vijos1165]火烧赤壁</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/02/usaco-arithmetic-progressions/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>[USACO]The Clocks</title>
		<link>http://blog.tomtung.com/2007/02/usaco-p58-the-clocks/</link>
		<comments>http://blog.tomtung.com/2007/02/usaco-p58-the-clocks/#comments</comments>
		<pubDate>Sun, 18 Feb 2007 18:22:56 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[USACO]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/02/usaco-p58-the-clocks/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/02/usaco-p58-the-clocks/" title="[USACO]The Clocks"></a>The Clocks IOI&#8217;94 &#8211; Day 2 Consider nine clocks arranged in a 3&#215;3 array thusly: &#124;-------&#124; &#124;-------&#124; &#124;-------&#124; &#124; &#124; &#124; &#124; &#124; &#124; &#124; &#124;---O &#124; &#124;---O &#124; &#124; O &#124; &#124; &#124; &#124; &#124; &#124; &#124; &#124;-------&#124; &#124;-------&#124; &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/02/usaco-p58-the-clocks/">继续阅读 &#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/02/usaco-milking-cows/' rel='bookmark' title='[USACO]Milking Cows / [Vijos1165]火烧赤壁'>[USACO]Milking Cows / [Vijos1165]火烧赤壁</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/02/usaco-p58-the-clocks/" title="[USACO]The Clocks"></a><div style="text-align: center;"><strong>The Clocks</strong><br />
<strong>IOI&#8217;94 &#8211; Day 2</strong></div>
<div>Consider nine clocks arranged in a 3&#215;3 array thusly:</div>
<div>
<pre> |-------|    |-------|    |-------|
 |       |    |       |    |   |   |
 |---O   |    |---O   |    |   O   |
 |       |    |       |    |       |
 |-------|    |-------|    |-------|
     A            B            C

 |-------|    |-------|    |-------|
 |       |    |       |    |       |
 |   O   |    |   O   |    |   O   |
 |   |   |    |   |   |    |   |   |
 |-------|    |-------|    |-------|
     D            E            F

 |-------|    |-------|    |-------|
 |       |    |       |    |       |
 |   O   |    |   O---|    |   O   |
 |   |   |    |       |    |   |   |
 |-------|    |-------|    |-------|
     G            H            I
</pre>
<p>The goal is to find a minimal sequence of moves to return all the dials to 12 o&#8217;clock. Nine different ways to turn the dials on the clocks are supplied via a table below; each way is called a move. Select for each move a number 1 through 9 which will cause the dials of the affected clocks (see next table) to be turned 90 degrees clockwise.</p>
<table border="1">
<tbody>
<tr>
<td>Move</td>
<td>Affected clocks</td>
</tr>
<tr>
<td align="middle">1</td>
<td align="middle">ABDE</td>
</tr>
<tr>
<td align="middle">2</td>
<td align="middle">ABC</td>
</tr>
<tr>
<td align="middle">3</td>
<td align="middle">BCEF</td>
</tr>
<tr>
<td align="middle">4</td>
<td align="middle">ADG</td>
</tr>
<tr>
<td align="middle">5</td>
<td align="middle">BDEFH</td>
</tr>
<tr>
<td align="middle">6</td>
<td align="middle">CFI</td>
</tr>
<tr>
<td align="middle">7</td>
<td align="middle">DEGH</td>
</tr>
<tr>
<td align="middle">8</td>
<td align="middle">GHI</td>
</tr>
<tr>
<td align="middle">9</td>
<td align="middle">EFHI</td>
</tr>
</tbody>
</table>
<h3>Example</h3>
<p>Each number represents a time accoring to following table:</p>
<pre>9 9 12          9 12 12       9 12 12        12 12 12         12 12 12
6 6 6   5 -&gt;    9  9  9   8-&gt; 9  9  9   4 -&gt; 12  9  9   9 -&gt;  12 12 12
6 3 6           6  6  6       9  9  9        12  9  9         12 12 12
</pre>
<p>[But this might or might not be the `correct' answer; see below.]</p>
<h3>PROGRAM NAME: clocks</h3>
<h3>INPUT FORMAT</h3>
<table border="1">
<tbody>
<tr>
<td>Lines 1-3:</td>
<td>Three lines of three space-separated numbers; each number represents the start time of one clock, 3, 6, 9, or 12. The ordering of the numbers corresponds to the first example above.</td>
</tr>
</tbody>
</table>
<h3>OUTPUT FORMAT</h3>
<p>A single line that contains a space separated list of the shortest sequence of moves (designated by numbers) which returns all the clocks to 12:00. If there is more than one solution, print the one which gives the lowest number when the moves are concatenated (e.g., 5 2 4 6 &lt; 9 3 1 1).</p>
</div>
<h3>SOLUTION</h3>
<p><strong>1.广度优先搜索</strong></p>
<p>这道题叙述感觉很像最少步数问题，因此我第一个想到的就是BFS。开始的思路是这样的：搜索树为一个&#8221;九叉树&#8221;，从根节点到每一个节点的路径代表一个操作序列。每一个节点扩展出9个节点。当在某一深度找到可行解时，则完成该深度所有节点的检查，取其中操作序列连接的数字最小的为答案。</p>
<p>但是很明显，这个算法在时间复杂度和空间复杂度上都不可接受。</p>
<p>我们又发现：1.任何一个操作执行4次就等于没有执行；2.同一个操作序列，各个操作的顺序改变不影响整个序列的操作效果。</p>
<p>根据以上两点，我们可以进行如下改进：1.任何操作不超过3次；2.为了避免同一操作序列由于顺序不同造成的节点重复，我们每次扩展节点时，都只对操作号大于当前操作号的操作进行尝试。</p>
<p>这样，我们就得到了一棵小得多的搜索树。这棵树的最大深度为3*9=27，节点数为4的9次方即262144，根节点到任意节点路径所代表的操作序列都是不重复的。（要知道原来的搜索树有无限的深度和节点数，且充满了重复）。而且我们发现，由于节点的生成方式，我们通过bfs找到的第一个可行解必然就是最优解。</p>
<div>bfs另一个麻烦的问题是内存空间。我把储存节点的struct中int改成了short int，然后又改成了char，算了算，一个节点占49字节，满足要求，这才过的。说实话，我是第一次这么一个字节一个字节地锱铢必较。源码如下：</div>
<pre class="brush:c++">/*
ID:tom.tun1
PROG:clocks
LANG:C++
*/
#include &lt;iostream&gt;
#include &lt;fstream&gt;
#include &lt;cstring&gt;
using namespace std;
ifstream fin("clocks.in");
ofstream fout("clocks.out");
struct
{
  char Time[9];//九个钟表的状态：0,3,6,9
  char depth;//当前节点的深度
  char num[28];//记录到达该节点的各个操作编号
  char operate_times[9 + 1];//operate_times[i]的值为已经进行i操作的次数
  char operation;//记录直接得到本节点的操作
}Clocks[270000];
const char operate[9 + 1][6] =
{
  /*每个操作影响的钟表号，每个操作的序列以-1结尾*/
  0, 0, 0, 0, 0, 0, 0, 1, 3, 4, -1, 0, 0, 1, 2, -1, 0, 0, 1, 2, 4, 5, -1, 0,
  0, 3, 6, -1, 0, 0, 1, 3, 4, 5, 7, -1, 2, 5, 8, -1, 0, 0, 3, 4, 6, 7, -1, 0,
  6, 7, 8, -1, 0, 0, 4, 5, 7, 8, -1, 0
};

int
main()
{
  /*输入及初始化*/
  for (int i = 0,c; i &lt; 9; i++)
    {
      fin &gt;&gt; c;
      if (c == 12)
          c = 0;
      Clocks[0].Time[i] = c + '0';
    }
  Clocks[0].depth = 0;
  Clocks[0].operation = 1;
  memset(Clocks[0].operate_times, 0, sizeof(Clocks[0].operate_times));

  /*BFS*/
  int head = 0, tail = 1;/*首尾指针*/
  bool flag = true;
  while (flag &amp;&amp; head &lt; tail)
    {
      for (int i = Clocks[head].operation;
           i &lt;= 9;
           i++)/*生成的i对应操作的编号*/
        {
          /*如果某操作执行超过3次则取消本次循环*/
          if (Clocks[head].operate_times[i] + 1 &gt; 3)
              continue;

          /*产生节点*/
          strcpy(Clocks[tail].Time, Clocks[head].Time);/*Time[9]部份*/
          for (int j = 0; operate[i][j] != -1; j++)
            {
              int n = operate[i][j];
              if (Clocks[head].Time[n] == '9')
                  Clocks[tail].Time[n] = '0';
              else
                  Clocks[tail].Time[n] = Clocks[head].Time[n] + 3;
            }
          Clocks[tail].depth = Clocks[head].depth + 1;/*depth部份*/
          strcpy(Clocks[tail].num, Clocks[head].num);/*num[]部份*/
          Clocks[tail].num[Clocks[head].depth] = i + '0';
          /*operate_times[]部份*/
          memcpy(Clocks[tail].operate_times, Clocks[head].operate_times,
                 sizeof(Clocks[0].operate_times));
          Clocks[tail].operate_times[i]++;
          Clocks[tail].operation = i;/*operation部份*/
          /*节点产生完毕*/

          /*判断是否达到目标节点*/
          flag = false;
          for (int j = 0; j &lt; 9; j++)
              if (Clocks[tail].Time[j] != '0')
                {
                  flag = true;
                  break;
                }
          if (!flag)
              break;
          tail++;
        }
      head++;
    }

  /*输出答案*/
  for (int i = 0; i &lt; strlen(Clocks[tail].num); i++)
    {
      if (i == strlen(Clocks[tail].num) - 1)
          fout &lt;&lt; Clocks[tail].num[i] &lt;&lt; endl;
      else
          fout &lt;&lt; Clocks[tail].num[i] &lt;&lt; ' ';
    }
  //system("pause");
  return 0;
}
</pre>
<p><strong>2.深度优先搜索</strong></p>
<p>当我们得到了上面那棵搜索树后，我们发现由于深度和节点数都不大，DFS是完全可行的，而且更易于编写，省去了节省内存空间的麻烦。如同ghost所说，此题穷举才是正解。我们搜索过程中不断更新得到的最优解，当整棵树都遍历后就输出答案。可见此算法耗时相当稳定。我这里根据深度进行了小小的剪枝，完美AC。有牛说其实完全可以不要任何剪枝的，时间就可以稳定在0.3s。</p>
<p>源码如下：</p>
<pre class="brush:c++">/*
ID:tom.tun1
PROG:clocks
LANG:C++
*/
#include &lt;iostream&gt;
#include &lt;fstream&gt;
using namespace std;
ifstream fin("clocks.in");
ofstream fout("clocks.out");
struct Clocks
{
  char Time[9];//九个钟表的状态：0,3,6,9
  int depth;//当前节点的深度
  char num[28];//记录到达该节点的各个操作编号
  int operate_times[9 + 1];//operate_times[i]的值为已经进行i操作的次数
}FirstClock;
const char operate[9 + 1][6] =
{
  /*每个操作影响的钟表号，每个操作的序列以-1结尾*/
  0, 0, 0, 0, 0, 0, 0, 1, 3, 4, -1, 0, 0, 1, 2, -1, 0, 0, 1, 2, 4, 5, -1, 0,
  0, 3, 6, -1, 0, 0, 1, 3, 4, 5, 7, -1, 2, 5, 8, -1, 0, 0, 3, 4, 6, 7, -1, 0,
  6, 7, 8, -1, 0, 0, 4, 5, 7, 8, -1, 0
};
char min_num[28], min_depth = 30;
void
search(struct Clocks Clock, int i)//DFS递归函数，Clock为当前节点，i操作编?
{
  /*节点产生*/
  struct Clocks NextClock;
  /*depth部分*/
  NextClock.depth = Clock.depth + 1;
  if (NextClock.depth &gt; min_depth)
      return;
  /*num部分*/
  strcpy(NextClock.num, Clock.num);
  NextClock.num[NextClock.depth - 1] = i + '0';
  /*Time[9]部分*/
  strcpy(NextClock.Time, Clock.Time);
  for (int j = 0; operate[i][j] != -1; j++)
    {
      int n = operate[i][j];
      if (Clock.Time[n] == '9')
          NextClock.Time[n] = '0';
      else
          NextClock.Time[n] = Clock.Time[n] + 3;
    }
  /*operate_times部分*/
  memcpy(NextClock.operate_times, Clock.operate_times,
         sizeof(Clock.operate_times));
  if (++NextClock.operate_times[i] &gt; 3)
      return;

  /*检查是否为目标节点*/
  if (strcmp(NextClock.Time, "000000000") == 0)
    {
      /*更新min_depth和min_num的值*/
      if (NextClock.depth &lt; min_depth)
        {
          strcpy(min_num, NextClock.num);
          min_depth = NextClock.depth;
          return;
        }
    }

  /*递归*/
  for (int j = i; j &lt;= 9; j++)
      search(NextClock, j);
}
int
main()
{
  /*输入及初始化*/
  for (int i = 0,c; i &lt; 9; i++)
    {
      fin &gt;&gt; c;
      if (c == 12)
          c = 0;
      FirstClock.Time[i] = c + '0';
    }
  FirstClock.depth = 0;
  memset(FirstClock.operate_times, 0, sizeof(FirstClock.operate_times));

  /*DFS*/
  for (int i = 1; i &lt;= 9; i++)
      search(FirstClock, i);

  /*输出答案*/
  for (int i = 0; i &lt; min_depth; i++)
    {
      if (i == min_depth - 1)
          fout &lt;&lt; min_num[i] &lt;&lt; endl;
      else
          fout &lt;&lt; min_num[i] &lt;&lt; ' ';
    }
  return 0;
}
</pre>
<p><strong>3.特殊方法</strong></p>
<p>上面两种方法只是常规的，不足为奇。下面贴出我翻译自USACO官方Analysis的两种方法。非常精彩。</p>
<blockquote><p><strong>Lucian Boca提交了一种常数时间的解法</strong></p>
<p>你可以预先计算一个如下的矩阵：</p>
<p>a[i][0],a[i][1],&#8230;.,a[i][8]是“仅仅”将第i个钟表（0&lt;=i&lt;=8，共有9个钟表：0=A, 1=B, &#8230; 8=I）顺时针转动90度所必须的操作1、2、3&#8230;9的执行次数。这样你就得到了一个矩阵：</p>
<p>int a[9][9]= { {3,3,3,3,3,2,3,2,0},<br />
{2,3,2,3,2,3,1,0,1},<br />
{3,3,3,2,3,3,0,2,3},<br />
{2,3,1,3,2,0,2,3,1},<br />
{2,3,2,3,1,3,2,3,2},<br />
{1,3,2,0,2,3,1,3,2},<br />
{3,2,0,3,3,2,3,3,3},<br />
{1,0,1,3,2,3,2,3,2},<br />
{0,2,3,2,3,3,3,3,3} };</p>
<p>这意味着<strong>仅仅</strong>将钟表0（钟表A）顺时针转动90度，你必须执行{3,3,3,3,3,2,3,2,0}，即操作1执行3次，操作2执行3次，&#8230;，操作8执行2次，操作9执行0次，等等。</p>
<p><strong>仅仅</strong>将钟表8（钟表I）顺时针转动90度，你必须执行{0,2,3,2,3,3,3,3,3}：操作1执行0次，操作2执行2次&#8230;操作9执行3次&#8230;</p>
<p>没错！你可以在一个数组v[9]里记录每一种必须执行操作的次数，答案就是它取4的模（任何一种操作执行5次与执行1次效果一样）。源码：</p>
<pre class="brush:c">
#include &lt;stdio.h&gt;

int a[9][9]= { {3,3,3,3,3,2,3,2,0},
               {2,3,2,3,2,3,1,0,1},
               {3,3,3,2,3,3,0,2,3},
               {2,3,1,3,2,0,2,3,1},
               {2,3,2,3,1,3,2,3,2},
               {1,3,2,0,2,3,1,3,2},
               {3,2,0,3,3,2,3,3,3},
               {1,0,1,3,2,3,2,3,2},
               {0,2,3,2,3,3,3,3,3} };
int v[9];

int main() {
    int i,j,k;
    freopen(&quot;clocks.in&quot;,&quot;r&quot;,stdin);
    for (i=0; i&lt;9; i++) {
        scanf(&quot;%d&quot;,&amp;k);
        for(j=0; j&lt;9; j++)
             v[j]=(v[j]+(4-k/3)*a[i][j])%4;
    }
    fclose(stdin);

    k=0;
    freopen(&quot;clocks.out&quot;,&quot;w&quot;,stdout);
    for (i=0; i&lt;9; i++)
        for (j=0; j&lt;v[i]; j++)
            if (!k) { printf(&quot;%d&quot;,i+1); k=1; }
            else    printf(&quot; %d&quot;,i+1);
    printf(&quot;\n&quot;);
    fclose(stdout);
    return 0;
}
</pre>
<p><strong>这是来自Sopot Cela的另一种解法</strong>——没有循环，常数时间。但这个实在是极端的复杂：在比赛的环境下想要写对这样一个程序将是一种极限挑战。</p>
<pre class="brush:pascal">
program clocks;
const
  INV : array[3..12] of byte =(1, 0, 0, 2, 0, 0, 3, 0, 0, 0);

var inp, out: text;
    a, b, c, d, e, f, g, h, i: integer;
    ax, bx, cx, dx, ex, fx, gx, hx, ix,l: integer;
    t,an: array[1..9] of integer;
begin
    assign (inp, 'clocks.in'); reset (inp);
    readln(inp, ax, bx, cx);
    readln(inp, dx, ex, fx);
    readln(inp, gx, hx, ix);
    a:=inv[ax]; b:=inv[bx]; c:=inv[cx]; d:=inv[dx];
    e:=inv[ex]; f:=inv[fx]; g:=inv[gx]; h:=inv[hx];
    i:=inv[ix];
    t[1] := (8+a+2*b+c+2*d+2*e-f+g-h) mod 4;
    t[2] := (a+b+c+d+e+f+2*g+    2*i) mod 4;
    t[3] := (8+  a+2*b+  c  -d+2*e+2*f      -h+  i) mod 4;
    t[4] := (    a+  b+2*c+  d+  e+      g+  h+2*i) mod 4;
    t[5] := (4+  a+2*b+  c+2*d  -e+2*f+  g+2*h+  i) mod 4;
    t[6] := (  2*a+  b+  c+      e+  f+2*g+  h+  i) mod 4;
    t[7] := (8+  a  -b+    2*d+2*e  -f+  g+2*h+  i) mod 4;
    t[8] := (  2*a+    2*c+  d+  e+  f+  g+  h+  i) mod 4;
    t[9] := (8      -b+  c  -d+2*e+2*f+  g+2*h+  i) mod 4;
    assign(out, 'clocks.out'); rewrite(out);
    for a := 1 to 9 do
        for b := 1 to t[a] do Begin
            inc(l);
            an[l]:=a;
        end;
    for a:=1 to l-1 do
        write(out,an[a],' ');
    write(out,an[l]);
    writeln(out); close(out)
end.
</pre>
</blockquote>

<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/02/usaco-milking-cows/' rel='bookmark' title='[USACO]Milking Cows / [Vijos1165]火烧赤壁'>[USACO]Milking Cows / [Vijos1165]火烧赤壁</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/02/usaco-p58-the-clocks/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>[USACO]Mixing Milk</title>
		<link>http://blog.tomtung.com/2007/02/usaco-mixing-milk/</link>
		<comments>http://blog.tomtung.com/2007/02/usaco-mixing-milk/#comments</comments>
		<pubDate>Wed, 14 Feb 2007 13:12:26 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[USACO]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/02/usaco-p76-mixing-milk/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/02/usaco-mixing-milk/" title="[USACO]Mixing Milk"></a>Mixing Milk Since milk packaging is such a low margin business, it is important to keep the price of the raw product (milk) as low as possible. Help Merry Milk Makers get the milk they need in the cheapest possible &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/02/usaco-mixing-milk/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/02/usaco-milking-cows/' rel='bookmark' title='[USACO]Milking Cows / [Vijos1165]火烧赤壁'>[USACO]Milking Cows / [Vijos1165]火烧赤壁</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/02/usaco-mixing-milk/" title="[USACO]Mixing Milk"></a><h1 style="text-align: center;">Mixing Milk</h1>
<p>Since milk packaging is such a low margin business, it is important to keep the price of the raw product (milk) as low as possible. Help Merry Milk Makers get the milk they need in the cheapest possible manner.</p>
<p>The Merry Milk Makers company has several farmers from which they may buy milk, and each one has a (potentially) different price at which they sell to the milk packing plant. Moreover, as a cow can only produce so much milk a day, the farmers only have so much milk to sell per day. Each day, Merry Milk Makers can purchase an integral amount of milk from each farmer, less than or equal to the farmer&#8217;s limit.</p>
<p>Given the Merry Milk Makers&#8217; daily requirement of milk, along with the cost per gallon and amount of available milk for each farmer, calculate the minimum amount of money that it takes to fulfill the Merry Milk Makers&#8217; requirements.</p>
<p>Note: The total milk produced per day by the farmers will be sufficient to meet the demands of the Merry Milk Makers.</p>
<h3>PROGRAM NAME: milk</h3>
<h3>INPUT FORMAT</h3>
<table border="1">
<tbody>
<tr>
<td>Line 1:</td>
<td>Two integers, N and M.<br />
The first value, N, (0 &lt;= N &lt;= 2,000,000) is the amount of milk that Merry Milk Makers&#8217; want per day. The second, M, (0 &lt;= M &lt;= 5,000) is the number of farmers that they may buy from.</td>
</tr>
<tr>
<td>Lines 2 through M+1:</td>
<td>The next M lines each contain two integers, P<sub>i</sub> and A<sub>i</sub>.<br />
P<sub>i</sub> (0 &lt;= P<sub>i</sub> &lt;= 1,000) is price in cents that farmer i charges.<br />
A<sub>i</sub> (0 &lt;= Ai &lt;= 2,000,000) is the amount of milk that farmer i can sell to Merry Milk Makers per day.</td>
</tr>
</tbody>
</table>
<h3>OUTPUT FORMAT</h3>
<p>A single line with a single integer that is the minimum price that Merry Milk Makers can get their milk at for one day.</p>
<h3>SOLUTION</h3>
<p>这个题当然是巨弱的题，基本上即使不知道有贪心这回事的人也能凭感觉做出来。但是我看到官方的Analysis，觉得很不错，就发上来了。它用一个最简单的例子向我们展示了<strong>最优美的代码是怎样炼成的</strong>。</p>
<p>下面是代码我翻译的说明、注释。</p>
<blockquote><p>
因为我们获得的东西都是同样大小的（在这里是单位量的牛奶），贪心算法可以满足要求：我们按照出价对农夫们进行排序，然后向出价最低的农夫买牛奶，买光他的牛奶后在向下一个出价次低的农夫买。</p>
<p>实现方法：我们把数据读入Farmer结构体中，按照出价对数组进行排序，扫描数组，依次买牛奶直到牛奶量达到所需。</p>
<pre class="brush:c++">
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;assert.h&gt;

#define MAXFARMER 5000

typedef struct Farmer Farmer;
struct Farmer {
 int p; /* 每加仑的价格*/
 int a; /* 要卖的总量*/
};

int
farmcmp(const void *va, const void *vb)
{
 return ((Farmer*)va)-&gt;p - ((Farmer*)vb)-&gt;p;
}

int nfarmer;
Farmer farmer[MAXFARMER];

void
main(void)
{
 FILE *fin, *fout;
 int i, n, a, p;

 fin = fopen("milk.in", "r");
 fout = fopen("milk.out", "w");

 assert(fin != NULL &amp;&amp; fout != NULL);

 fscanf(fin, "%d %d", &amp;n, &amp;nfarmer);
 for(i=0; i&lt;nfarmer; i++)
  fscanf(fin, "%d %d", &amp;farmer[i].p, &amp;farmer[i].a);

 qsort(farmer, nfarmer, sizeof(farmer[0]), farmcmp);

 p = 0;
 for(i=0; i&lt;nfarmer &amp;&amp; n &gt; 0; i++) {
/* 向farmer[i]买尽可能多的牛奶，最大为amount n */
  a = farmer[i].a;
  if(a &gt; n)
   a = n;
  p += a*farmer[i].p;
  n -= a;
 }

 fprintf(fout, "%d\n", p);
 exit(0);
}
</pre>
<p>加拿大的Ran Pang写道：</p>
<p>这里有个程序能在线性时间内解决问题的程序，而我觉得官方给出的算法是O(n log n)的。</p>
<pre class="brush:c++">
#include&lt;stdio.h&gt;

#define MAXPRICE 1001

int amount_for_price[MAXPRICE]={0};
int N, M;

int Cal(void);
int Read(void);

int main(void) {
    Read();
    Cal();
    return 0;
}

int Cal(void) {
    int i;
    int price_total=0;
    int milk_total=0;
    for(i=0;i&lt;MAXPRICE;i++) {
        if(amount_for_price[i]) {
            if(milk_total+amount_for_price[i]&lt;N) {
                price_total+=(i*amount_for_price[i]);
                milk_total+=amount_for_price[i];
            }
            else {
                int amount_needed = N-milk_total;
                price_total+=(i*amount_needed);
                break;
            }
        }
    }
    {
        FILE* out=fopen("milk.out","w");
        fprintf(out,"%d\n",price_total);
        fclose(out);
    }
    return 0;
}

int Read(void) {
    FILE* in = fopen("milk.in","r");
    int i, price, amount;
    fscanf(in,"%d %d",&amp;N,&amp;M);
    for(i=0;i&lt;M;i++) {
        fscanf(in, "%d %d", &amp;(price), &amp;(amount));
        amount_for_price[price]+=amount;
    }
    fclose(in);
    return 0;
}
</pre>
<p>另一个解法，来自SVK（这是哪啊？）的Adam Okruhlica</p>
<p>完全没有必要用O(nlgn)的时间对价格进行快排，因为价格有一个$1000的上界，而且我们知道所有价格都是整数。我们可以用count sort对数组进行排序。我们可以给每一个可用的价格（0..1000）建立一个“盒子”。我们把输入数据存入一个数组，然后扫描每一个农夫，记录他在（0..1000）数组中的下标，此下标就等于他的出价。因此我们可以把出价相同的农夫放入一个链表中。最后，我们从0到1000扫描整个数组，并从链表中取出农夫们的下标。这很容易实现，且时间复杂度为O(n)。</p>
<pre class="brush:pascal">
program milk;

type pList = ^List;
      List = record
                farmer:longint;
                next:pList;
              end;
      HeadList = record
                   head:pList;
                   tail:pList;
                  end;

var fIn,fOut:text;
    sofar,i,x,want,cnt,a,b:longint;
    sorted,cost,amount:array[1..5010] of longint;
    csort:array[0..1010] of HeadList;

    t:pList;

begin
    assign(fIn,'milk.in');reset(fIn);
    assign(fOut,'milk.out'); rewrite(fOut);

    readln(fIn,want,cnt);
    for i:=1 to cnt do readln(fIn,cost[i],amount[i]);

    for i:=0 to 1000 do begin
         new(csort[i].head);
         csort[i].tail:=csort[i].head;
         csort[i].head^.farmer:=-1;
    end;

    {向数组中存入下标}
    for i:=1 to cnt do begin

       t:=csort[cost[i]].tail;
       if t^.farmer = -1 then t^.farmer:=i;
       new(t^.next);
       t^.next^.farmer:=-1;
       csort[cost[i]].tail:=t^.next;
    end;

    {取出下标}
    x:=1;
    for i:=0 to 1000 do begin
        t:=csort[i].head;
        while t^.farmer &gt; 0 do begin
          sorted[x]:=t^.farmer;
          inc(x);
          t:=t^.next;
        end;
    end;

    sofar:=0;
    for i:=1 to cnt do begin
      if want &lt; amount[sorted[i]] then begin
        inc(sofar,want*cost[sorted[i]]);
        want:=0; break;
      end

      else inc(sofar,amount[sorted[i]]*cost[sorted[i]]);
      dec(want,amount[sorted[i]]);
    end;

    writeln(fOut,sofar);
    close(fOut);
end.
</pre>
<p>Dwayne Crooks写道：</p>
<p>我们真的需要一个SVK的Adam Okruhlica在解答中使用的链表吗？我不这么想。这里有一个解法，本质上和Adam的一样，却没有使用链表。此解法是O(max(MAXP,M)) 的（MAXP=1000，M&lt;=5000）。编辑注：为了避免溢出，Dwayne应该使用 long long integers (64 bit)而非int。</p>
<pre class="brush:c++">
#include &lt;iostream&gt;
#include &lt;fstream&gt;

#define MAXP 1000

using namespace std;

int main() {
    ifstream in("milk.in");
    ofstream out("milk.out");

    int N, M;
    int P[MAXP+1];

    in &gt;&gt; N &gt;&gt; M;
    for (int i = 0; i &lt;= MAXP; i++) P[i]=0;
    for (int i = 0; i &lt; M; i++) {
        int price, amt;
        in &gt;&gt; price &gt;&gt; amt;

         // 我们可以将价格相同的各个农民手中牛奶的总量相加
        // 因为x加仑售价c美分、
        //          y加仑售价c美分
        // 和x+y加仑售价c美分
        //      是一回事
        P[price] += amt;
    }

    // 贪心策略：尽可能多的买售价最低的
    int res = 0;
    for (int p = 0; p&lt;=MAXP &amp;&amp; N&gt;0; p++) {
        if (P[p]&gt;0) {
            res+=p*(N&lt;P[p]?N:P[p]);
            N-=P[p];
        }
    }
    out &lt;&lt; res &lt;&lt; endl;

    in.close();
    out.close();

    return 0;
}
</pre>
<p>做为结语，保加利亚的Miroslav Paskov从以上所有精彩的想法中提炼出了这个简单的解法：</p>
<pre class="brush:c++">
#include &lt;fstream&gt;
#define MAXPRICE 1001
using namespace std;

int main() {
    ifstream fin ("milk.in");
    ofstream fout ("milk.out");
    unsigned int i, needed, price, paid, farmers, amount, milk[MAXPRICE][2];
    paid = 0;
    fin&gt;&gt;needed&gt;&gt;farmers;
    for(i = 0;i&lt;farmers;i++){
        fin&gt;&gt;price&gt;&gt;amount;
        milk[price][0] += amount;
    }
    for(i = 0; i&lt;MAXPRICE &amp;&amp; needed;i++){
        if(needed&gt; = milk[i][0]) {
            needed -= milk[i][0];
            paid += milk[i][0] * i;
        } else if(milk[i][0]&gt;0) {
            paid += i*needed;
            needed = 0;
        }
    }
    fout &lt;&lt; paid &lt;&lt; endl;
    return 0;
}
</pre>
</blockquote>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/02/usaco-milking-cows/' rel='bookmark' title='[USACO]Milking Cows / [Vijos1165]火烧赤壁'>[USACO]Milking Cows / [Vijos1165]火烧赤壁</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/02/usaco-mixing-milk/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>[USACO]Friday the Thirteenth</title>
		<link>http://blog.tomtung.com/2007/02/usaco-friday-the-13th/</link>
		<comments>http://blog.tomtung.com/2007/02/usaco-friday-the-13th/#comments</comments>
		<pubDate>Sun, 11 Feb 2007 06:31:51 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[USACO]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/02/usaco-p109-friday-the-thirteenth/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/02/usaco-friday-the-13th/" title="[USACO]Friday the Thirteenth"></a>Friday the Thirteenth Is Friday the 13th really an unusual event? That is, does the 13th of the month land on a Friday less often than on any other day of the week? To answer this question, write a program &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/02/usaco-friday-the-13th/">继续阅读 &#187;</a></p><hr/><blockquote>
可能你对下面的文章也感兴趣：<ol>
<li><a href='http://blog.tomtung.com/2007/02/usaco-milking-cows/' rel='bookmark' title='[USACO]Milking Cows / [Vijos1165]火烧赤壁'>[USACO]Milking Cows / [Vijos1165]火烧赤壁</a></li>
</ol></blockquote>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/02/usaco-friday-the-13th/" title="[USACO]Friday the Thirteenth"></a><div>
<h1 style="text-align: center;"><strong>Friday the Thirteenth</strong></h1>
<p>Is Friday the 13th really an unusual event?</p>
<p>That is, does the 13th of the month land on a Friday less often than on any other day of the week? To answer this question, write a program that will compute the frequency that the 13th of each month lands on Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, and Saturday over a given period of N years. The time period to test will be from January 1, 1900 to December 31, 1900+N-1 for a given number of years, N. N is non-negative and will not exceed 400.</p>
<p>There are few facts you need to know before you can solve this problem:</p>
<ul>
<li>January 1, 1900 was on a Monday.</li>
<li>Thirty days has September, April, June, and November, all the rest have 31 except for February which has 28 except in leap years when it has 29.</li>
<li>Every year evenly divisible by 4 is a leap year (1992 = 4*498 so 1992 will be a leap year, but the year 1990 is not a leap year)</li>
<li>The rule above does not hold for century years. Century years divisible by 400 are leap years, all other are not. Thus, the century years 1700, 1800, 1900 and 2100 are not leap years, but 2000 is a leap year.</li>
</ul>
<p>Do not use any built-in date functions in your computer language.</p>
<p>Don&#8217;t just precompute the answers, either, please.</p>
<h3>PROGRAM NAME: friday</h3>
<h3>INPUT FORMAT</h3>
<p>One line with the integer N.</p>
<h3>SAMPLE INPUT (file friday.in)</h3>
<pre> 20</pre>
<h3>OUTPUT FORMAT</h3>
<p>Seven space separated integers on one line. These integers represent the number of times the 13th falls on Saturday, Sunday, Monday, Tuesday, &#8230;, Friday.</p>
<h3>SAMPLE OUTPUT (file friday.out)</h3>
<pre> 36 33 34 33 35 35 34</pre>
</div>
<h3>SOLUTION</h3>
<p>这个题没什么可说的。只是我是按照常规方法做的，但刚发现了更简单的方法，即利用蔡勒公式来解。这里把公式和用此公式解题的源码贴上来。</p>
<blockquote><p>蔡勒（Zeller）公式：是一个计算星期的公式。</p>
<p>随便给一个日期，就能用这个公式推算出是星期几。</p>
<p>蔡勒（Zeller）公式如下：</p>
<p>w=y+[y/4]+[c/4]-2c+[26(m+1)/10]+d-1<br />
公式中的符号含义如下，<br />
w：星期；<br />
c：世纪；<br />
y：年（两位数）；<br />
m：月（m大于等于3，小于等于14，即在蔡勒公式中，某年的1、2月要看作上一年的13、14月来计算，比如2003年1月1日要看作2002年的13 月1日来计算）；<br />
d：日；[ ]代表取整，即只要整数部分。</p>
<p>下面以中华人民共和国成立100周年纪念日那天（2049年10月1日）来计算是星期几。过程如下：<br />
w=y+[y/4]+[c/4]-2c+[26(m+1)/10]+d-1<br />
=49+[49/4]+[20/4]-2×20+[26× (10+1)/10]+1-1<br />
=49+[12.25]+5-40+[28.6]<br />
=49+12+5-40+28<br />
=54 (除以7余5)<br />
即2049年10月1日（100周年国庆）是星期五。</p>
<p>不过，以上的公式都只适合于1582年10月15日之后的情形（当时的罗马教皇将恺撒大帝制订的儒略历修改成格里历，即今天使用的公历）。</p></blockquote>
<pre class="brush:java">import java.io.*;
import java.util.*;

class friday
{
 public static void main(String args[])
 {
  int n, c, y, m, w, yearAdjusted;
  final int d = 13;
  int[] weekday13thCount = new int[7];

  for (int i = 0; i &lt; 7; i++)
   weekday13thCount[i] = 0;

  Scanner in = new Scanner(System.in);
  Formatter out = new Formatter(System.out);
  try
  {
   in = new Scanner(new FileInputStream("friday.in"));
   out = new Formatter(new FileOutputStream("friday.out"));
  }
  catch (IOException e)
  {
   System.exit(1);
  }

  n = in.nextInt();
  if (n != 0)
   for (int year = 1900; year &lt;= 1900 + n - 1; year++)
    for (int month = 1; month &lt;= 12; month++)
    {
     if (month &lt;= 2)
     {
      m = month + 12;
      yearAdjusted = year - 1;
     }
     else
     {
      m = month;
      yearAdjusted = year;
     }
     c = yearAdjusted / 100;
     y = yearAdjusted % 100;
     w = ((c / 4) - (2 * c) + y + (y / 4) + (26 * (m + 1) / 10) + d - 1) % 7;
     if (w &lt; 0)
      w += 7;
     weekday13thCount[w]++;
    }

  out.format("%d %d %d %d %d %d %d\n",
   weekday13thCount[6],
   weekday13thCount[0],
   weekday13thCount[1],
   weekday13thCount[2],
   weekday13thCount[3],
   weekday13thCount[4],
   weekday13thCount[5]
  );

  in.close();
  out.close();
  System.exit(0);
 }
}
</pre>

<hr/><blockquote><p>可能你对下面的文章也感兴趣：</p><ol>
<li><a href='http://blog.tomtung.com/2007/02/usaco-milking-cows/' rel='bookmark' title='[USACO]Milking Cows / [Vijos1165]火烧赤壁'>[USACO]Milking Cows / [Vijos1165]火烧赤壁</a></li>
</ol></blockquote>]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/02/usaco-friday-the-13th/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>[USACO]Milking Cows / [Vijos1165]火烧赤壁</title>
		<link>http://blog.tomtung.com/2007/02/usaco-milking-cows/</link>
		<comments>http://blog.tomtung.com/2007/02/usaco-milking-cows/#comments</comments>
		<pubDate>Thu, 08 Feb 2007 07:35:38 +0000</pubDate>
		<dc:creator>逆铭</dc:creator>
				<category><![CDATA[OI那一年]]></category>
		<category><![CDATA[USACO]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[解题报告]]></category>

		<guid isPermaLink="false">http://blog.tomtung.com/2007/02/usaco-milking-cows-vijos-p1165-%e7%81%ab%e7%83%a7%e8%b5%a4%e5%a3%81/</guid>
		<description><![CDATA[<a href="http://blog.tomtung.com/2007/02/usaco-milking-cows/" title="[USACO]Milking Cows / [Vijos1165]火烧赤壁"></a>Milking Cows Three farmers rise at 5 am each morning and head for the barn to milk three cows. The first farmer begins milking his cow at time 300 (measured in seconds after 5 am) and ends at time 1000. &#8230;<p class="read-more"><a href="http://blog.tomtung.com/2007/02/usaco-milking-cows/">继续阅读 &#187;</a></p>]]></description>
			<content:encoded><![CDATA[<a href="http://blog.tomtung.com/2007/02/usaco-milking-cows/" title="[USACO]Milking Cows / [Vijos1165]火烧赤壁"></a><h1 style="text-align: center;"><strong>Milking Cows</strong></h1>
<p><strong> </strong>Three farmers rise at 5 am each morning and head for the barn to milk three cows. The first farmer begins milking his cow at time 300 (measured in seconds after 5 am) and ends at time 1000. The second farmer begins at time 700 and ends at time 1200. The third farmer begins at time 1500 and ends at time 2100. The longest continuous time during which at least one farmer was milking a cow was 900 seconds (from 300 to 1200). The longest time no milking was done, between the beginning and the ending of all milking, was 300 seconds (1500 minus 1200).<br />
Your job is to write a program that will examine a list of beginning and ending times for N (1 &lt;= N &lt;= 5000) farmers milking N cows and compute (in seconds):</p>
<ul>
<li>The longest time interval at least one cow was milked.</li>
<li>The longest time interval (after milking starts) during which no cows were being milked.</li>
</ul>
<h3>PROGRAM NAME: milk2</h3>
<h3>INPUT FORMAT</h3>
<table border="1">
<tbody>
<tr>
<td>Line 1:</td>
<td>The single integer</td>
</tr>
<tr>
<td>Lines 2..N+1:</td>
<td>Two non-negative integers less than 1000000, the starting and ending time in seconds after 0500</td>
</tr>
</tbody>
</table>
<h3>OUTPUT FORMAT</h3>
<p>A single line with two integers that represent the longest continuous time of milking and the longest idle time.</p>
<h3>SOLUTION</h3>
<p>这道题是本巨菜第一次做出的涉及区间处理的题（虽然至今搞不清“离散化”是什么意思），具有重大的意义~~~~~我是参照<a href="http://oiflyingforever.spaces.live.com/blog/cns!5C715499A28E16A!146.entry">这个说明</a>做的：</p>
<blockquote><p>按照开始时间将挤奶信息升序排序，然后按照如下方法从左到右扫描一遍，即可得到结果。<br />
设r1为最长连续挤奶时间，r2为最长连续空闲时间，r1、r2初始时均为0。<br />
每条挤奶信息都是一个区间，那么我们用变量c记录当前连续区间，初始时当前区间变量为排序后的第一个区间(c=milk[1])。</p>
<p>如果下一个区间i和当前区间相交或当前区间包含区间i(milk[i].start&lt;=c.end)，就将两个区间的并集作为当前区间 (c.end=max(c.end,milk[i].end))； 如果下一个区间i和当前区间不相交(milk[i].start&gt;c.end)，则更新r1、r2的值(r1=max(r1,c.end- c.start)，r2=max(r2,milk[i].start-c.end))，并将区间i作为当前区间(c=milk[i])。</p>
<p>因为我们只在下一个区间和当前区间不相交时才会去更新最优解，所以最后一个连续区间我们并没有更新，因此别忘了再更新一下 r1(r1=max(r1,c.end-c.start))。<br />
r1，r2即为所求。</p></blockquote>
<p>源码如下：</p>
<pre class="brush:c++">/*
ID: tom.tun1
PROG: milk2
LANG: C++
*/
#include &lt;iostream&gt;
#include &lt;fstream&gt;
using namespace std;
ifstream fin("milk2.in");
ofstream fout("milk2.out");
struct time
{
  int start;
  int end;
}milk[5000], present;
int N, MaxWork = 0, MaxBreak = 0;
int Partition(int first, int end);
void QuickSort(int first, int end);
void compute(int j);
bool intersection(struct time, struct time);//判断是否有交集
struct time unite(struct time, struct time);//求并集 

int
main()
{
  fin &gt;&gt; N;
  for (int j = 0; j &lt; N; j++)
      fin &gt;&gt; milk[j].start &gt;&gt; milk[j].end;
  QuickSort(0, N - 1);
  present = milk[0];

  for (int j = 1; j &lt; N; j++)
      compute(j);
  MaxWork = max(MaxWork, present.end - present.start);
  fout &lt;&lt; MaxWork &lt;&lt; ' ' &lt;&lt; MaxBreak &lt;&lt; endl;
  return 0;
}

void
compute(int j)
{
  if (intersection(milk[j], present))
      present = unite(milk[j], present);
  else
    {
      MaxWork = max(MaxWork, present.end - present.start);
      MaxBreak = max(MaxBreak, milk[j].start - present.end);
      present = milk[j];
    }
}

bool
intersection(struct time a, struct time b)
{
  if (a.end &lt; b.start || b.end &lt; a.start)
      return false;
  else
      return true;
}

struct time
unite(struct time a, struct time b)
{
  struct time x;
  x.start = min(a.start, b.start);
  x.end = max(a.end, b.end);
  return x;
}

void
QuickSort(int first, int end)
{
  if (first &lt; end)
    {
      int pivot = Partition(first, end);
      QuickSort(first, pivot - 1);
      QuickSort(pivot + 1, end);
    }
}
int
Partition(int first, int end)
{
  int i = first, j = end;
  while (i &lt; j)
    {
      while (i &lt; j &amp;&amp; milk[i].start &lt;= milk[j].start)
          j--;
      if (i &lt; j)
        {
          swap(milk[i], milk[j]);
          i++;
        }
      while (i &lt; j &amp;&amp; milk[i].start &lt;= milk[j].start)
          i++;
      if (i &lt; j)
        {
          swap(milk[i], milk[j]);
          j--;
        }
    }
  return i;
}
</pre>
<p>虽然不优美，但是AC了~~~~多谢那位写题解的牛！</p>
<p>USACO上的Analysis提到里提到的方法一即此方法，方法二是我原来一直用的。<span style="text-decoration: line-through;">方法三至今看不懂，渴望有达人解释一下，多谢！</span><br />
我听从了ghost牛的建议进行手工单步，终于搞懂了官方给出的第三种解法，hoho~~~~兴奋中~~~~~这里特别感谢ghost给我讲qsort()是怎么用的……</p>
<p>该算法将每次有farmer开始或结束挤奶做为一个单独“事件”看待。每一个事件包括两个信息：事件的开始时间、该事件记录的是开始挤奶还是结束挤奶。其中后者可以用1和-1表示。先把所有事件按照时间先后排序。每次处理事件的第二个信息时直接加和即可。有人开始挤奶，人数就+1；有人结束挤奶，人数就-1.这样，我们只需要看此加和的值即能知道当前事件发生后有几个人在挤奶。</p>
<p>这样我们就能很容易地找到一个有人挤奶或无人挤奶的完整时间段的起始点和终点（也就是另一个完整时间段的起点）。得到长度后再与当前最大值比较……剩下就很容易了。</p>
<p>下面附上官方的此算法源码以及我加的详细注释（注释均针对前一语句）：</p>
<pre class="brush:c++">#include &lt;fstream.h&gt;
#include &lt;stdlib.h&gt;

struct event
{
 long seconds;   /*记录该事件发生时离早上5点有多少秒*/
 signed char ss; /*若该事件是某人开始挤奶，则为1；若是某人结束挤奶，则为-1*/
};

int eventcmp (const event *a, const event *b)
{
 if (a-&gt;seconds != b-&gt;seconds)
  return (a-&gt;seconds - b-&gt;seconds); /*早发生的事件排在前面*/

 return (b-&gt;ss - a-&gt;ss);
 /* 若两个事件同时发生，
     则记录某人开始挤奶的事件排在前面*/
}

int main ()
{
 ifstream in;
 ofstream out;

 in.open("milk2.in");
 out.open("milk2.out");

 int num_intervals, num_events, i;
 /*num_intervals即输入的农夫数N，
    num_events即相应的事件数*/
 event events[5000 * 2];/*记录所有事件的数组*/

 in &gt;&gt; num_intervals;
 num_events = num_intervals * 2;/*很明显，事件数为农夫数的二倍*/
 for (i = 0; i &lt; num_intervals; ++i)
 {
  in &gt;&gt; events[2*i].seconds; events[2*i].ss = 1;
  in &gt;&gt; events[2*i+1].seconds; events[2*i+1].ss = -1;
 }

 qsort(events, num_events, sizeof(event),
  (int(*)(const void*, const void*)) eventcmp);
  /*对所有事件进行排序，详见eventcmp处的注释*/

/* for (i = 0; i &lt; num_events; ++i)
  out &lt;&lt; events[i].seconds
    &lt;&lt; (events[i].ss == 1 ? " start" : " stop") &lt;&lt; endl; */
   /*测试一下排序结果如何*/

 int num_milkers = 0, was_none = 1;
 /*num_milkers为当前事件发生时挤奶者的数目
   （包括当前事件本身对其的影响），
    was_none为上一个事件发生时挤奶者的数目
   （包括上一事件本身对其的影响）*/
 int longest_nomilk = 0, longest_milk = 0;/*记录最大值的变量*/
 int istart, ilength;
 /*istart记录当前正在处理的完整时间段的起始位置，
    ilength是在比较当前完整时间段与最大完整时间段大小时的中间变量*/

 for (i = 0; i &lt; num_events; ++i)
 {
  num_milkers += events[i].ss;

  if (!num_milkers &amp;&amp; !was_none)
  /*当num_milkers==was_none==0时。
     num_milkers==0表示此时无挤奶者，
     was_none==0表示上一个“事件”时有挤奶者。
     这样后一个事件就成了上一挤奶完整时间段的终点
     和下一完整无人挤奶时间段的起点，
     应该计算结束的时间段长度并与longest_milk比较*/
 {
   ilength = (events[i].seconds - istart);
   if (ilength &gt; longest_milk)
    longest_milk = ilength;
   istart = events[i].seconds;
  }
  else if (num_milkers &amp;&amp; was_none)
  /*当num_milkers!=0&amp;&amp;was_none!=0时。
     num_milkers!=0表示此时有挤奶者，
     was_none!=0表示上一个“事件”时无挤奶者。
     这样后一个事件就成了上一无人挤奶完整时间段的终点
     和下一完整挤奶时间段的起点，
     应该计算计算结束的时间段长度并与longest_nomilk比较*/
  {
   if (i != 0)
   {
    ilength = (events[i].seconds - istart);
    if (ilength &gt; longest_nomilk)
     longest_nomilk = ilength;
   }
   istart = events[i].seconds;
  }

  was_none = (num_milkers == 0);
 }

 out &lt;&lt; longest_milk &lt;&lt; " " &lt;&lt; longest_nomilk &lt;&lt; endl;

 return 0;
}
</pre>
<p>&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8211;</p>
<p>解决了上面那道题，我就做了原来不会做的Vijos上的那个火烧赤壁，巩固一下方法。</p>
<p><span style="color: #ff9900;"><strong>描述 Description</strong></span><br />
<span style="color: #006666;">曹操平定北方以后，公元208年，率领大军南下，进攻刘表。他的人马还没有到荆州，刘表已经病死。他的儿子刘琮听军声势浩大，吓破了胆，先派人求降了。</span></p>
<p><span style="color: #006666;">孙权任命周瑜为都督，拨给他三万水军，叫他同刘备协力抵抗曹操。</span></p>
<p><span style="color: #006666;">隆冬的十一月，天气突然回暖，刮起了东南风。</span></p>
<p><span style="color: #006666;">没想到东吴船队离开北岸大约二里距离，前面十条大船突然同时起火。火借风势，风助火威。十条火船，好比十条火龙一样，闯进曹军水寨。那里的船舰，都挤在一起，又躲不开，很快地都烧起来。一眨眼工夫，已经烧成一片火海。</span></p>
<p><span style="color: #006666;">曹操气急败坏的把你找来，要你钻入火海把连环线上着火的船只的长度统计出来！</span></p>
<p><span style="color: #ff9900;"><strong>输入格式 Input Format</strong></span><br />
<span style="color: #494949;"> </span><span style="color: #006666;">第一行：N<br />
以后N行，每行两个数：Ai  Bi(表示连环线上着火船只的起始位置和终点,-10^9&lt;=Ai,Bi&lt;=10^9)</span></p>
<p><span style="color: #ff9900;"><strong>输出格式 Output Format<br />
</strong></span><span style="color: #006666;">输出着火船只的总长度</span></p>
<p><span style="color: #ff9900;"><strong>样例输入 Sample Input<br />
</strong></span><span style="color: #006666;">3<br />
-1 1<br />
5 11<br />
2 9</span></p>
<p><span style="color: #ff9900;"><strong>样例输出 Sample Output<br />
</strong></span><span style="color: #006666;">11</span></p>
<p><span style="color: #ff9900;"><strong>注释 Hint</strong></span><br />
<span style="color: #006666;">n&lt;=20000<br />
如果Ai=Bi是一个点则看作没有长度</span></p>
<p><span style="color: #ff9900;"><strong>题解 Solution</strong></span></p>
<p><span style="color: #006666;">看看原来大牛们的题解吧：<br />
线段树或者平面扫除法的一维情形<br />
离散化+快排+二分查找，一定不会超时的^^</span></p>
<p><span style="color: #006666;">这种题解对我这样的巨菜来说和没有一样……现在终于通过Milking Cows搞定了这道问题。此题应该说比Milking Cows简单，只是要注意Ai=Bi的情况。</span></p>
<p><span style="color: #006666;">源码如下：</span></p>
<pre class="brush:c++">
#include &lt;iostream&gt;
using namespace std;
int num=0;
struct ship{
 __int64 start;
 __int64 end;
}ships[20000],current;

struct ship Unite(struct ship,struct ship);
void QuickSort(int first,int end);
int Partition(int first,int end);
int main()
{
 int N;
 cin &gt;&gt; N;
 __int64 a,b;
 for(int i=0;i&lt;N;i++)
 {
  cin &gt;&gt; a &gt;&gt; b;
  if(a!=b)
  {
   ships[num].start=a;
   ships[num].end=b;
   num++;
  }
 }
 QuickSort(0,num-1);
 /*for(int i=0;i&lt;num;i++)
  cout &lt;&lt; ships[i].start &lt;&lt; '~' &lt;&lt; ships[i].end &lt;&lt; endl;*/

 __int64 length=0;
 current=ships[0];
 for(int i=1;i&lt;num;i++)
 {
  if(ships[i].start&gt;current.end)
  {
   length+=(current.end-current.start);
   current=ships[i];
  }
  else current=Unite(current,ships[i]);
 }
 length+=(current.end-current.start);
 cout &lt;&lt; length &lt;&lt; endl;
 return 0;
}

struct ship Unite(struct ship a,struct ship b)
{
 struct ship c;
 c.start=min(a.start,b.start);
 c.end=max(a.end,b.end);
 return c;
}

void QuickSort(int first,int end)
{
 if(first&lt;end)
 {
  int pivot=Partition(first,end);
  QuickSort(first,pivot-1);
  QuickSort(pivot+1,end);
 }
}
int Partition(int first,int end)
{
 int i=first,j=end;
 while(i&lt;j)
 {
  while(i&lt;j&amp;&amp;ships[i].start&lt;=ships[j].start) j--;
  if(i&lt;j)
  {
   swap(ships[i],ships[j]);
   i++;
  }
  while(i&lt;j&amp;&amp;ships[i].start&lt;=ships[j].start) i++;
  if(i&lt;j)
  {
   swap(ships[j],ships[i]);
   j--;
  }
 }
 return i;
}
</pre>

]]></content:encoded>
			<wfw:commentRss>http://blog.tomtung.com/2007/02/usaco-milking-cows/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>

