纪中DAY1做题小结
T1:水叮当的舞步
Description
水叮当得到了一块五颜六色的格子形地毯作为生日礼物,更加特别的是,地毯上格子的颜色还能随着踩踏而改变。为了讨好她的偶像虹猫,水叮当决定在地毯上跳一支轻盈的舞来卖萌~~~地毯上的格子有N行N列,每个格子用一个0 ~ 5之间的数字代表它的颜色。水叮当可以随意选择一个0 ~ 5之间的颜色,然后轻轻地跳动一步,地毯左上角的格子所在的联通块里的所有格子就会变成她选择的那种颜色。这里连通定义为:两个格子有公共边,并且颜色相同。由于水叮当是施展轻功来跳舞的,为了不消耗过多的真气,她想知道最少要多少步才能把所有格子的颜色变成一样的。
Input
每个测试点包含多组数据。
每组数据的第一行是一个整数N,表示地摊上的格子有N行N列。
接下来一个N*N的矩阵,矩阵中的每个数都在0~5之间,描述了每个格子的颜色。
N=0代表输入的结束。
Output
对于每组数据,输出一个整数,表示最少步数。
Sample Input
2 0 0 0 0 3 0 1 2 1 1 2 2 2 1 0
Sample Output
0 3
Data Constraint
对于30%的数据,N<=5
对于50%的数据,N<=6
对于70%的数据,N<=7
对于100%的数据,N<=8,每个测试点不多于20组数据。
简要思路:本题数据范围较小,一开始考虑爆搜,然而根据以往经验这肯定不妥。后面,自然而然想到IDE*迭代搜索,不过刚开始代码总调不对,感谢这份博客为我提供了思路。简单来说,就是先进行一次dfs将图分成三个区域,内圈(与左上角颜色相同的区域),内圈边缘以及外圈,分别进行标记(我标的分别是1 , 2 , 0)。不断枚举0至5六种颜色,通过内圈边缘同色点(如果有)进行对外扩展,通过控制层数来控制时间复杂度。这里还有一个小剪枝,就是每递归到一层时同记外圈的颜色种类数,这就是期望最少扩张数。如果当前层数加上期望最少扩张数大于层数限制,就可以返回了。最后说一句,记得回溯。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 10;
int map[N][N] , jud[N] , flag[N][N];
int xx[5] = { 0 , 1 , -1 , 0 , 0 };
int yy[5] = { 0 , 0 , 0 , 1 , -1 };
int n , res;
void read( int & res ) {
res = 0;
int pd = 1;
char a = getchar();
while ( a < '0' || a > '9' ) {
if ( a == '-' ) {
pd = -pd;
}
a = getchar();
}
while ( a >= '0' && a <= '9' ) {
res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
a = getchar();
}
res *= pd;
return;
}
void dfs( int x , int y , int col ) {
flag[x][y] = 1;
int xxx , yyy;
for ( int i = 1 ; i <= 4 ; ++i ) {
xxx = x + xx[i];
yyy = y + yy[i];
if ( xxx >= 1 && xxx <= n && yyy >= 1 && yyy <= n && flag[xxx][yyy] != 1 ) {
flag[xxx][yyy] = 2;
if ( map[xxx][yyy] == col ) {
dfs( xxx , yyy , col );
}
}
}
return;
}
int getcol() {
int tot = 0;
memset( jud , 0 , sizeof(jud) );
for ( int i = 1 ; i <= n ; ++i ) {
for ( int j = 1 ; j <= n ; ++j ) {
if ( !jud[map[i][j]] && flag[i][j] != 1 ) {
jud[map[i][j]] = 1;
tot++;
}
}
}
return tot;
}
int judge( int col ) {
int tot = 0;
for ( int i = 1 ; i <= n ; ++i ) {
for ( int j = 1 ; j <= n ; ++j ) {
if ( map[i][j] == col && flag[i][j] == 2 ) {
tot++;
dfs( i , j , col );
}
}
}
return tot;
}
int check( int x ) {
int tot = getcol();
if ( tot + x > res ) {
return 0;
}
if ( !tot ) {
return 1;
}
int cp[N][N];
for ( int i = 0 ; i <= 5 ; i++ ) {
memcpy( cp , flag , sizeof(flag) );
if ( judge(i) && check( x + 1 ) ) {
return 1;
}
memcpy( flag , cp , sizeof(flag) );
}
return 0;
}
int main () {
while ( scanf("%d",&n) != EOF && n != 0 ) {
for ( int i = 1 ; i <= n ; ++i ) {
for ( int j = 1 ; j <= n ; ++j ) {
read(map[i][j]);
}
}
memset( flag , 0 , sizeof(flag) );
dfs( 1 , 1 , map[1][1] );
for ( res = 0 ; res <= n * n ; ++res ) {
if ( check(0) ) {
break;
}
}
printf("%d\n",res);
}
return 0;
}
T2:Vani和Cl2捉迷藏
Description
vani和cl2在一片树林里捉迷藏……
这片树林里有N座房子,M条有向道路,组成了一张有向无环图。
树林里的树非常茂密,足以遮挡视线,但是沿着道路望去,却是视野开阔。如果从房子A沿着路走下去能够到达B,那么在A和B里的人是能够相互望见的。
现在cl2要在这N座房子里选择K座作为藏身点,同时vani也专挑cl2作为藏身点的房子进去寻找,为了避免被vani看见,cl2要求这K个藏身点的任意两个之间都没有路径相连。
为了让vani更难找到自己,cl2想知道最多能选出多少个藏身点?
Input
第一行两个整数N,M。
接下来M行每行两个整数x、y,表示一条从x到y的有向道路。
Output
一个整数K,表示最多能选取的藏身点个数。
Sample Input
4 4 1 2 3 2 3 4 4 2
Sample Output
2
Data Constraint
对于20% 的数据,N≤10,M<=20。
对于60% 的数据, N≤100,M<=1000。
对于100% 的数据,N≤200,M<=30000,1<=x,y<=N。
简要思路:这题涉及到传递闭包,二分图最大匹配,最大独立集等于最小点覆盖这三个知识点。首先,我们通过利用floyd传递闭包,快速整理出一个数组保存从 a a a点能否到 b b b点的0/1值。其次,因为这个图有向无环,必为二分图,可用求二分图最大匹配的方法来求出这个图的最小点覆盖,再利用最大独立集等于最小点覆盖这个定理(自己上网找证明)求解,最终答案为总点数减去被匹配的点对数。
#include <iostream>
#include <cstdio>
#include <cstring>
#define N 205
using namespace std;
int n , m;
int vis[N] , map[N][N] , match[N];
inline void read( int & res ) {
res = 0;
int pd = 1;
char a = getchar();
while ( a < '0' || a > '9' ) {
if ( a == '-' ) {
pd = -pd;
}
a = getchar();
}
while ( a >= '0' && a <= '9' ) {
res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
a = getchar();
}
res *= pd;
return;
}
inline int dfs( int cur ) {
for ( int i = 1 ; i <= n ; ++i ) {
if ( map[cur][i] && !vis[i] ) {
vis[i] = 1;
if ( !match[i] || dfs( match[i] ) ) {
match[i] = cur;
return 1;
}
}
}
return 0;
}
int main () {
read(n);
read(m);
int x , y;
for ( int i = 1 ; i <= m ; ++i ) {
read(x);
read(y);
map[x][y] = 1;
}
for ( int k = 1 ; k <= n ; ++k ) {
for ( int i = 1 ; i <= n ; ++i ) {
for ( int j = 1 ; j <= n ; ++j ) {
if ( k == i || k == j || i == j ) {
continue;
}
map[i][j] |= map[i][k] & map[k][j];//传递闭包
}
}
}
int res = n;
for ( int i = 1 ; i <= n ; ++i ) {
memset( vis , 0 , sizeof(vis) );
res -= dfs(i);
}
printf("%d",res);
return 0;
}
T3:粉刷匠
Description
赫克托是一个魁梧的粉刷匠,而且非常喜欢思考= =
现在,神庙里有N根排列成一直线的石柱,从1到N标号,长老要求用油漆将这些石柱重新粉刷一遍。赫克托有K桶颜色各不相同的油漆,第i桶油漆恰好可以粉刷Ci根石柱,并且,C1+C2+C3…CK=N(即粉刷N根石柱正好用完所有的油漆)。长老为了刁难赫克托,要求相邻的石柱颜色不能相同。
喜欢思考的赫克托不仅没有立刻开始粉刷,反而开始琢磨一些奇怪的问题,比如,一共有多少种粉刷的方案?
为了让赫克托尽快开始粉刷,请你尽快告诉他答案。
Input
第一行一个正整数T,表示测试数据组数
对于每一组测试数据数据:
第1行:一个正整数K
第2行:K个正整数,表示第i桶油漆可以粉刷的石柱个数,Ci。
Output
对于每组输入数据,输出一行一个整数,表示粉刷的方案数mod 1000000007。
Sample Input
3 3 1 2 3 5 2 2 2 2 2 10 1 1 2 2 3 3 4 4 5 5
Sample Output
10 39480 85937576
简要思路:这题是一道非常好的排列组合的题,没做过还真的不会qwq。
首先我们用DP方程
f
[
i
]
[
j
]
f[i][j]
f[i][j]表示涂上前
i
i
i种颜色后有
j
j
j对涂色不合法的相邻石柱(这里的相邻是忽略未涂色石柱后的相邻)。那么,每当我们涂上一种颜色时,我们考虑把能涂上
a
a
a根柱子的颜色分成
k
k
k份颜色,其中
l
l
l份插入之前
j
j
j对不合法的石柱之间,剩下的
k
−
l
k-l
k−l份放入其余地方,包括两端。若设
m
m
m为先前已涂好的石柱数,不难得出状态转移方程为(既有加法原理,又有乘法原理):
f
[
i
]
[
j
−
k
−
l
+
a
]
+
=
f
[
i
−
1
]
[
j
]
∗
C
a
−
1
k
−
1
∗
C
j
l
∗
C
m
+
1
−
j
k
−
l
f[i][j - k - l + a] += f[i - 1][j] * C^{k - 1}_{a - 1} *C^{l}_{j} * C^{k - l}_{m + 1 - j}
f[i][j−k−l+a]+=f[i−1][j]∗Ca−1k−1∗Cjl∗Cm+1−jk−l,其中
C
a
−
1
k
−
1
C^{k - 1}_{a - 1}
Ca−1k−1表示将能涂上
a
a
a根柱子的颜色分成
k
k
k份颜色的方案数,
C
j
l
C^{l}_{j}
Cjl表示将
l
l
l份颜色插入之前
j
j
j对不合法的石柱之间的方案数,
C
m
+
1
−
j
k
−
l
C^{k - l}_{m + 1 - j}
Cm+1−jk−l表示剩下的
k
−
l
k-l
k−l份放入其余地方的方案数。
最后处理好细节即可。
#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
const ll mod = 1e9 + 7;
ll c[95][95];
ll f[20][95];
int t , n , m , a;
void read( int & res ) {
res = 0;
int pd = 1;
char a = getchar();
while ( a < '0' || a > '9' ) {
if ( a == '-' ) {
pd = -pd;
}
a = getchar();
}
while ( a >= '0' && a <= '9' ) {
res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
a = getchar();
}
res *= pd;
return;
}
int main () {
c[0][0] = 1;
for ( int i = 1 ; i <= 91 ; i++ ) {
c[i][i] = 1;
c[i][0] = 1;
for ( int j = 1 ; j < i ; j++ ) {
c[i][j] = c[i - 1][j - 1] + c[i - 1][j];
c[i][j] %= mod;
}
}
read(t);
while ( t-- ) {
memset( f , 0 , sizeof(f) );
f[0][0] = 1;
read(n);
m = 0;
for ( int i = 1 ; i <= n ; ++i ) {
read(a);
for ( int j = 0 ; j < max( m , 1 ) ; ++j ) {
if ( f[i - 1][j] ) {
for ( int k = 1 ; k <= a ; ++k ) {
int end = min( a , j );
for ( int l = 0 ; l <= end ; ++l ) {
f[i][j - k - l + a] += ( ( ( ( ( f[i - 1][j] * c[a - 1][k - 1] ) % mod ) * c[j][l] ) % mod ) * c[m + 1 - j][k - l] ) % mod;
f[i][j - k - l + a] %= mod;//日常膜一膜
}
}
}
}
m += a;
}
printf("%lld\n",f[n][0]);
}
return 0;
}