关于题目描述:附件中会有一份中文的题目,所以以下题解不再赘述题意。
E-easy N-normal H-hard VH-very hard
A – A problem on tree (N)
Solution
Tag:贪心,结论。
我们首先来考虑一条链的情况:设第一个点的值是a,第二个是b,第三个是c。。。即a-b-c-d。。。那么有:
ch[1]=a
ch[2]=a+b
ch[3]=max(2a+c,a+b+c)
ch[4]=max(3a+d,2a+2b+d,2a+c+d,a+b+c+d)
ch[5]=max(4a,3a+3b,4a+c,2a+2b+2c,value[4])+e
可以发现4a+c>4a,所以有结论:一个点的ch值最多从不超过3层的地方转移而来。
那么现在题目就很简单了,记录每个节点最下三层最大的ch值,然后当前节点从这三个值中取最大的转移即可。复杂度O(3*N)。
PS:给的式子可以很容易的看出来是具有单调性的,而且在链上的时候确实存在,但是在树上的时候就不一定了(貌似是没有?),顺带求单调队列做法(如果有单调性的话)。
B – Books changing(E)
Solution
Tag:数据结构,模拟。
本题并不考察算法,其实仔细观察的话可以发现A和B实际上就是一个双端队列,按照题目里面说的一步步模拟就可以了,因为每本书最多被移动2次,所以总的复杂度是O(2*N)。
C – Come to aspring outing(N)
Solution
Tag:动态规划。
一个最直观的想法是dp[i][j][k][l]表示前i个物品,第一个背包装了j,第二个背包装了k,第三个背包装了l是否可行,然后方程是dp[i][j][k][l]=dp[i-1][j-v[i]][k][l]| dp[i-1][j][k-v[i]][l] | dp[i-1][j][k][l-v[i]], 但是这样做时间和空间复杂度显然无法承受,再来观察一下可以发现,前i个物品的时候,如果已知i和j背包的容量,那么l背包的容量是可以算出来的,l = sum(v[1~i])-i-j。这样的话复杂度就可以降一维,dp[i][j][k] = dp[i-1][j-v[i]][k]| dp[i-1][j][k-v[i]] | dp[i-1][j][k]。算法时间复杂度O(N*M*M)。
D – Deal withnumbers(N)
Solution
Tag:数据结构
线段树的结构还是很容易知道的,假设只有减法操作和询问操作的话,就是一个很常见的题目,但是对于除法操作的话,延迟标记显然是不行的,因为sum[n]/k!=a1/k+a2/k+…an/k。
因为只有除法和减法操作,一个很好的性质就是一个数只会减少,不会增多,减法操作可以用lazy_tag很容易地处理掉,对于除法操作,因为一个数即使是每次被2除,在除了十几次之后也会变成0,那么可知除法操作的总次数为logai*n,这并不是一个非常大的数,于是遇到除法操作我们就直接做到底就好了,然后减法操作仍然按照常规线段树操作来做,注意除法的时候如果除数是1,那么就直接跳过,不然会Tle。复杂度O(N*log(N)+log(a)*N)。
E – Easy game(H)
Solution
Tag:结论。
首先可以知道一个东西,一个格子(x,y)只和(x-2,y),(x+2,y),(x,y-2),(x,y+2)相关,并且它移动的时候与他不相关的格子不会改变,那么原图我们就可以分成4个小图,原图中的旋转操作可以认为是在小图中转田字的四个点。
下面来研究小图的情况:对于一个n*m的矩阵,已经有一些黑格子顺次排放了,如
00100
00000
11100
11111
首先可以证明一个还未按顺序排放的格子可以在不干扰已经顺序排放的格子的情况下,放置在下一个应该放的位置,即上图可以变成
00000
00000
11110
11111
并且该结论在只有最后一行全为0的时候仍适用,那么我们可以得出结论:在小图行和列均超过2行的情况下,我们总是有方法可以把格子转成顺次排列。
之所以说要超过2行,因为在2行以下的时候会有不能转的格子,这种情况得分类讨论。
如果四个小图均可以,我们可以通过这四个小图中的黑格子数量来判断是否可以组成一个满足条件的矩阵,
复杂度O(N*M)。
F – Finding home(VH)
Solution
Tag:模拟
题目的思路还是很好想的,就是枚举时间,然后暴力模拟每个人的动作,包括碰撞,如果一个人所在的路两端都有人占了,那么他就是-1。
关于碰撞的话情况非常多,包括在岛上碰撞,在边上碰撞,两个人在整点碰撞,两个人在非整点碰撞,来回碰撞。。。有种比较好写的方式是每次模拟半步,这样的话碰撞的情况会少掉很多,注意如果一个岛被占了,两个人同时走上来不认为fight了。而是各自反向。
G – Game world(H)
Solution
Tag:图论
首先点双连通分量缩点,原图成为一棵树,对于询问树上的两个点,我们可以通过最近公共祖先的方法在log(n)的时间内得到其路上的关节点数量,然后就是传送阵的判断,如果两个城市均是关节点,并且其另外一段都有传送阵的话,那么答案为2(自身),否则如果有一个点是连通分量点,那么传送阵是不能使用的,答案就是中间的关节点数量。
复杂度O(nlogn)
H – Hard mathproblem(N)
Solution
Tag:枚举
首先可以证明,三个数中的其中一个数是不必要的。
假设开始的三个数a, b, c满足a>=b>=c。那么无论用a + b替换掉a,b,c中的
任意一个,都会发现c在接下来的操作中毫无用处。
于是问题转化为,用每次用a + b的和替换掉a,b中的任意一个,如何凑出x。
接下来,从反面解答此题会非常容易。
除了x以外,枚举最后的状态(x,y),且满足x > y。那么显然可以推出前面唯一对应的状态(y, x - y),这样依次递推下去,若中间过程包含了初始时的状态,那么最终的状态就是可达的。
PS:因为要得出的是最大的数,所以1的答案是-1,虽然后面已经在很努力的让notice醒目点了,不过貌似还是把某支队给坑了>.<。
I – Invading system(E)
Solution
Tag:模拟
直接做就行,签到题。
J – Join in tasks(N)
Solution
Tag:贪心,数学。
假设已经有一个序列,对于相邻的两个数a,b(a>b),可以证明在a>b的时候如果将a,b互换可以得到更优的解,那么可以得出最优的原序列一定是按照T从小到大排列的。
对于一个已经排列好的序列,现在的问题是如何算其总等待时间。对于一个小一点的任务a,他后面所有的任务都要等待a+a-1+a-2+…+1,对于一个大一点的任务b,他前面的任务的等待时间是满足一个线性公式的,具体见代码。
UPD:数据和标程下载链接 http://acm.whu.edu.cn/13cc/attachments/WHUACM2013data.zip
UPD1:前面的C题代码有bug,以下面代码为准,非常抱歉!
/* * Author: chlxyd * Created Time: 2013/3/28 14:43:42 * File Name: C.cpp */ #include<iostream> #include<sstream> #include<fstream> #include<vector> #include<list> #include<deque> #include<queue> #include<stack> #include<map> #include<set> #include<bitset> #include<algorithm> #include<cstdio> #include<cstdlib> #include<cstring> #include<cctype> #include<cmath> #include<ctime> using namespace std; const double eps(1e-8); typedef long long lint; #define clr(x) memset( x , 0 , sizeof(x) ) #define sz(v) ((int)(v).size()) #define rep(i, n) for (int i = 0; i < (n); ++i) #define repf(i, a, b) for (int i = (a); i <= (b); ++i) #define repd(i, a, b) for (int i = (a); i >= (b); --i) #define clrs( x , y ) memset( x , y , sizeof(x) ) int n , m ; int w[50] ; bool dp[40][410][410] ; int sum[50] ; string solve() { repf( i , 1 , m ) repf( j , 0 , m ) if ( dp[n][i][j] ) return "Yes" ; return "No" ; } void update( bool &x , bool y ) { x = x || y ; } int main(){ int ca = 0 ; freopen("c1.out","w",stdout) ; int t ; scanf("%d" , &t ) ; while ( scanf("%d %d" , &n , &m ) == 2 ) { clr( sum ) ; clr(dp) ; repf( i , 1 , n ) { scanf("%d" , &w[i] ) ; sum[i] = sum[i-1] + w[i] ; } dp[0][0][0] = true ; repf( i , 1 , n ) repf( j , 0 , m ) repf( k , 0 , m ) { int l = sum[i] - j - k ; if ( l < 0 || l > m ) continue ; bool &v = dp[i][j][k] ; if ( j >= w[i] ) update( v , dp[i-1][j-w[i]][k] ) ; if ( k >= w[i] ) update( v , dp[i-1][j][k-w[i]] ) ; if ( l >= w[i] ) update( v , dp[i-1][j][k] ) ; } printf("Case %d: %s\n" , ++ca , solve().c_str() ) ; } }