SCOI2009题解

15 篇文章 0 订阅

总的来说,SCOI2009的数学比较浓,而代码相对来说都比较短,注重思维过程。

第一试:

1生日快乐

看到题目感觉无法下手,但是由于题目的限制面积必须相同且只能平行边界切注意到n的范围非常小,所以直接枚举即可

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const double inf = 1e100;
const double eps = 1e-12;
int x,y,n;
void init()
{
	freopen("cake.in","r",stdin);
	freopen("cake.out","w",stdout);
}

void readdata()
{
	scanf("%d%d%d",&x,&y,&n);
}

double f(double x,double y,int k)
{
	if(x < y)swap(x,y);
	if(k == 1)return x / y;
	double ans = inf;
	for(int i = 1;i <= (k >> 1);i++)
	{
		ans = min(ans,max(f(i / (double)k * x,y,i),f((k - i) / (double)k * x,y,k - i)));
		ans = min(ans,max(f(x,i / (double)k * y,i),f(x,(k - i) / (double)k * y,k - i)));
		if(ans - 1 < eps)return ans;
	}
	return ans;
}

void solve()
{
	printf("%.6lf",f(x,y,n));
}

int main()
{
	init();
	readdata();
	solve();
	return 0;
}


2、windy数

比较简单的一道数位DP,需要注意好前导0的处理。

#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 20;
int dp[maxn][maxn];
int digit[maxn];
int A,B;
void init()
{
	freopen("windy.in","r",stdin);
	freopen("windy.out","w",stdout);
}

int abs(int x)
{
	return x >= 0 ? x : -x;
}

void readdata()
{
	scanf("%d%d",&A,&B);
}

int dfs(int pos,int last,bool zero,bool inf)
{
	if(pos == -1)return 1;
	if(!inf && dp[pos][last] != -1 && !zero)return dp[pos][last];
	int end = inf ? digit[pos] : 9;
	int ans = 0;
	for(int i = 0;i <= end;i++)
	{
		if(zero)ans += dfs(pos - 1,i,zero && i == 0,inf && i == end);
		else if(abs(i - last) >= 2)ans += dfs(pos - 1,i,false,inf && i == end);
	}
	if(!inf && !zero)dp[pos][last] = ans;
	return ans;
		
}

int calc(int x)
{
	if(x == 0)return 1;
	int pos = 0;
	while(x)
	{
		digit[pos++] = x % 10;
		x /= 10;
	}
	return dfs(pos - 1,0,1,1);
}

void solve()
{
	memset(dp,-1,sizeof(dp));
	printf("%d\n",calc(B) - calc(A - 1));
}

int main()
{
	init();
	readdata();
	solve();
	return 0;
}

3、游戏

原问题易转化成:

求几个长度和为n的置换的所有长度最小公倍数有多少种可能

由于1并不影响最小公倍数的可能数 -> 几个长度和小于等于n的置换的所有长度最小公倍数有多少种可能

由唯一分解定理可知,每个大于1的自然数均可写为质数的积 -> 几个小于等于n的只含一个质因数的正整数的最小公倍数有多少种可能

这样就可以使用DP来求解本题:

我们定义f[i][j]表示前i个质数,和为j的可能数,则:f[i][j] = ∑f[i-1][j-k]

#include<cstdio>
#include<cstring>
using namespace std;
const int maxn = 300;
const int maxm = 1000 + 10;
const int maxprime = 100000;
int p[maxn];
long long f[maxn][maxm];
bool flag[maxprime];
int n,cnt;
void init()
{
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
}

void readdata()
{
	scanf("%d",&n);
}

void get_prime()
{
	memset(p,0,sizeof(p));
	memset(flag,false,sizeof(flag));
	cnt = 0;
	for(int i = 2;i <= n;i++)
	{
		if(!flag[i])p[++cnt] = i;
		for(int j = 1;j <= cnt && p[j] * i <= n;j++)
		{
			flag[i*p[j]] = true;
			if(i % p[j] == 0)break;
		}
	}
}

void solve()
{
	memset(f,0,sizeof(f));
	get_prime();
	for(int i = 0;i <= n;i++)f[0][i] = 1;
	for(int i = 1;i <= cnt;i++)
	{
		for(int j = 0;j <= n;j++)
		{
			f[i][j] = f[i-1][j];
			for(int k = p[i];k <= j;k *= p[i])
			{
				f[i][j] += f[i-1][j-k];
			}
		}
	}
	printf("%lld",f[cnt][n]);
}

int main()
{
	init();
	readdata();
	solve();
	return 0;
}

第二试:

1、最大距离

开始没理解“格子中心的欧几里徳距离”,后来发现就是一个坐标的距离。

同样数据范围非常小,我们对相邻的格子之间连边,边权为这两个格子的障碍格子的个数为0,1,2

这样我们以每个点为起点做spfa,若dis[i][j]<= t * 2,说明可达,更新答案就可以了。

#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int dx[4] = {-1,1,0,0};
const int dy[4] = {0,0,-1,1};
const int maxn = 50;
const int maxque = 100000;
struct pnode
{
	int x,y;
	int len;
	pnode *next;
}*first[maxn][maxn];
int dis[maxn][maxn],w[maxn][maxn];
int	que[maxque][2];
bool flag[maxn][maxn];
int n,m,t;
void init()
{
	freopen("maxlength.in","r",stdin);
	freopen("maxlength.out","w",stdout);
}

void readdata()
{
	scanf("%d%d%d",&n,&m,&t);
	for(int i = 1;i <= n;i++)
	{
		getchar();
		for(int j = 1;j <= m;j++)
		{
			w[i][j] = getchar() - '0';
		}	
	}
}

void insert(int a,int b,int x,int y)
{
	pnode *p = new pnode;
	p -> x = x;p -> y = y;
	p -> len = w[a][b] + w[x][y];
	p -> next = first[a][b];
	first[a][b] = p;
}

void build_map()
{
	for(int i = 1;i <= n;i++)
	{
		for(int j = 1;j <= m;j++)
		{
			for(int k = 0;k < 4;k++)
			{
				int nx = i + dx[k],ny = j + dy[k];
				if(nx < 1 || nx > n || ny < 1 || ny > m)continue;
				insert(i,j,nx,ny);
			}
		}
	}
}

void spfa(int a,int b)
{
	memset(dis,0x3f,sizeof(dis));
	memset(flag,false,sizeof(flag));
	dis[a][b] = 0;flag[a][b] = true;
	int l = 0,r = 0;
	que[r][0] = a;que[r++][1] = b;
	while(l < r)
	{
		int x = que[l][0],y = que[l++][1];
		flag[x][y] = false;
		for(pnode *p = first[x][y];p != NULL;p = p -> next)
		{
			if(dis[x][y] + p -> len < dis[p->x][p->y])
			{
				dis[p->x][p->y] = dis[x][y] + p -> len;
				if(!flag[p->x][p->y])
				{
					flag[p->x][p->y] = true;
					que[r][0] = p -> x;
					que[r++][1] = p -> y;
				}
			}
		}
	}
}

double dist(int a,int b,int x,int y)
{
	return sqrt((a - x) * (a - x) + (b - y) * (b - y));
}

void solve()
{
	build_map();
	double ans = 0.0;
	for(int i = 1;i <= n;i++)
	{
		for(int j = 1;j <= m;j++)
		{
			if(w[i][j])continue;
			spfa(i,j);
			for(int x = 1;x <= n;x++)
				for(int y = 1;y <= m;y++)
					if(dis[x][y] <= (t << 1))
						ans = max(ans,dist(i,j,x,y));
		}
	}
	printf("%.6lf\n",ans);
}

int main()
{
	init();
	readdata();
	solve();
	return 0;
}

2、粉刷匠

比较水的一道DP

sum[i][j][0]和sum[i][j][1]表示i行前j个格子有多少个0或1

g[i][j][k]表示i行前j个格子刷k次的最大正确涂色

f[i][j]表示前i行刷j次的最大正确涂色

则f[i][j] = max(f[i][j],f[i-1][k] + g[i][m][j-k])

g[i][j][k] = max(g[i][j][k],g[i][l][k-1] + max(sum[i][j][0] - sum[i][l][0],sum[i][j][1] - sum[i][l][1]))

即可。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 50 + 10;
const int maxt = 2500 + 10;
int thi[maxt],tmp[maxn],sum[maxn][maxn][2];
int g[maxn][maxn][maxt],f[maxn][maxt];
int n,m,t;
void init()
{
	freopen("paint.in","r",stdin);
	freopen("paint.out","w",stdout);
}

void readdata()
{
	memset(g,0,sizeof(g));
	scanf("%d%d%d",&n,&m,&t);
	for(int i = 1;i <= n;i++)
	{
		getchar();
		for(int j = 1;j <= m;j++)
		{
			tmp[j] = getchar() - '0';
			if(tmp[j] == 0)
			{
				sum[i][j][0] = sum[i][j-1][0] + 1;
				sum[i][j][1] = sum[i][j-1][1];
			}
			else
			{
				sum[i][j][1] = sum[i][j-1][1] + 1;
				sum[i][j][0] = sum[i][j-1][0];
			}
		}
	}
}

void solve()
{
	for(int i = 1;i <= n;i++)
	{
		for(int k = 1;k <= m;k++)
			for(int j = 1;j <= m;j++)
				for(int l = 0;l < j;l++)
					g[i][j][k] = max(g[i][j][k],g[i][l][k-1] + max(sum[i][j][0] - sum[i][l][0],sum[i][j][1] - sum[i][l][1]));
	}
	memset(f,0,sizeof(f));
	for(int i = 1;i <= n;i++)
		for(int j = 0;j <= t;j++)
			for(int k = 0;k <= j;k++)
				f[i][j] = max(f[i][j],f[i-1][k] + g[i][m][j-k]);
	printf("%d\n",f[n][t]);
}

int main()
{
	init();
	readdata();
	solve();
	return 0;
}

3、迷路

很经典的矩阵乘法,由于权值最大为9,所以将每个点拆为[9i,9i + 9]这9个点,然后在这九个点的相邻两个点都连一条边若i -> j有条权值为k的边,则连边9i + k -> 9j

然后再使用矩阵快速幂求出t时刻从1 -> n的方案数就可以了。

#include<cstdio>
#include<cstring>
using namespace std;
const int mo = 2009;
const int maxT = 9;
const int maxn = 100;
struct Matrix
{
	int v[maxn][maxn];
	int x,y;
	Matrix()
	{
		memset(v,0,sizeof(v));
		x = y = 0;
	}
}map;
int n,t;
int tmp[maxn][maxn];
void init()
{
	freopen("road.in","r",stdin);
	freopen("road.out","w",stdout);
}


Matrix mtMul(Matrix A,Matrix B)
{
	if(!A.x || !A.y)return B;
	Matrix C;
	C.x = A.x;C.y = B.y;
	for(int i = 1;i <= A.x;i++)
	{
		for(int j = 1;j <= B.y;j++)
		{
			for(int k = 1;k <= A.y;k++)
			{
				C.v[i][j] = (A.v[i][k] * B.v[k][j] + C.v[i][j]) % mo;
			}
		}
	}
	return C;
}

void readdata()
{
	scanf("%d%d",&n,&t);
	for(int i = 1;i <= n;i++)
	{
		getchar();
		for(int j = 1;j <= n;j++)
			tmp[i][j] = getchar() - '0';
	}
}

void build_map()
{
	for(int i = 1;i <= n;i++)
	{
		for(int k = 1;k < maxT;k++)
			map.v[(i - 1) * maxT + k][(i - 1) * maxT + k + 1] = 1;
		for(int j = 1;j <= n;j++)
		{
			if(tmp[i][j])map.v[(i - 1) * maxT + tmp[i][j]][(j - 1) * maxT + 1] = 1;
		}
	}
	n *= maxT;
	map.x = n;map.y = n;
}

Matrix mtPow(Matrix A,int k)
{
	if(k == 1)return A;
	Matrix tmp;
	while(k)
	{
		if(k & 1)tmp = mtMul(tmp,A);
	    k >>= 1;
		A = mtMul(A,A);	
	}
	return tmp;
}

void solve()
{
	build_map();
	Matrix ans = mtPow(map,t);
	printf("%d\n",ans.v[1][n - maxT + 1]);
}

int main()
{
	init();
	readdata();
	solve();
	return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值