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掉,所以需要優化一下我們的搜索。
Step1Step 1:考慮一個很常見的搜索方式IDAIDA^{*}。具體來說,在最優情況下,有幾種不同顏色就至少要跳幾次,令這個最有情況的步數爲tt,則搜索層數x+t&lt;=MaxDx+t&lt;=MaxD,否則直接returnreturn
Step2Step 2: 有另一個可能比較顯然而有用的優化(據說上一個勉強能過),每一次修改聯通塊的顏色必須要使它新的顏色與相鄰的位置的顏色中有相同的,不然就是一步無用修改。


代碼

#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]改名爲a[i]a[i]
這道題是一個計數問題,考慮搜索或DpDp。冥冥之中有一個聲音指導我們用DpDp
狀態:Dp[i][j]Dp[i][j]用完前ii種油漆使jj組相鄰柱子顏色相同(相應的塗完的柱子數爲j=1ia[j]\sum\limits_{j=1}^{i}{a[j]}

注意:不是最後的固定順序,中間的空是可以安插柱子進行塗色的;jj可以理解爲在描述空位兩邊

在這裏插入圖片描述
該圖中j=4j=4
然後考慮一下轉移。
我們現在要做的是把已知的f[i1][j]f[i-1][j]的狀態轉移到f[i][...]f[i][...]。更形象的說就是把當前的a[i]a[i]桶油漆塗進這個序列中(往空位裏面放)。根據狀態定義我們需要知道新的jj
所有我們把a[i]a[i]桶油漆分成kk組(Ca[i]1kC_{a[i]-1}^{k}(想成x1+x2+......+xn=sxi&gt;=1x1+x2+......+xn=s(xi&gt;=1)然後用隔板法)),其中把tt組放入以前相鄰顏色相同的空中,把剩下的放進剩下的格子中,是可以放開頭結尾的。
先算出這個操作的方案數

Ca[i]1kCjtCs[i1]+1tkt C_{a[i]-1}^{k}*C_{j}^{t}*C_{s[i-1]+1-t}^{k-t}

想一想爲什麼不乘CktC_{k}^{t}

再計算一下之後的相鄰顏色相同的空位數

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

新增了a[i]ka[i]-k:本來a[i]a[i]個有a[i]1a[i]-1個,分成了kk段,要切k1k-1刀,切一刀少一個所以是多了a[i]1(k1)a[i]-1-(k-1)a[i]ka[i]-k個。

狀態轉移方程就出來了:
f[i][jt+a[i]k]+=f[i1][j]Ca[i]1k1CjtCs[i1]+1jktf[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}
記得取模。

代碼

#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));
    }
}

ThanksThanks forfor readingreading!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章