【模拟赛】纪中提高A组 19.8.1 测试

Posted on 2019-08-16 11:34  opethrax  阅读(159)  评论(1编辑  收藏

第一天,题目难度适中易改,出现了以前模拟赛做过的,但却没有做出来。

T1.水叮当的舞步

题目数据范围较小,考虑搜索。在每一步枚举当前选择的颜色。

此时时间复杂度为 \(O(ans^6)\)\(ans\) 最坏情况下为 \(N\times N\),若将搜索树整颗遍历完显然时间 \((N^{12})\) 是不能接受的。

因此:

  1. 考虑限制搜索深度以提高搜索效率 (IDA*) 。

  2. 考虑某个状态下,达到目标状态还需要的步数最小可能是剩下的颜色种类,作为一个剪枝。

IDA* + 剪枝 即可通过本题。

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
#include<queue>
using namespace std;

template<class T>void read(T &x){
	x=0; bool f=0; char c=getchar();
	while(c<'0'||'9'<c){f|=(c=='-'); c=getchar();}
	while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
	x=f?-x:x;
}

const int N=10;

int n,lim;
int a[N][N],cnt,t[N];
struct node{
	int x,y;
}p[N*N];
bool in[N][N];
int dx[5]={0,1,0,-1};
int dy[5]={1,0,-1,0};

void init() {
	memset(a,0,sizeof(a));
	memset(t,0,sizeof(t));
	memset(p,0,sizeof(p));
	memset(in,0,sizeof(in));
	lim=cnt=0;
}
void ins(int c) {
	for(int i=1;i<=cnt;i++)
		for(int j=0;j<=3;j++) {
			int nx=p[i].x+dx[j], ny=p[i].y+dy[j];
			if(nx<1||ny<1||nx>n||ny>n) continue;
			if(a[nx][ny]==c) if(!in[nx][ny]) {
				--t[a[nx][ny]];
				p[++cnt]=(node){nx,ny};
				in[nx][ny]=1;
			}
		}
}
void del(int rem) {
	while(cnt>rem){
		++t[a[p[cnt].x][p[cnt].y]];
		in[p[cnt].x][p[cnt].y]=0;
		p[cnt--]=(node){0,0};
	}
}
bool dfs(int dep){
//	if(dep>lim) return 0;
	int tmp=0;
	for(int i=0;i<=5;i++) if(t[i]) ++tmp;
	if(dep+tmp>lim) return 0;
	if(cnt==n*n) return 1;
	for(int i=0;i<=5;i++) {
		tmp=cnt; ins(i);
		if(tmp!=cnt) {
			bool suc=dfs(dep+1);
			del(tmp);
			if(suc) return 1;
		}
	}
	return 0;
}
		
void solve() {
	init();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++) {
			read(a[i][j]);
			++t[a[i][j]];
		}
	in[1][1]=1;
	p[++cnt]=(node){1,1};
	--t[a[1][1]];
	ins(a[1][1]);
	while(lim<=n*n) {
		if(dfs(0)) {
			printf("%d\n",lim);
			return ;
		}
		++lim;
	}
}

int main() {
//	freopen("a.in","r",stdin); 
//	freopen("a.out","w",stdout);
	read(n);
	while(n) {
		solve();
		read(n);
	}
	return 0;
}

T2.Vani和Cl2捉迷藏

题意是求一张有向图的最大反链。反链即一个点集,其中任意两点间不能到达。

在图上跑传递闭包,图转化为一张二分图,匈牙利算法求最大独立集即可通过本题。(原题 \(CTSC2008 river\)

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;

template<class T>void read(T &x){
	x=0; char c=getchar();
	while(c<'0'||'9'<c)c=getchar();
	while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
}

const int N=205;

int n,m,ans;
int match[N];
bool f[N][N],vis[N];

bool dfs(int x){
	for(int y=1;y<=n;y++) if(f[x][y]) if(!vis[y]) {
		vis[y]=1;
		if(!match[y]||dfs(match[y])){
			match[y]=x;
			return 1;
		}
	}
	return 0;
}

int main(){
	read(n); read(m);
	int x,y;
	for(int i=1;i<=m;i++){
		read(x); read(y);
		f[x][y]=1;
	}
	for(int k=1;k<=n;k++)
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n;j++)
				f[i][j]|=f[i][k]&&f[k][j];
	for(int i=1;i<=n;i++){
		memset(vis,0,sizeof(vis));
		if(dfs(i)) ++ans;
	}
	printf("%d\n",n-ans);
	return 0;
}

T3.粉刷匠

最朴素的想法是 \(f_{i,r1,r2,...,rk}\) 表示粉刷到第 \(i\) 根石柱,\(k\) 种颜色分别剩下 \(r_i\) 桶的情况。时间和空间上都不能接受。

上面的做法可以利用 \(c\) 的取值范围来优化,把 k 种颜色剩下的桶数按剩下的数量分组,可以通过本题,代码待写。

换种思路,不考虑逐个计算每一根柱子,考虑把上好色的柱子放进处理好的柱子里。

使用另一种状态的定义方式 \(f_{i,j}\) 表示当前处理完了前i种颜色,用j对柱子颜色相同。

每次把第 \(i\) 种颜色插进前面的柱子里,只有两种情况:插到 \(2\) 根同色的柱子间,插到 \(2\) 根异色的柱子间。

所以第 \(i\) 种颜色插进去会减少 \(j\) 对同色柱子的颜色,同时产生一些同色柱子。

我们决定把 \(c\)\(i\) 色柱子拆成 \(x\) 块,把其中 \(y\) 块放进同色柱子间,其他放进异色柱子间。

不管怎么分,\(c\) 根柱子分成 \(x\) 块都必然有 \(c-x\) 对同色,\(j\) 增加了 \(c-x\)。方案数 \(C^{x-1}_{c-1}\)

因为 \(y\) 块放进了已经存在的 \(j\) 对同色柱子间,\(j\) 减少 \(y\)。方案数 \(C^{y-1}_{j-1}\)

剩下的 \(x-y\) 块柱子就放进 \(n-j+1\) 对异色柱子间。方案数 \(C^{n-j+1}_{x-y}\)

代码:

#include<stdio.h>
#include<string.h>
#include<algorithm>
using namespace std;

template<class T>void read(T &x){
	x=0; bool f=0; char c=getchar();
	while(c<'0'||'9'<c){f|=(c=='-'); c=getchar();}
	while('0'<=c&&c<='9'){x=(x<<1)+(x<<3)+(c^48); c=getchar();}
	x=f?-x:x;
}
typedef long long ll;
const int N=20;
const int M=1000000007;
int k,n,a[N];
ll f[N][N*N],c[N*N][N*N];
void add(ll &x,ll y){x+=y; if(x>=M)x%=M;}
void pre(){
	c[0][0]=1;
	for(int i=1;i<=100;i++){
		c[i][0]=c[i][i]=1;
		for(int j=1;j<=i;j++) c[i][j]=c[i-1][j]+c[i-1][j-1];
	}
}

void solve(){
	f[0][0]=1;
	for(int i=1;i<=k;i++){
		for(int j=0;j<=n;j++)
			for(int x=0;x<=a[i];x++){
				int in=min(x,j);
				for(int y=0;y<=in;y++)
					add(f[i][j+a[i]-x-y],f[i-1][j]*c[j][y]%M*c[a[i]-1][x-1]%M*c[n-j+1][x-y]);
				}
		n+=a[i];
	}
	printf("%lld\n",f[k][0]);
}

int main() {
//	freopen("c.in","r",stdin);
	pre();
	int T; read(T);
	while(T--) {
		read(k); n=0;
		memset(a,0,sizeof(a));
		memset(f,0,sizeof(f));
		for(int i=1;i<=k;i++) read(a[i]);
		solve();
	}
	return 0;
}