总的来说,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;
}