纪中DAY11做题小结

T1:粉刷(paint)

Description
鸡腿想到了一个很高(sha)明(bi)的问题,墙可以看作一个N*M的矩阵,有一些格子是有污点的。现在鸡腿可以竖着刷一次,覆盖连续的最多C列,或者横着刷一次,覆盖连续的最多R行。现在鸡腿把墙上的情况告诉你,请你告诉鸡腿最少要刷多少次才能刷干净!

Input
第1行,输入俩正整数N,M。
第2到N+1行,每行一个长度为M的字符串,每个字符可能是’.’表示干净的,或者’X’表示这个格子有污点。
第N+2行,输入俩正整数表示R和C。

Output
输出一行一个整数,表示鸡腿最少要刷几次。

Sample Input
输入1:

1 9
XXXXXXXXX
2 3

输入2:

11 14
XXX..XXX..XXX.
.X..X....X...X
.X..X....X...X
.X..X....X...X
.X...XXX..XXX.
..............
...XX...XXX...
....X......X..
....X....XXX..
....X......X..
...XXX..XXX...
1 2

Sample Output
输出1:

1

输出2:

7

Data Constraint
对于50%的数据1≤N,M≤5;
对于100%的数据1≤N,M,R,C≤15。

简要思路:本题看上去像个贪心题,确实如此,然而,直接贪心可能会WRONG,因为我们要妥善处理行与列的关系。我们可以先用dfs来暴力枚举每行刷或者不刷的情况,同时可以加点剪枝优化一下(数据小,不用担心超时 ),再用贪心的方法处理列的情况(贪心法很显然,我不用讲了吧 )。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int N = 25;
int n , m , r , c , ans;
char ss[N];
int room[N][N] , h[N] , hh[N] , l[N];
inline void read( int & res ) {
	res = 0;
	int pd = 1;
	char a = getchar();
	while ( a < '0' || a > '9' ) {
		if ( a == '-' ) {
			pd = -pd;
		}
		a = getchar();
	}
	while ( a >= '0' && a <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
		a = getchar();
	}
	res *= pd;
	return;
}
void dfs( int preh , int sum ) {
	if ( preh > n ) {
		int tot = sum;
		memset( l , 0 , sizeof(l) );
		for ( int j = 1 ; j <= m ; ++j ) {
			for ( int i = 1 ; i <= n ; ++i ) {
				if ( room[i][j] && h[i] ) {
					l[j] = 1;
				}
			}
		}
		for ( int i = 1 ; i <= m ; ++i ) {
			if ( !l[i] ) {
				continue;
			}
			l[i] = 0;
			for ( int j = 1 ; j <= c ; ++j ) {
				l[min( i + j - 1 , m )] = 0;
			}
			tot++;
		}
		ans = min( ans , tot );
		return;
	}
	dfs( preh + 1 , sum );
	if ( h[preh] ) {//剪枝 
		for ( int i = 1 ; i <= r ; ++i ) {
			h[min( i + preh - 1 , n )] = 0;
		}
		dfs( preh + 1 , sum + 1 );
		for ( int i = 1 ; i <= r ; ++i ) {
			if ( hh[min( i + preh - 1 , n )] == 1 ) {
				h[min( i + preh - 1 , n )] = 1;
			}
		}
	}
}
int main () {
	read(n);
	read(m);
	for ( int i = 1 ; i <= n ; ++i ) {
		scanf("%s",ss);
		for ( int j = 1 ; j <= m ; ++j ) {
			switch( ss[j - 1] ) {
				case 'X' : {
					room[i][j] = 1;
					h[i] = 1;
					hh[i] = 1;
					break;
				}
				case '.' : {
					room[i][j] = 0;
					break;
				}
			}
		}
	}
	read(r);
	read(c);
	ans = 0x3f3f3f3f;
	dfs( 1 , 0 );
	printf("%d",ans);
	return 0;
}

T2:运算符(calc)

Description
鸡腿想到了一个很高(sha)明(bi)的运算符,那就是’!’,没错就是感叹号。他给了如下的定义:
1、n!k = n!(k-1) * (n-1)!k (n> 0 and k > 0)
2、n!k = 1 (n = 0)
3、n!k = n (k = 0)
现在鸡腿告诉你n和k你能告诉他n!k的不同约数个数有多少个吗?只要对1,000,000,009取模就可以了哦!

Input
一行,输入两个正整数n,k。

Output
一行,输出一个整数表示答案。

Sample Input
输入1:

3 1

输入2:

100 2

Sample Output
输出1:

4

输出2:

321266186

Data Constraint
对于30%的数据0 <n ≤ 10, 0 <k ≤ 10;
对于100%的数据0 <n ≤ 1000, 0 <k ≤ 100。

简要思路:初看这题总感觉它又是某道推柿子的数论难题,其实,它只是一道基础数论加上假的 DP的普通题,总体不算太难(然而我在考场上也只会打暴力 )。首先,由唯一分解定理,设 p i p_i pi为素数,若数 n n n可表示为 p 1 k 1 ∗ p 2 k 2 ∗ . . . ∗ p m k m p_1^{k_1} * p_2^{k_2} * ... * p_m^{k_m} p1k1p2k2...pmkm,则 n n n的因数个数为 ( k 1 + 1 ) ∗ ( k 2 + 1 ) ∗ . . . ∗ ( k m + 1 ) ( k_1 + 1 ) * ( k_2 + 1 ) * ... * ( k_m + 1 ) (k1+1)(k2+1)...(km+1)不管有没有思路,先欧拉筛一波素数再说
然后,分析题目要求,发现题目中的运算符 ! ! !满足无后效性的递归关系,对于题中的数据,我们只要记录它们每种素因子的个数即可,用 f [ i ] [ j ] [ l ] f[i][j][l] f[i][j][l]表示 i ! j i!j i!j的第 l l l个素数的个数。根据题目规定,未到边界时 i ! j = i ! ( j − 1 ) ∗ ( i − 1 ) ! j i!j = i!(j - 1) * (i - 1)!j i!j=i!(j1)(i1)!j,而在乘法运算时积的因子数为两个乘数的因子和,故 f [ i ] [ j ] [ l ] = f [ i − 1 ] [ j ] [ l ] + f [ i ] [ j − 1 ] [ l ] f[i][j][l] = f[i - 1][j][l] + f[i][j - 1][l] f[i][j][l]=f[i1][j][l]+f[i][j1][l]最后,处理边界值,所有 f [ 0 ] [ j ] [ l ] f[0][j][l] f[0][j][l]均为零,所有 f [ i ] [ 0 ] [ l ] f[i][0][l] f[i][0][l]值为数 i i i l l l因子的数目。
记得勤取模,数组不要开太大, f f f必须用 i n t int int存贮,否则会 M L E MLE MLE

#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
#define mod 1000000009
using namespace std;
int n , k , pcnt , sum , tem;
int vis[1001] , prime[169];
int f[1001][101][169];
ll ans;
inline void read( int & res ) {
	res = 0;
	int pd = 1;
	char a = getchar();
	while ( a < '0' || a > '9' ) {
		if ( a == '-' ) {
			pd = -pd;
		}
		a = getchar();
	}
	while ( a >= '0' && a <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
		a = getchar();
	}
	res *= pd;
	return;
}
void pre() {
	for ( int i = 2 ; i <= n ; ++i ) {
		if ( !vis[i] ) {
			prime[++pcnt] = i;
		}
		for ( int j = 1 ; j <= pcnt ; ++j ) {
			if ( i * prime[j] > n ) {
				break;
			}
			vis[i * prime[j]] = 1;
			if ( !( i % prime[j] ) ) {
				break;
			}
		}
	}
	return;
}
int main () {
	read(n);
	read(k);
	pcnt = 0;
	pre();
	ans = 1;
	for ( int i = 1 ; i <= n ; ++i ) {
		for ( int j = 1 ; j <= pcnt ; ++j ) {
			tem = i;
			sum = 0;
			while ( !( tem % prime[j] ) ) {
				tem /= prime[j];
				sum ++;
			}
			f[i][0][j] = sum;
		}
	}
	memset( f[0] , 0 , sizeof(f[0]) );
	for ( int l = 1 ; l <= pcnt ; ++l ) {
		for ( int i = 1 ; i <= n ; ++i ) {
			for ( int j = 1 ; j <= k ; ++j ) {
				f[i][j][l] = f[i][j - 1][l] + f[i - 1][j][l];
				f[i][j][l] %= mod;
			}
		}
	}
	for ( int i = 1 ; i <= pcnt ; ++i ) {
		ans *= ( f[n][k][i] + 1 );
		ans %= mod;
	}
	printf("%lld",ans);
	return 0;
}

T3:倾斜的线

Description
图1

Input
图2

Output
图3

Sample Input

6 15698 17433
112412868 636515040
122123982 526131695
58758943 343718480
447544052 640491230
162809501 315494932
870543506 895723090 

Sample Output

193409386/235911335

Data Constraint
图4

简要思路:这是一道数学题,合理地推导式子可以大大地简化问题的求解。
题目要求算出一种最接近的斜率,根据题意,可得: ∣ y i − y j x i − x j − P Q ∣ \vert{y_i-y_j\over x_i-x_j} - {P\over Q}\vert xixjyiyjQP,题目要求我们求上式的最小值,通过转(xia)化(gao),可得: ∣ y i ∗ Q − y j ∗ Q + x j ∗ P − x i ∗ P x i ∗ Q − x j ∗ Q ∣ \vert { y_i * Q-y_j*Q+x_j*P-x_i*P \over x_i * Q -x_j*Q }\vert xiQxjQyiQyjQ+xjPxiP,进一步转化为: ∣ ( y i ∗ Q − x i ∗ P ) − ( y j ∗ Q − x j ∗ P ) x i ∗ Q − x j ∗ Q ∣ \vert { (y_i * Q-x_i*P)-(y_j*Q-x_j*P) \over x_i * Q -x _j*Q }\vert xiQxjQ(yiQxiP)(yjQxjP),将 y i ∗ Q − x i ∗ P y_i * Q-x_i*P yiQxiP当做点 i i i的新纵坐标, x i ∗ Q x_i * Q xiQ当做点 i i i的新横坐标,那么这问题可转化为求最小斜率的问题。
不过,我们还有一个问题,如何高效求出一组点中最小的斜率呢?
其实只要将所有点按纵坐标从小到大进行排序,取所有相邻两点的最小斜率即可。
图5
如图,有三个点在 x A x_A xA x B x_B xB之间,不难发现,在两点纵坐标之间的点 C C C E E E D D D A A A B B B之间的连线中,总会有直线的斜率小于等于 k A B k_{AB} kAB,当然,也总会有直线的斜率大于等于 k A B k_{AB} kAB,所以,求相邻点的斜率最值即可得到整组点的斜率最值,求直线的最小斜率或最大斜率都可以用这招。
其它一些小细节就自己搞定吧,我也不赘述了。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 2e5 + 5;
int n , p , q;
struct node{
	double x;
	double y;
	int z;
}poi[N];
int xx[N] , yy[N];
bool cmp( node a , node b ) {
	return a.y < b.y;
}
inline int gcd( int a , int b ) {
	return b == 0 ? a : gcd( b , a % b );
}
inline void read( int & res ) {
	res = 0;
	int pd = 1;
	char a = getchar();
	while ( a < '0' || a > '9' ) {
		if ( a == '-' ) {
			pd = -pd;
		}
		a = getchar();
	}
	while ( a >= '0' && a <= '9' ) {
		res = ( res << 1 ) + ( res << 3 ) + ( a - '0' );
		a = getchar();
	}
	res *= pd;
	return;
}
int main () {
	//freopen( "slope.in" , "r" , stdin );
	//freopen( "slope.out" , "w" , stdout );
	read(n);
	read(p);
	read(q);
	for ( int i = 1 ; i <= n ; ++i ) {
		read(xx[i]);
		read(yy[i]);
		poi[i].x = double(xx[i]) * double(q);
		poi[i].y = double(yy[i]) * double(q) - double(xx[i]) * double(p);
		poi[i].z = i;
	}
	sort( poi + 1 , poi + 1 + n , cmp );
	double minn = 1000000000.0;
	int ans = 0;
	for ( int i = 1 ; i <= n - 1 ; ++i ) {
		if ( fabs(poi[i + 1].y - poi[i].y ) / fabs(poi[i + 1].x - poi[i].x ) < minn ) {
			minn = fabs(poi[i + 1].y - poi[i].y) / fabs(poi[i + 1].x - poi[i].x);
			ans = i;
		} else if ( fabs(poi[i + 1].y - poi[i].y ) / fabs(poi[i + 1].x - poi[i].x ) == minn ) {
			if ( fabs((double)yy[poi[i + 1].z] - (double)yy[poi[i].z]) / fabs((double)xx[poi[i + 1].z] - (double)xx[poi[i].z]) < fabs((double)yy[poi[ans + 1].z] - (double)yy[poi[ans].z]) / fabs((double)xx[poi[ans + 1].z] - (double)xx[poi[ans].z]) ) {
				ans = i;
			}
		}
	}
	int aa = (int)yy[poi[ans + 1].z] - (int)yy[poi[ans].z];
	int bb = (int)xx[poi[ans + 1].z] - (int)xx[poi[ans].z];
	int d = gcd( aa , bb );
	aa /= d;
	bb /= d;
	printf("%d/%d",aa,bb);
	return 0;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值