2019.8.1中山纪中提高A组题解

【NOIP2013模拟】水叮当的舞步

题目描述

水叮当得到了一块五颜六色的格子形地毯作为生日礼物,更加特别的是,地毯上格子的颜色还能随着踩踏而改变。
为了讨好她的偶像虹猫,水叮当决定在地毯上跳一支轻盈的舞来卖萌~~~
地毯上的格子有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组数据。

题意强调:注意每次都是左上角所在的联通块变色


题解

很显然的可以先想到可以搜索。每次枚举左上角所在的联通块将变成什么颜色,直接一直搜索到全块变成同一个颜色即可。
见代码
在这里插入图片描述
同样显然的是这个会T掉,所以需要优化一下我们的搜索。
S t e p 1 Step 1 Step1:考虑一个很常见的搜索方式 I D A ∗ IDA^{*} IDA。具体来说,在最优情况下,有几种不同颜色就至少要跳几次,令这个最有情况的步数为 t t t,则搜索层数 x + t &lt; = M a x D x+t&lt;=MaxD x+t<=MaxD,否则直接 r e t u r n return return
S t e p 2 Step 2 Step2: 有另一个可能比较显然而有用的优化(据说上一个勉强能过),每一次修改联通块的颜色必须要使它新的颜色与相邻的位置的颜色中有相同的,不然就是一步无用修改。


代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#define INF int(1e9+7)
const int MAXN=10;
int n;
int MaxD;
int map[MAXN][MAXN];
int vis[MAXN][MAXN];
int fx[]={0,0,-1,1},fy[]={1,-1,0,0};
void Col(int x,int y,int col) {
	vis[x][y]=1;
	for(int i=0;i<4;i++) {
		int nx=x+fx[i],ny=y+fy[i];
		if(nx<1||ny<1||nx>n||ny>n||vis[nx][ny]==1)
			continue;
		vis[nx][ny]=2;
		if(map[nx][ny]==col)
			Col(nx,ny,col);
	}
}//将(1,1)所在的联通块标记并标记标出与联通块相邻的鸽子
int Find() {
	int ret=0;
	bool c[10]={};
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(vis[i][j]!=1&&!c[map[i][j]]) {
				c[map[i][j]]=1;
				ret++;
			}
	return ret;
}//找联通块以外还有多少种颜色,方便搜索提前判断在最理想情况下是否可行
bool Check(int col) {
	int ret=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			if(vis[i][j]==2&&map[i][j]==col) {
				ret++;
				Col(i,j,col);
			}
	return ret>0;
}//判断与联通块相邻的格子里有没有不同颜色,即Step2中的优化
bool flag;
void Dfs(int x) {
	int f=Find();
	int tmp[MAXN][MAXN];
	if(!f) {
		flag=1;
		return;
	}
	if(x+f>MaxD)
		return;
	for(int i=0;i<=5;i++) {
		memcpy(tmp,vis,sizeof(vis));
		if(Check(i)) Dfs(x+1);
		memcpy(vis,tmp,sizeof(tmp));
		if(flag)
			return;
	}
}
void Init() {
	memset(vis,0,sizeof(vis));
	flag=0;
}
int main()
{
	while(~scanf("%d",&n)&&n) {
		Init();
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				scanf("%d",&map[i][j]);
		Col(1,1,map[1][1]);
		//puts("OK");
		MaxD=0;
		for(MaxD=0;;MaxD++) {
			Dfs(0);
			if(flag) {
				printf("%d\n",MaxD);
				break;
			}
		}
	}
}

【NOIP2013模拟】Vani和Cl2捉迷藏

题目描述

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。

题解

我很懵逼过得勉强不想说话别看我的所以去看MiMiZhaner
如果你不觉得那是一篇题解的话就从来一次吧:
【NOIP2013模拟】Vani和Cl2捉迷藏


【NOIP2013模拟】粉刷匠

题目描述

赫克托是一个魁梧的粉刷匠,而且非常喜欢思考= =
现在,神庙里有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

Data Constraint

30% N≤10, T≤5
50% N≤15, T≤5
80% K≤15,Ci≤5,T≤500
100% K≤15,Ci≤6,T≤2000

题解

为了和组合数区分,下文中我们将题面里的 C [ i ] C[i] C[i]改名为 a [ i ] a[i] a[i]
这道题是一个计数问题,考虑搜索或 D p Dp Dp。冥冥之中有一个声音指导我们用 D p Dp Dp
状态: D p [ i ] [ j ] Dp[i][j] Dp[i][j]用完前 i i i种油漆使 j j j组相邻柱子颜色相同(相应的涂完的柱子数为 ∑ j = 1 i a [ j ] \sum\limits_{j=1}^{i}{a[j]} j=1ia[j]

注意:不是最后的固定顺序,中间的空是可以安插柱子进行涂色的; j j j可以理解为在描述空位两边

在这里插入图片描述
该图中 j = 4 j=4 j=4
然后考虑一下转移。
我们现在要做的是把已知的 f [ i − 1 ] [ j ] f[i-1][j] f[i1][j]的状态转移到 f [ i ] [ . . . ] f[i][...] f[i][...]。更形象的说就是把当前的 a [ i ] a[i] a[i]桶油漆涂进这个序列中(往空位里面放)。根据状态定义我们需要知道新的 j j j
所有我们把 a [ i ] a[i] a[i]桶油漆分成 k k k组( C a [ i ] − 1 k − 1 C_{a[i]-1}^{k-1} Ca[i]1k1(想成 x 1 + x 2 + . . . . . . + x n = s ( x i &gt; = 1 ) x1+x2+......+xn=s(xi&gt;=1) x1+x2+......+xn=sxi>=1然后用隔板法)),其中把 t t t组放入以前相邻颜色相同的空中,把剩下的放进剩下的格子中,是可以放开头结尾的。
先算出这个操作的方案数

C a [ i ] − 1 k − 1 ∗ C j t ∗ C s [ i − 1 ] + 1 − j k − t C_{a[i]-1}^{k-1}*C_{j}^{t}*C_{s[i-1]+1-j}^{k-t} Ca[i]1k1CjtCs[i1]+1jkt

想一想为什么不乘 C k t C_{k}^{t} Ckt

再计算一下之后的相邻颜色相同的空位数

j ′ = j − t + a [ i ] − k j^{&#x27;}=j-t+a[i]-k j=jt+a[i]k

新增了 a [ i ] − k a[i]-k a[i]k:本来 a [ i ] a[i] a[i]个有 a [ i ] − 1 a[i]-1 a[i]1个,分成了 k k k段,要切 k − 1 k-1 k1刀,切一刀少一个所以是多了 a [ i ] − 1 − ( k − 1 ) a[i]-1-(k-1) a[i]1(k1) a [ i ] − k a[i]-k a[i]k个。

状态转移方程就出来了:
f [ i ] [ j − t + a [ i ] − k ] + = f [ i − 1 ] [ j ] ∗ C a [ i ] − 1 k − 1 ∗ C j t ∗ C s [ i − 1 ] + 1 − j k − t f[i][j-t+a[i]-k]+=f[i-1][j]*C_{a[i]-1}^{k-1}*C_{j}^{t}*C_{s[i-1]+1-j}^{k-t} f[i][jt+a[i]k]+=f[i1][j]Ca[i]1k1CjtCs[i1]+1jkt
记得取模。

代码

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN=int(2e1+5);
#define mod int(1e9+7)
int n;
int f[MAXN][105];
int C[105][105];
int a[MAXN];
int s[MAXN];
void Prepare() {
    int r=100;
    for(int i=0;i<=r;i++)
        C[i][i]=C[i][0]=1,C[i][1]=i;
    for(int i=1;i<=r;i++)
        for(int j=2;j<=i-1;j++)
            C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
int main()
{
    Prepare();
    int T;
    scanf("%d",&T);
    while(T--) {
        int n;
        scanf("%d",&n);
        for(int i=1;i<=n;i++) {
            scanf("%d",&a[i]);
            s[i]=s[i-1]+a[i];
        }
        f[0][0]=1;
        for(int i=1;i<=n;i++)
            for(int j=0;j<=s[i-1];j++)
                for(int k=1;k<=a[i];k++)
                    for(int t=0;t<=min(k,j);t++)
                        f[i][j-t+a[i]-k]=(f[i][j-t+a[i]-k]+1ll*f[i-1][j]*C[j][t]%mod*C[a[i]-1][k-1]*C[s[i-1]+1-j][k-t]%mod)%mod;
        printf("%d\n",f[n][0]);
        memset(f,0,sizeof(f));
    }
}

T h a n k s Thanks Thanks f o r for for r e a d i n g reading reading!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值