QhelDIV

@____@

[HAOI2007] 上升序列

总是想得很复杂,其实很简单。

用O(n2)或者O(nlogn)的时间复杂度统计出fi即以i开始的最长上升序列是多少

因为字典序的缘故,暴力找最优解

假设某次询问是a,给定的数列是{Ai}

对于第一个位置,为了使字典序最小,我们一定选最早的i,使得fi1>=a

对于第二个位置,Ai2一定 且fi1>=a-1所以接着向后找第一个满足着两个条件的位置

……

这一段的代码:

 

	for(int i=1;i<=M;i++){
	int j,a,b,pr;
		fin>>a;
		b=0;
		A[pr=0]=-1;
		for(j=1;j<=N;j++)
			if(f[j]>=a && A[j]>A[pr]){
				Ans[++b]=j;
				a--;
				if(!a)
					break;
				pr=j;
			}
		if(b<a){
			fout<<"Impossible\n";
			continue;
		}
		for(j=1;j<=b;j++)
			fout<<A[Ans[j]]<<" ";
		fout<<endl;
	}

刷题小记

今天早上。。出于某种不可抗拒的原因,没心情写题,所以正好小记以下(我是按分类记录,不是按日期记录):

  • 动态规划:
    • SCOI2008 着色方案  很好的题,f[c1][c2][c3][c4][c5][k]表示状态进行转移(上网看)
    • HAOI2010 计数  主要是涉及到 具有不可区别物体的集合的排列(比如重新排列SUCCESS能构成多少种不同的串,公式是(n,k1)*(n-k1,k2)*(n-k1-k2,k3)... = n!/(k1!*k2!...*kn!)其中ki表示某种元素的个数
    • SDOI2010 地精部落  这个题网上说的方法看不太懂,神马之云想的方法是 f[i][j]表示长度为i的序列第1个是j且开始下降的序列个数,g[i][j]表示...且开始上升的序列个数,那么f[i][j]=Sigma(g[i-1][k])k<j,g[i][j]=Sigma(f[i-1][k])k>j,dp的时候记录一下Sigma信息就可以省掉转移的那一维。
    • HAOI2010 最长公共子序列  dp时记录方案数,再容斥掉多计算的即可
    • HAOI2010 软件安装  好题 一开始看不就是个简单的树形依赖背包嘛,写出来才发现图里面竟然有环,不过既然是环,选一个就全都得选,所以缩成点,构成新的图,再在新图里面dp就可以了(注意环连接的要么什么都没有要么是外向树,对于外向树,缩点之后都不可能有前驱,所以连接到0,这样就是棵标准的树了,从0开始dp就行了)
    • 越狱第1季第13集 大神的做法不会,不过好像和我的方法有点像(二分答案,然后检查)
    • 瑰丽华尔兹 又是好题 f[k][y][x]表示第k段的结尾在y行x列的最长滑动距离,转移O(n)的,所以优先队列优化就可以Ac
  • 图论
    • 公路修建 。。。看出来了很简单,裸的最小生成树,不过由于边数太多Kruskal会跪掉,所以只能用Prim了
    • 卡赞群岛 找到双连通分量,缩点然后dp(由于是无向图,缩点之后必然是个无根树,或者是无根树森林,所以可dp。
    • HAOI 软件安装 见上文
  • 贪心
    • SCOI2008 斜堆 很有意思的题,按照斜堆的规则,最后插入的点一定在最左边(也就是便利的时候从不向右转),如果没有根变成左子树的情况,每次找最左边的点,删掉,然后模拟一下就行了(基于字典顺序最小的原则)。考虑上较小元素会替代根的情况,如果我们发现某个极左点没有右子树,那么说明它是叶子结点或者是替代根的元素,此时必须先删它(否则不可能构成目标树),基于最小字典序的原则,这些步骤合在一起就是:每次从根向左找没有右子树的点,一旦发现就把它删掉,并且将它的祖先的左右子树互换,最后倒序输出删点顺序即可(有一个特殊情况,即某点的左子树只有一个叶子结点,而且它没右子树,那么此时删除那个叶子结点(基于字典序最小的原则)),相关拓展见这位大神网志
    • 立春树 树上贪心,比较简单,不说了
  • 数论
    • 双亲数 貌似都是利用容斥搞的,用Mobius函数搞会快点,关于Mobius Function见Noi2010 能量项链,HAOI2011问题B,以及网上各种题解。

 

 

[USACO 4.3]Buy Low,Buy Lower

先声明一下下述的变量

N为总天数,

A[i]表示第i个数字,

f[i]和g[i]的定义见下文

Max=max(f[i])1<=i<=N

 

第一问就是个最简单的最长下降序列,f[i]表示当前包含第i个数字的最长下降序列,

第二问稍复杂,我的分析:

  1. 如果没有不能重复这个限制,非常好想到用g[i]存储当前包括第i个数字的最长下降序列的个数
    1. 如果f[i]被f[j]+1(j<i)更新,g[i]就更新为g[j]
    2. 如果f[i]等于f[j]+1,g[i]就更新为g[i]+g[j]
    3. 最后输出的方案数就是∑g[i] (f[i]=Max,1<=i<=N)
  2. 如果加上不能重复这个限制,想一想会发现对于任意两个相同的数字A[i]和A[j](j<i),如果A[i-1]的值已经求出,那么之后的任何f[i]值都用不到A[j]了,因为f[i]>=f[j] 且 g[i]>=g[j],所以遇到A[i]=A[j]的情况就把A[j]标记为不可用,以后动态规划的时候就跳过j,这样就避免了重复,一下将证明该算法的正确性(分成两个独立的部分)
    1. 用数学归纳证明按照上述做法,当程序结束时g[i]表示的序列总数中没有任何重复:
      1. 首先对于i=1时,f[i]=1,g[i]=1只有一种方案,所以没有重复
      2. 对于N≥i>1,假设当前要求f[i]和g[i],那么对于1<=j<i的g[j]都是没有任何重复的(归纳假设)
      3. 只有A[j]>A[i]且f[i]=f[j]+1时才有可能发生重复,因为g[j]没有任何重复,所以g[i]有重复当且仅当枚举j的时候有A[j1]=A[j2]且j1≠j2(举个例子:3 2 1 3 2 1,要求f[6]和g[6],当j1,j2取1,4或2,5时显然重复了).
      4. 每遇到A[i]=A[j]就把A[j]删去,保证了数列元素唯一性
      5. 按照上述做法 可以做到没有j1,j2(j1≠j2)使得A[j1]=A[j2](因为数列保证了元素的唯一性) , 那么g[i]也是没有任何重复的.
      6. 注:其实上面的话可以简述为:因为算法保证了元素单调性,所以不可能发生重复
    2. 接下来证明g[i]没有少也没多计算任何情况(算出的g[i]是正确的)
      1. 容易看出g[i]不会比正确的结果大(删除序列元素不会增加答案)
      2. 假设某个被删除的元素为A[j],那么肯定有A[i]=A[j](i>j),而g[i]包括了g[j]的所有情况(g[i]≥g[j]),所以不会有任何一种非重复情况被遗漏
      3. 综上,算出的g[i]是正确的

我的代码(因为答案可能很大,所以用了高精度,并重载了"+"运算

 

继续阅读

[NOI 2005]维护序列

中午的时候突然想起NOI2005的维护序列,于是就写了8小时搞出来,3小时写,5小时调,效率真够低的...

这是个很麻烦的问题,我用Splay解决的,首先题目要求的操作有6种,

其中翻转,求最大和比较麻烦.

  1. 对于insert操作,效仿NOI2003的文本编辑器,我直接建了一颗几乎平衡的二叉树,然后塞到Splay中相应的位置(这样快一点点)
  2. delete操作比较简单,找到相应的子树,删去即可.
  3. MakeSame操作:用delta表示要改变成什么,并传递这个值,每次改变delta都覆盖原有的,注意,如果没有delta值,是不能传递或是更改的,否则就会出错,所以我用了一个极小的常量NA表示没有delta值
  4. reverse操作:刚看过去感觉有点奇怪,稍稍思考一下发现reverse操作可以像MakeSame操作那样传递(翻转操作其实就是某棵树中所有的子树位值取反一下(左子树放右边,右子树放左边)),然后向下传递就行了
  5. GetSum也好做,用Sum记录该子树的和,每次MakeSame或者是进行Splay操作后维护一下就好了(Sum=左子树的Sum+右子树的Sum+1)
  6. MaxSum这个比较不好做,但是之前遇到在过线段树上比这个还要麻烦的问题(求某区间所有子串和的和...),就很容易想到了,即LMax表示某子树构成的序列中最大的前缀和是多少RMax表示最大的后缀和是多少,Max_Sum就表示最大子串和是多少,Max_Sum根据LMax和RMax算出就行了

Splay的两个维护函数:deliver()和maintain()

deliver()负责传递树根信息(翻转和MakeSame)

maintain()负责维护Splay的正确性(重新计算相关信息如Sum,LMax...)

我写了6K的代码,花了我很长的时间,然而并没有结束,因为明天还有继续优化代码和算法结构,以便下次再遇到能很快的写出.

[USACO 4.1]Fence Rails

4.1中的奶牛家秘术和Fence Rails都是要加好几个剪枝才可以AC的问题

搜索对象:每个栅栏(rail)对应那个Board(可能的深度太大而答案很可能在很浅的地方,所以迭代深度去找(DFSID),如果某个深度找到一个可行解,立刻退出,再搜索下一个深度)

一般优化都可以从以下3点考虑:

1.可行性剪枝   如果我们预料到某种情况无论再怎么搜索也得不到可行解,那就直接退出

2.最优化剪枝   如果我们预料到某种情况无论再怎么走索也得不到比当前最优解更优的解,那也直接退出

3.搜索顺序      良好的搜索顺序会更快的找到答案

其他技巧因问题而不同

那么Fence Rails可以这样剪枝:

1.可行性:     如果当前剩下的不可用的Board(即不能再切出rails的board)的和比board总面积减去rails的总面积还大,那肯定不可行(通俗地讲就是不能再装Rails的面积比装满所有rails剩下来的面积大)

2.搜索顺序:  容易想到,如果刚开始的时候就用大的rail塞到较小的board中,很快就能找到可行解

3.注意有很多重复,考虑到重复的元素位置不同没有影响,所以如果上一个元素和该元素相同,那么所在的board就直接从上一个开始枚举

这样就可以AC了

我的代码(有些慢):

继续阅读

[USACO 4.4]重叠的图像

 

1.构图

2.求出所有可能的满足拓扑序的答案并输出

 

1.的关键是题目的条件(每条边至少能看到1点)

2.这时用DFS求拓扑序好像就不管用了,所以采用暴力搜索去寻求答案,并满足拓扑序,

那就是记录一下每个矩形的出度,如果出度为0,说明当前可以继续搜(没有矩型被他压着),继续向下搜索,并删除该矩形(把压着他的矩形的出度-1),然后递归到下一层再找出度为0的矩形,这样就可以了.

 

 

描述

看下面的五张 9 x 8 的图像:

........   ........   ........   ........   .CCC....
EEEEEE..   ........   ........   ..BBBB..   .C.C....
E....E..   DDDDDD..   ........   ..B..B..   .C.C....
E....E..   D....D..   ........   ..B..B..   .CCC....
E....E..   D....D..   ....AAAA   ..B..B..   ........
E....E..   D....D..   ....A..A   ..BBBB..   ........
E....E..   DDDDDD..   ....A..A   ........   ........
E....E..   ........   ....AAAA   ........   ........
EEEEEE..   ........   ........   ........   ........

   1          2           3          4          5 

现在,把这些图像按照 1—5 的编号从下到上重叠,第 张在最下面,第 张在最顶端。如果一张图像覆盖了另外一张图像,那么底下的图像的一部分就变得不可见了。我们得到下面的图像:

 .CCC....
 ECBCBB..
 DCBCDB..
 DCCC.B..
 D.B.ABAA
 D.BBBB.A
 DDDDAD.A
 E...AAAA
 EEEEEE.. 

对于这样一张图像,计算构成这张图像的矩形图像从底部到顶端堆叠的顺序。

下面是这道题目的规则:

  • 矩形的边的宽度为 ,每条边的长度都不小于 
  • 矩形的每条边中,至少有一部分是可见的。注意,一个角同时属于两条边。
  • 矩形用大写字母表示,并且每个矩形的表示符号都不相同。

PROGRAM NAME: frameup

INPUT FORMAT(file frameup.in)

第一行

两个用空格分开的整数:图像高 H (3 <= H <=30) 和图像宽 W (3 <= W <= 30) 

第二行到第 H+1 

行,每行 个字母。

OUTPUT FORMAT

SAMPLE OUTPUT (file frameup.out)

按照自底向上的顺序输出字母。如果有不止一种情况,按照字典顺序输出每一种情况(至少会有一种合法的顺序)。

SAMPLE INPUT (file frameup.in)

9 8
.CCC....
ECBCBB..
DCBCDB..
DCCC.B..
D.B.ABAA
D.BBBB.A
DDDDAD.A
E...AAAA
EEEEEE.. 

SAMPLE OUTPUT (file frameup.out)

EDABC