[游记&题解]2019暑假中山纪中集训day1

中山纪中NOIP2019提高组模拟赛

A组 day1 solution

Date:2019.8.1

Probelm A B C
题目名称 水叮当的舞步 Vani和Cl2捉迷藏 粉刷匠
Difficulty 1900 2200 2300
Finished Yes Yes Yes

简单游记

day 0

AM
坐飞机前往广州,第一次遇到飞机延误,还好只延误了半小时…
上了飞机才发现其他同学都带了书看qwq???只好睡觉1h+…
PM
出了机场,就收到了广州精心准备的台风欢迎大礼包。
坐了好久的车才到中山纪中,之前听说纪中是由一座公园改建的,果然如此,真的好大…
然后去找宿舍,看到房间心里咯噔一下…
接着去处理各种各样地杂事,就到吃饭时间了。
品尝了一下当地特色的擂茶,莫名想起暑假前几天研学时捣抹茶的可怕回忆。不过其他菜都很不错…
晚上会宿舍开始hi,很迟才睡…

day 1

AM
今天是集训的第一天。
早上起来太迟,不知道考试时间来迟了半个小时…[话说学校早饭的瘦肉汤面居然是…方便面]。
然后看第一题…暴力???
好像数据范围比暴力大那么一点,应该是个多项式复杂度的题。
随便猜了个结论,感觉好像是正确的…
所以是…BFS+状压DP???
然后开始写…
调过了样例之后随手出了组数据,然后就把自己hack掉了…[我hack我自己]
然后发现这个做法是假的…
此时离考试结束还有一小时…
然后看第二题,感觉比较简单。
简单分析了一下,发现就是求DAG的最大独立集…
然后想起前几天被Codeforce图上最大独立集最大匹配结论题,坑出前100的惨痛经历…[10min 猜出结论,后面1h都在证明…]。
由于一般图上最大独立集显然是个NP问题…
显然如果图是个DAG的话还是会和二分图有一些关系。
最简单的DAG转二分图显然是将DAG拆成两个点连边。
发现这样做好像能过样例…
于是半信半疑的写完代码…
这时只剩下了15min
怀着看客的心情看完了C,发现可能用一个组合数插板法就做出来了…
然后试着打了一打水法,发现样例二就过不了了。
此时还有5min…
该交代码了,预估了一下,如果我B题WA了,岂不是直接垫底啊qwq。。。
于是day1直接没有交代码…
PM
下午开始改题…
发现B做法没错…
然后发现A是近1年半都没用过的IDA*算法…
C果然是插板法,但还是比较难做…

晚上回宿舍又打算hi,然后发现这里的宿管查房评频率真高…
然后就好好睡觉了…

水叮当的舞步 (dance)

[题解]

算法标签:DFS,IDA*,暴力
其实自己对这类复杂度玄学的题目并没有什么好的看法…
做法其实相当简单,但写起来也需要一定的小技巧。

其实看到这道题首先显然会想到BFS,然而我们发现BFS需要保存上一次所访问过的一个8*8的vis数组,这样做起来比较麻烦…

更好的做法是IDA*,我们可以借鉴BFS的思路,每次BFS限制一下深度,这样我们就能够很方便的继承上一层的信息。

IDA*的优点在于它可以剪枝,最显然的剪枝就是当颜色数+当前深度大于限定深度时,直接退出即可。

至于为什么IDA*可以通过本题,是因为本题的颜色数量相当的少,因此DFS最多是一个6叉完全数,注意到每一层都会是之前前面几层的节点数总和的几倍,因此我们针对深度大的节点进行剪枝,会得到相当好的效果。

[实现]

#include
#include
#include
#include
#include
#include
using namespace std;
#define MAXN 9
#define Pr pair
#define X first
#define Y second
int n,a[MAXN+1][MAXN+1];
int vis[MAXN+1][MAXN+1];
int px[4]={1,-1,0,0},py[4]={0,0,1,-1};
int tmp[MAXN+1],F,d;
void color(int x,int y,int col)
{
	vis[x][y]=1;
	for(int i=0;i<4;i++){
		int nx=x+px[i],ny=y+py[i];
		if(nx<1||ny<1||nx>n||ny>n||vis[nx][ny]==1)continue;
		vis[nx][ny]=2;
		if(a[nx][ny]==col)color(nx,ny,col);
	}
}
int rmcol(){
	int res=0;
	memset(tmp,0,sizeof(tmp));
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		if(vis[i][j]!=1&&!tmp[a[i][j]])
		tmp[a[i][j]]=1,res++;
	return res;
}
bool check(int col) {
    int flag=0;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
		if(a[i][j]==col&&vis[i][j]==2)
		flag=1,color(i,j,col);
    return flag;
}
void dfs(int x)
{
	int val=rmcol(),tp[MAXN+1][MAXN+1];
	//printf("%d::%d %d %d\n",d,x,val,F);
	if(val==0){F=1;return ;}
	if(val+x>d)return ;
	for(int i=1;i<=6;i++){
		memcpy(tp,vis,sizeof(tp));
		if(check(i))dfs(x+1);
		memcpy(vis,tp,sizeof(vis));
		if(F)return ;
	}
}
int main()
{
	while(~scanf("%d",&n)&&n)
	{
		F=0;memset(vis,0,sizeof(vis));
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
			{scanf("%d",&a[i][j]);a[i][j]++;}
		color(1,1,a[1][1]);
		d=0;
		while(1){
			dfs(0);
			if(F){printf("%d\n",d);break;}
			d++;
		}
	}
}

Vani和Cl2捉迷藏 (seek)

[题解]

算法标签:DAG,二分图匹配,Floyd

其实自己很疑惑为什么这题会有这么多人得到满分…

直到几天后在《算法竞赛进阶指南》上翻到了原题…[就是那个在那里基本上人手一本的那本书…]

其实在那本书里已经讲得很详细了,这里就不再赘述了吧…

复杂度: O ( n 3 ) O(n^3) O(n3)

[实现]

#include
#include
#include
#include
#include
using namespace std;
#define MAXN 200
#define MAXM 80000
int n,m,ans;
int dis[MAXN+1][MAXN+1];
int vis[MAXN+5],match[MAXN+5],head[MAXN+5],ecnt;
int dfs(int x)
{
	if(vis[x])return 0;
	vis[x]=1;
    for(int i=1;i<=n;i++)
    if(dis[x][i]){
        if(match[i]==-1||dfs(match[i])){
            match[i]=x;
            return 1;
        }
    }
    return 0;
}
int main()
{
	memset(head,-1,sizeof(head));
	memset(match,-1,sizeof(match));
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++){
		int u,v;
		scanf("%d%d",&u,&v);
		dis[u][v]=1;
	}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
		if((i!=j)&&(!dis[i][j]))
			for(int k=1;k<=n;k++)
			dis[i][j]|=(dis[i][k]&dis[k][j]);
	for(int i=1;i<=n;i++)
    {
        memset(vis,0,sizeof(vis));
        ans+=dfs(i);
    }
	printf("%d",n-ans);
}

粉刷匠 (paint)

[题解]

算法标签:dp,组合数学

一道组合数学题。

先考虑只有两个颜色的情况。

显然可以先将第一种颜色排好,然后用第二种颜色分配到其余空隙中。

设第一种颜色的有 k k k根,那么会产生 k − 1 k-1 k1个空隙,这些空隙中必须至少放一个不同颜色的石柱,与此同时,还会产生另外 2 2 2个空隙(我们将两边称为空隙),这两个空隙我们可以放石柱,也可以不放。

因此,我们定义合法空隙为中间可以放石柱,也可以不放的空隙,不合法的同理。

d p [ i ] [ j ] dp[i][j] dp[i][j]为当前放到第 i i i种颜色,产生了 j j j个非法空隙的方案数,考虑如何转移。

注意到新增加的非法空隙的数量只与你将多少的石柱插入到多少个空隙中有关,即产生的空隙数为插入石柱数量减去要插入到多少个空隙。

与此同时,我们选中的空隙中是非法空隙的也会消失。

因此我们每次先枚举 i , j i,j i,j,再枚举在空隙中选多少个合法空隙,多少个非法空隙,然后转移即可。

时间复杂度: O ( T N C K 2 ) O(TNCK^2) O(TNCK2)

[实现]

#include
#include
#include
#include
#include
using namespace std;
#define MAXN 90
#define MAXK 15
#define MOD 1000000007
#define LL long long
int T,k,c[MAXK+1];
int dp[MAXK+1][MAXN+1];
LL Comb[MAXN+1][MAXN+1];
int main()
{
	for(int i=0;i<=MAXN;i++)
		for(int j=0;j<=i;j++)
		if(!i||i==j)Comb[i][j]=1;
		else Comb[i][j]=(Comb[i-1][j]+Comb[i-1][j-1])%MOD;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&k);
		memset(dp,0,sizeof(dp));
		int n=0;
		for(int i=1;i<=k;i++)
		{scanf("%d",&c[i]);n+=c[i];}
		int ps=0;
		dp[0][0]=1;
		for(int i=1;i<=k;i++)
		{	
			for(int j=0;j<=ps;j++)//j个非法空隙 
				for(int k=0;k<=min(j,c[i]);k++)//其中分配k个非法空隙放当前一些数字 
					for(int p=0;p<=min(c[i]-k,ps+1-j);p++)//其中分配p个合法空隙放当前其余数字 
					{
						if(!(k+p))continue;
						LL val=dp[i-1][j];int nj=j+(c[i]-k-p)-k;
						val=(((val*Comb[c[i]-1][k+p-1])%MOD*Comb[j][k])%MOD*Comb[ps+1-j][p])%MOD;
						dp[i][nj]=(dp[i][nj]+val)%MOD;
					}
			ps+=c[i];
		}
		printf("%d\n",dp[k][0]);
	}
}

你可能感兴趣的:(游记合辑)