想想接触ACM已经一年了,参加ACM也半年了。大四只有少量的课程,一般都准备考研、找工作、实习了。想想我的大学生活俨然已经走过了一半,一年半以来,不说自己大学过得如何,至少做的还不算太差,时间过的真是快啊!想想就算在学校整天玩耍、也没有多少青春可以挥霍了。因此2017年一定要比2016年付出更多的努力才行,任何东西只要肯投入,没有学不会或者做不到的,除非你是无法企及的事。
2017年,相信我会做的更好!
之前队内打算利用空余的时间刷网上的poj分类,所以漫漫的求索路又开始了。
初期的题目做得总是很快的,一下就刷完了,在此做个总结….
(1)、枚举
1、 poj1753Flip Game
题意:一个4*4的方格,每个方格中有一粒棋子,这个棋子一面是白色,一面是黑色。游戏规则为每次任选16颗中的一颗,把选中的这颗以及它四周的棋子一并反过来,当所有的棋子都是同一个颜色朝上时,游戏就完成了。现在给定一个初始状态,要求输出能够完成游戏所需翻转的最小次数,如果初始状态已经达到要求输出0。如果不可能完成游戏,输出Impossible。
分析:方法实在太多,随便暴力都能过,利用位运算枚举16个格子的所有翻转情况,复杂度为O(16*2^16)。高效一点的办法就是BFS求取最小的次数,不过这里可以采用部分枚举的策略。我们的目标是使得所有格子都相同,只要枚举第一行的所有翻转情况,然后依次判断第1行至第3行的每个格子,如果要翻转的话只能通过下面那个格子来解决,最后判断第4行格子是否要翻转即可。当然可以翻转为白色的和黑色的,所以枚举两次就够了。题目还可以采用高斯消元的办法,然而这个不会,从书本上了解到的。。。
#include
#include
#include
using namespace std;
const int N = 6;
const int INF = 0x3f3f3f3f;
int dx[] = {1, 0, 0, 0}, dy[] = {0, 1, -1, 0};
int tmp[N][N], filed[N][N];
void change(int x, int y) {
for (int i = 0; i < 4; i++) {
int nx = x + dx[i], ny = y + dy[i];
tmp[nx][ny] = !tmp[nx][ny];
}
}
int cal(int f) {
int cnt = 0;
for (int i = 1; i < 4; i++) {
for (int j = 1; j <= 4; j++) {
if (f) {
if (tmp[i][j]) change(i+1, j), cnt++;
}
else {
if (!tmp[i][j]) change(i+1, j), cnt++;
}
}
}
for (int i = 1; i <= 4; i++) {
if (f) {
if (tmp[4][i]) return INF;
}
else {
if (!tmp[4][i]) return INF;
}
}
return cnt;
}
int main() {
for (int i = 1; i <= 4; i++) {
for (int j = 1; j <= 4; j++) {
char c = getchar();
filed[i][j] = c == 'b' ? 1 : 0;
}
getchar();
}
int ans = INF;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 16; j++) {
memcpy(tmp, filed, sizeof(filed));
int t = 0;
for (int k = 0; k < 4; k++)
if (j & (1 << k)) change(1, k+1), t++;
ans = min(ans, t + cal(i));
}
}
ans == INF ? puts("Impossible") : printf("%d\n", ans);
return 0;
}
2、poj2965The Pilots Brothers' refrigerator
题意:一个冰箱上有4*4共16个开关,改变任意一个开关的状态(即开变成关,关变成开)时,此开关的同一行、同一列所有的开关都会自动改变状态。要想打开冰箱,要所有开关全部打开才行。求使得冰箱打开至少要改变开关状态的次数,以及改变哪几个格子。
分析:不是和上题一样了,这次有变化了,没多想,直接暴力过的,利用位运算按次数从小到大枚举翻转的子集。当然还是可以BFS求最小次数。
还有很巧妙的方法,从网上了解到的,如果一个格子为+,那么我们对该格子所在行和列的所有7个格子全部操作一遍,开关本身状态改变了7次,开关同一行、同一列的开关状态改变了4次,其他开关状态改变了2次。(其实也相当于只改变了这一个格子的状态)最后统计所有格子的翻转次数、如果翻转次数为奇数,那么相当于操作一次,翻转偶数次相当于没翻转。这样肯定可以保证次数是最少的。
#include
#include
#include
#include
#include
using namespace std;
const int N = 4;
int handle[N][N], cntx[N], cnty[N], vis[N][N];
int dx[] = {1, -1, 0, 0}, dy[] = {0, 0, 1, -1};
typedef pair p;
vector vec;
bool check(int s) {
for (int i = 0; i < 4; i++) cntx[i] = cnty[i] = 0;
memcpy(vis, handle, sizeof(handle));
vec.clear();
for (int i = 0; i < 16; i++)
if (s & (1 << i)) vec.push_back(p(i/4, i%4));
for (int i = 0; i < vec.size(); i++) {
int x = vec[i].first, y = vec[i].second;
cntx[x]++; cnty[y]++;
vis[x][y] = !vis[x][y];
}
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
if ((vis[i][j] + cntx[i] + cnty[j]) & 1) return 0;
printf("%d\n", vec.size());
for (int i = 0; i < vec.size(); i++) printf("%d %d\n", vec[i].first+1, vec[i].second+1);
return 1;
}
int main() {
//freopen("in.txt", "r", stdin);
for (int i = 0; i < 4; i++) {
char s[10];
scanf("%s", s);
for (int j = 0; j < 4; j++) handle[i][j] = s[j] == '+' ? 1 : 0;
}
if (!check(0)) {
int flag = 1, up = 1 << 16;
for (int i = 1; i <= 16 && flag; i++) {
int t = (1 << i) - 1;
while (t < up && flag) {
if (check(t)) flag = 0;
int x = t & -t, y = t + x;
t = ((t & ~y) / x >> 1) | y;
}
}
}
return 0;
}
(2)、贪心
1、poj1328
题意:海岸线是一条无限延伸的直线。陆地在海岸线的一侧,而海洋在另一侧。每一个小的岛屿是海洋上的一个点。雷达坐落于海岸线上,只能覆盖d距离,所以如果小岛能够被覆盖到的话,它们之间的距离最多为d。题目要求计算出能够覆盖给出的所有岛屿的最少雷达数目。
分析:首先按坐标排序,然后我们按照从左至右依次覆盖的顺序来求解答案。如何求取最小数目呢?假设从某个小岛开始覆盖、要使得数目最小,当然是要使得一个雷达覆盖尽量多的点,覆盖一个小岛的话,雷达的横坐标只能在x + sqrt(d*d-y*y)的范围内,所以我们采取贪心的策略、尽量地往右移动雷达坐标以覆盖后面的点,所以假设当前的坐标为x + sqrt(d*d-y*y),那么不断地比较,如果x + sqrt(d*d-y*y) > xi - sqrt(d*d-yi*yi)的话证明可以覆盖到它,但是需要注意的一点是当前的坐标要选取到所有岛屿的x + sqrt(d*d-y*y)最小值,这样才不会保证往右移动导致之前的点不在当前的范围内了。采取这样的贪心策略可以保证每次覆盖地点最大化。
#include
#include
#include
#include
using namespace std;
const int N = 1010;
struct po{
double x, y;
bool operator < (const po& a)const {
return x == a.x ? y < a.y : x < a.x;
}
}p[N];
int main() {
int ca = 0, n;
double d;
while (scanf("%d %lf", &n, &d), n) {
for (int i = 0; i < n; i++) scanf("%lf %lf", &p[i].x, &p[i].y);
sort(p, p+n);
int ans = 1;
double s = d * d;
if (p[0].y > d) ans = -1;
else {
double t = sqrt(s - p[0].y * p[0].y) + p[0].x;
for (int i = 1; i < n; i++) {
if (p[i].y > d) { ans = -1; break; }
double tmp = sqrt(s - p[i].y * p[i].y), sum = p[i].x + tmp;
if (p[i].x - tmp > t) ans++, t = sum;
else t = min(sum, t);
}
}
printf("Case %d: %d\n", ++ca, ans);
}
return 0;
}
2、 poj2109
题意:给定p和n,求出p^(1/n)。
分析:说实话、不知道这题为什么被归到贪心中来了。。。明显的高精度,直接拿kuangbin的模板二分求取p的n次方根就行了。但是这题简直神坑、double可以过,记得在书上看到double的范围最大到10^308,题目中p最大才10^101而且又是整数,所以不会有精度误差。直接利用pow(p, 1/n)求就好了。
不过做了这题才知bin神的高精度模板有多厉害!
#include
#include
#include
#include
#include
using namespace std;
const int MAXN = 9999;
const int MAXSIZE = 10;
const int DLEN = 4;
const int INF = 1000000001;
class BigNum
{
public:
BigNum(){ len = 1; memset(a, 0, sizeof(a)); } //构造函数
BigNum(const int); //将一个int类型的变量转化为大数
BigNum(const char*); //将一个字符串类型的变量转化为大数
BigNum(const BigNum &); //拷贝构造函数
BigNum &operator=(const BigNum &); //重载赋值运算符,大数之间进行赋值运算
friend istream& operator>>(istream&, BigNum&); //重载输入运算符
friend ostream& operator<<(ostream&, BigNum&); //重载输出运算符
BigNum operator+(const BigNum &) const; //重载加法运算符,两个大数之间的相加运算
BigNum operator-(const BigNum &) const; //重载减法运算符,两个大数之间的相减运算
BigNum operator*(const BigNum &) const; //重载乘法运算符,两个大数之间的相乘运算
BigNum operator/(const int &) const; //重载除法运算符,大数对一个整数进行相除运算
BigNum operator^(const int &) const; //大数的n次方运算
int operator%(const int &) const; //大数对一个int类型的变量进行取模运算
bool operator>(const BigNum & T)const; //大数和另一个大数的大小比较
bool operator>(const int & t)const; //大数和一个int类型的变量的大小比较
void print(); //输出大数
int a[500]; //可以控制大数的位数
int len; //大数长度
};
//将一个int类型的变量转化为大数
BigNum::BigNum(const int b) {
int c, d = b;
len = 0;
memset(a, 0, sizeof(a));
while (d > MAXN) {
c = d - (d / (MAXN + 1)) * (MAXN + 1);
d = d / (MAXN + 1);
a[len++] = c;
}
a[len++] = d;
}
//将一个字符串类型的变量转化为大数
BigNum::BigNum(const char* s) {
int t, k, index, l;
memset(a, 0, sizeof(a));
l = strlen(s);
len = l / DLEN;
if (l % DLEN) len++;
index = 0;
for (int i = l-1; i >= 0; i -= DLEN) {
t = 0;
k = i-DLEN+1;
if (k < 0) k = 0;
for (int j = k; j <= i; j++) t = t * 10 + s[j] - '0';
a[index++] = t;
}
}
//拷贝构造函数
BigNum::BigNum(const BigNum & T) : len(T.len) {
memset(a, 0, sizeof(a));
for (int i = 0; i < len; i++) a[i] = T.a[i];
}
//重载赋值运算符,大数之间进行赋值运算
BigNum & BigNum::operator = (const BigNum & n) {
len = n.len;
memset(a, 0, sizeof(a));
for (int i = 0; i < len; i++) a[i] = n.a[i];
return *this;
}
//重载输入运算符
istream& operator >> (istream & in, BigNum & b) {
char ch[MAXSIZE * 4];
in >> ch;
int l = strlen(ch);
int cnt = 0, sum = 0;
for (int i = l-1; i >= 0;) {
sum = 0;
int t = 1;
for (int j = 0; j < 4 && i >= 0; j++, i--, t *= 10) sum += (ch[i] - '0') * t;
b.a[cnt++] = sum;
}
b.len = cnt++;
return in;
}
//重载输出运算符
ostream& operator << (ostream& out, BigNum& b) {
cout << b.a[b.len - 1];
for (int i = b.len - 2 ; i >= 0 ; i--) {
cout.width(DLEN);
cout.fill('0');
cout << b.a[i];
}
return out;
}
//两个大数之间的相加运算
BigNum BigNum::operator + (const BigNum & T) const {
BigNum t(*this);
int big= T.len > len ? T.len : len; //位数
for (int i = 0 ; i < big ; i++) {
t.a[i] += T.a[i];
if (t.a[i] > MAXN){
t.a[i + 1]++;
t.a[i] -= MAXN + 1;
}
}
t.a[big] ? t.len = big + 1 : t.len = big;
return t;
}
//两个大数之间的相减运算
BigNum BigNum::operator - (const BigNum & T) const {
int big;
bool flag;
BigNum t1, t2;
if (*this > T) {
t1 = *this;
t2 = T;
flag = 0;
}
else {
t1 = T;
t2 = *this;
flag = 1;
}
big = t1.len;
for (int i = 0 ; i < big ; i++) {
if(t1.a[i] < t2.a[i]) {
int j = i + 1;
while (t1.a[j] == 0) j++;
t1.a[j--]--;
while(j > i) t1.a[j--] += MAXN;
t1.a[i] += MAXN + 1 - t2.a[i];
}
else t1.a[i] -= t2.a[i];
}
t1.len = big;
while (t1.a[len - 1] == 0 && t1.len > 1) t1.len--, big--;
if (flag) t1.a[big-1] = 0 - t1.a[big-1];
return t1;
}
//两个大数之间的相乘运算
BigNum BigNum::operator * (const BigNum & T) const {
BigNum ret;
int up, i, j;
int temp, temp1;
for (i = 0 ; i < len ; i++) {
up = 0;
for (j = 0 ; j < T.len ; j++) {
temp = a[i] * T.a[j] + ret.a[i + j] + up;
if (temp > MAXN) {
temp1 = temp - temp / (MAXN + 1) * (MAXN + 1);
up = temp / (MAXN + 1);
ret.a[i + j] = temp1;
}
else {
up = 0;
ret.a[i + j] = temp;
}
}
if(up != 0) ret.a[i + j] = up;
}
ret.len = i + j;
while(ret.a[ret.len - 1] == 0 && ret.len > 1) ret.len--;
return ret;
}
//大数对一个整数进行相除运算
BigNum BigNum::operator / (const int & b) const {
BigNum ret;
int down = 0;
for(int i = len - 1; i >= 0 ; i--) {
ret.a[i] = (a[i] + down * (MAXN + 1)) / b;
down = a[i] + down * (MAXN + 1) - ret.a[i] * b;
}
ret.len = len;
while (ret.a[ret.len - 1] == 0 && ret.len > 1) ret.len--;
return ret;
}
//大数对一个int类型的变量进行取模运算
int BigNum::operator % (const int & b) const {
int d = 0;
for (int i = len-1; i >= 0; i--) d = ((d * (MAXN+1)) % b + a[i]) % b;
return d;
}
//大数的n次方运算
BigNum BigNum::operator ^ (const int & n) const {
BigNum t, ret(1);
if (n < 0) exit(-1);
if (n == 0) return 1;
if (n == 1) return *this;
int m = n, i;
while (m > 1) {
t = *this;
for (i = 1; i << 1 <= m; i <<= 1) t = t * t;
m -= i;
ret = ret * t;
if (m == 1) ret = ret * (*this);
}
return ret;
}
//大数和另一个大数的大小比较
bool BigNum::operator > (const BigNum & T) const {
int ln;
if (len > T.len) return true;
else if (len == T.len) {
ln = len - 1;
while (a[ln] == T.a[ln] && ln >= 0) ln--;
if (ln >= 0 && a[ln] > T.a[ln]) return true;
else return false;
}
return false;
}
//大数和一个int类型的变量的大小比较
bool BigNum::operator > (const int & t) const {
BigNum b(t);
return *this > b;
}
//输出大数
void BigNum::print() {
cout << a[len - 1];
for(int i = len - 2 ; i >= 0 ; i--) {
cout.width(DLEN);
cout.fill('0');
cout << a[i];
}
cout << endl;
}
int n;
BigNum p;
int cal(int s) {
BigNum t(s), tmp = t ^ n;
return tmp > p ? 0 : 1;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
while (cin >> n >> p) {
if (p.len == 1 && p.a[0] == 1) { puts("1"); continue; }
int l = 1, r = INF;
while (r - l > 1) {
int m = (r + l) / 2;
if (cal(m)) l = m;
else r = m;
}
printf("%d\n", l);
}
return 0;
}
3、 poj2586
题意:一个公司在12个月中,或固定盈余s,或固定亏损d.但记不得哪些月盈余,哪些月亏损,只能记得连续5个月的代数和总是亏损(<0为亏损),而一年中只有8个连续的5个月,分别为1~5,2~6,…,8~12
问全年是否可能盈利?若可能,输出可能最大盈利金额,否则输出“Deficit".。
分析:一看12个月,每个月或盈余s,或亏损d,直接暴力了。。。复杂度才O(2^12*12)。当然是贪心的题,当然要知道采取什么样的贪心策略了。首先每个月或亏或损、要使得盈余最大化,当然亏损的月数尽量少就行了,那么怎么处理呢。。其实和第一题有点神似、使得一个亏损月覆盖尽量多的大小为5的连续区间就行了。这样我们只需要将亏损月以5为单位尽量安排在右端就行了。这样只需要安排两次就够了。一次安排几个月亏损根据s和d来确定,原则是保证这个月是亏损的。
#include
#include
#include
#include
using namespace std;
const int N = 15;
int sum[N];
int main() {
int s, d;
while (~scanf("%d %d", &s, &d)) {
int up = 1 << 12, ans = -1;
for (int i = 0; i < up; i++) {
for (int j = 1; j <= 12; j++) sum[j] = sum[j-1] + (i & (1 << (j-1)) ? s : -d);
int flag = 1;
for (int j = 5; j <= 12 && flag; j++)
if (sum[j] - sum[j-5] >= 0) flag = 0;
if (flag) ans = max(ans, sum[12]);
}
ans == -1 ? puts("Deficit") : printf("%d\n", ans);
}
return 0;
}
贪心算法的如下:
#include
using namespace std;
int main() {
int s, d;
while (~scanf("%d %d", &s, &d)) {
int cnt = 0;
while (cnt * s - (5 - cnt) * d < 0) cnt++;
cnt--;
int ans = cnt * 2 * s - (5 - cnt) * 2 * d;
if (cnt >= 2) ans += 2 * s;
else ans += cnt * s - (2 - cnt) * d;
ans > 0 ? printf("%d\n", ans) : puts("Deficit");
}
return 0;
}
(3)、递归和分治法
这里分类上面没给题,自己找了两个题做做。
1、 poj3768
题意:题目意思就不多说了,根据给定的小图递归地构造大图。现在给定这个构造地次数,输出大图。
分析:递归求解,把图形打印到二维数组中,一行一行输出,这样效率最高。主要是要搞清楚递归时的坐标变化,以及递归求解开始的坐标。卡了我好久,坐标一直没搞对,其实仔细想想就清楚了。小图一次构造大图时,假设此时小图宽x,那么大图中原来坐标差1的现在就差x了。。。
#include
#include
#include
using namespace std;
struct dir{ int x, y; };
const int N = 3005;
char ans[N][N], tem[7][7];
vector d;
vector w;
char c;
void dfs(int x, int y, int n) {
if (n == 1) { ans[x][y] = c; return ; }
dfs(x, y, n-1);
for (int i = 0; i < d.size(); i++) dfs(x + d[i].x * w[n-1], y + d[i].y * w[n-1], n-1);
}
int main() {
int n;
while (scanf("%d", &n), n) {
getchar();
for (int i = 1; i <= n; i++) gets(tem[i]+1);
d.clear();
int x, y, flag = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (tem[i][j] != ' ') {
c = tem[i][j];
if (flag) x = i, y = j, flag = 0;
else d.push_back((dir){i-x, j-y});
}
}
}
int q;
scanf("%d", &q);
w.clear();
w.push_back(0); w.push_back(1);
int t = 1;
for (int i = 1; i <= q; i++) t *= n, w.push_back(t);
int a = w[q+1];
for (int i = 1; i <= a; i++) memset(ans[i]+1, ' ', a);
int sx = x, sy = y;
for (int i = 2; i < w.size()-1; i++) sx += (x-1) * w[i];
for (int i = 2; i < w.size()-1; i++) sy += (y-1) * w[i];
dfs(sx, sy, q+1);
for (int i = 1; i <= a; i++) ans[i][a+1] = 0, puts(ans[i]+1);
}
return 0;
}
看到分治法无疑想到了最近点对和逆序数的求解。这里找了最近点对。
2、 uva10245
题意:题目想必不用多说了,找出n个点中距离最小的两个点。
分析:分治法的套路都一样,首先将问题一分为二递归求解,之后合并两个子问题。首先将坐标排序,这个问题中主要是合并。左右各n/2个点,难道需要n*n/4次枚举求出点对在两边时的最小值吗?刚开始一直在纠结怎么合并,难道还有什么没学过的高效的合并知识?其实没有。任何高效的算法都是不断地进行算法优化得来的,即使是新的方法也是这样。仔细一想我们可以从减少枚举量出发,既然我们已经求出了使得两边的点对最小值d,那么我们的枚举量显然大大减少,对于左端的每个点,我们只需要考虑离他距离不超过d的点就行了。也许你会想,这样的优化是不是没什么用,但是效率很高,因为要计算的点可以变得很少了,网上有证明,合并的效率可以达到O(n),不过那样的话需要对y坐标排序。即使不处理y坐标,效率也很高。
#include
using namespace std;
typedef pair p;
const int N = 10010;
const double INF = 10010;
const double EPS = 1e-5;
p a[N];
double solve(p* a, int n) {
if (n <= 1) return INF;
int m = n/2;
double d = min(solve(a, m), solve(a+m, n-m));
for (int i = 0; i < m; i++) {
for (int j = m; j < n; j++) {
double dx = a[j].first - a[i].first;
if (dx - d > EPS) break;
double dy = a[j].second - a[i].second;
if (abs(dy) + dx - d > EPS) continue;
d = min(d, sqrt(dx * dx + dy * dy));
}
}
return d;
}
int main() {
int n;
while (scanf("%d", &n), n) {
for (int i = 0; i < n; i++) scanf("%lf %lf", &a[i].first, &a[i].second);
sort(a, a+n);
double ans = solve(a, n);
ans - 10000 > EPS ? puts("INFINITY") : printf("%.4f\n", ans);
}
return 0;
}
(4)、递推
直接在紫书上面找了两个简单题….
1、 uva580
题意:一个序列中每个位置只包含L和U,如果3个U连续出现就非法,求给定长度的非法串的数量。
分析:这题可以从正反两面考虑:
① 正面考虑:
设长度为i的序列非法的数目为dp[i],首先,只要连续出现3个U就非法,我们可以从此处下手。从最特殊的出发,假设一个序列中最左端的三个u的位置为i,i+1,i+2。既然我们已经保证了连续3个U的位置是最左端的,那么第i-1个位置上面不能出现U,只能为L了。这样i-1左边的序列必然是合法的,数目为2^(i-2) – dp[i-1],i+2右边的序列可以随便取,数目为2^(n-i-2)。然后这是乘法原理,数目就是两个相乘。然后我们只需要对i进行分类,把全部相加就得到了不合法的数目。
②、反面考虑
长度为i的序列个数为2^i,知道了合法的数目就可以了,直接相减就可以了。
设长度为i的序列合法的数目为dp[i],有以下三种情况:
1、长度为i-1的合法序列加上后缀L。2、长度为i-2的合法序列加上LU,长度为i-3的合法序列加上LUU。
所以dp[i] = dp[i-1] + dp[i-2] + dp[i-3]。
#include
using namespace std;
const int N = 30;
typedef long long LL;
LL dp[N+5];
void predeal() {
for (int i = 3; i <= N; i++) {
dp[i] += 1LL << (i-3);
for (int j = 2; j <= i-2; j++) dp[i] += ((1LL << (j-2)) - dp[j-2]) * (1LL << (i-j-2));
}
}
int main() {
predeal();
int n;
while (scanf("%d", &n) && n) printf("%lld\n", dp[n]);
return 0;
}
总结:从这题可以看出来在动态规划的递推过程中,如果设计好了状态,那么在递推时要使得该状态成立肯定会有一些限制条件的,我们需要细心发掘,第二就是问题的考虑可以从正反面下手,正难则反,正易则正,这大概就是数学思维的美吧。
2、 uva12034
题意:求n个人赛马时最终名次的种类数%10056,比如说,两个人赛马,有三种情况:A第一,B第二;A第二,B第一;AB第一。
分析:很明显的递推,设n个人赛马时的可能性有dp[n]种,那么第一名有i个人,这时候的方案数就是剩下的n-i个人的方案数即dp[n-i]。则答案为C(n,i)*dp[n-1],全部相加就好了。
一个题目状态的定义可能有多种,这题还可以按如下的方式:
n匹马会排成m排,其实就是求这个可能的排列的数目,定义dp[i][j]表示i匹马排成j排的数目,那么第i匹马可以有j+1种选择,即当前j排中任意一排,或者i单独为第一排。
则dp[i][j] = j * dp[i-1][j] + dp[i-1][j-1]。
总结:利用dp解题时,最重要的一步就是找到决策的可能性,即上一阶段向这一阶段的过渡。
#include
using namespace std;
const int N = 1010;
const int mod = 10056;
int c[N][N], dp[N];
void predeal() {
dp[0] = dp[1] = 1;
for (int i = 0; i <= 1000; i++) c[i][0] = c[i][i] = 1;
for (int i = 2; i <= 1000; i++)
for (int j = 1; j < i; j++) c[i][j] = (c[i-1][j] + c[i-1][j-1]) % mod;
for (int i = 2; i <= 1000; i++) {
for (int j = 1; j <= i; j++) dp[i] += c[i][j] * dp[i-j], dp[i] %= mod;
}
}
int main() {
predeal();
int t, ca = 0;
scanf("%d", &t);
while (t--) {
int n;
scanf("%d", &n);
printf("Case %d: %d\n", ++ca, dp[n]);
}
return 0;
}
(5)构造法
1、poj3295
题意:输入由p、q、r、s、t、K、A、N、C、E共10个字母组成的逻辑表达式,其中p、q、r、s、t的值为1(true)或0(false),即逻辑变量;K、A、N、C、E为逻辑运算符,
K --> and: x && y
A --> or: x || y
N --> not : !x
C --> implies : (!x)||y
E --> equals : x==y
求这个逻辑表达式是否为永真式。
分析:变量最多才5个,暴力枚举2^5中取值,把输入的表达式计算出来即可。
其实我也不知道这题为什么归类到构造法中了,应该是我对构造法的真正内涵理解不深刻吧…..
#include
#include
#include
#include
(6)模拟法
模拟题虽然看似简单,但有时候动则几百行,那种题完全是锻炼代码能力和debug能力的题….简单的模拟题当然就很水了。。。
这些模拟题都很简单,就不多说了,随便怎么来,能A就行。。。
1、 poj1068
#include
#include
#include
#include
using namespace std;
const int N = 1010;
int a[N], s[N], vis[N];
vector vec, ans;
int main() {
int t;
scanf("%d", &t);
while (t--) {
int n;
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
vec.clear(); ans.clear();
memset(vis, 0, sizeof(vis));
for (int i = 1; i <= n; i++) {
for (int j = 0; j < a[i] - a[i-1]; j++) vec.push_back(0);
vec.push_back(1);
}
for (int i = 0; i < vec.size(); i++) {
if (vec[i]) {
int cnt = 0;
for (int j = i-1; j >= 0; j--) {
if (!vec[j]) {
cnt++;
if (!vis[j]) { vis[j] = 1; break; }
}
}
ans.push_back(cnt);
}
}
for (int i = 0; i < ans.size(); i++) printf("%d%c", ans[i], i == ans.size()-1 ? '\n' : ' ');
}
return 0;
}
2、 poj2632
#include
#include
using namespace std;
pair, char> robot[110];
char dir[4] = {'E', 'N', 'W', 'S'};
struct instructions {
int order;
char action;
int times;
}ins[110];
int main() {
int t;
scanf("%d", &t);
while (t--) {
int len, wid, n, m;
scanf("%d %d %d %d", &len, &wid, &n, &m);
for (int i = 1; i <= n; i++) scanf("%d %d %c", &robot[i].first.first, &robot[i].first.second, &robot[i].second);
for (int i = 0; i < m; i++) scanf("%d %c %d", &ins[i].order, &ins[i].action, &ins[i].times);
int flag = 1;
for (int i = 0; i < m && flag; i++) {
int a = ins[i].order, b;
if (ins[i].action == 'L') {
for (int x = 0; x < 4; x++)
if (robot[a].second == dir[x]) b = x;
for (int j = 1; j <= ins[i].times % 4; j++)
if (++b > 3) b = 0;
robot[a].second = dir[b];
continue;
}
if (ins[i].action == 'R') {
for (int x = 0; x < 4; x++)
if (robot[a].second == dir[x]) b = x;
for (int j = 1; j <= ins[i].times%4; j++)
if (--b < 0) b = 3;
robot[a].second = dir[b];
continue;
}
if (ins[i].action == 'F') {
for (int j = 1; j <= ins[i].times && flag; j++) {
if (robot[a].second == 'W')
if (--robot[a].first.first < 1) flag = 0;
if (robot[a].second == 'E')
if (++robot[a].first.first > len) flag = 0;
if (robot[a].second == 'S')
if (--robot[a].first.second < 1) flag = 0;
if (robot[a].second == 'N')
if (++robot[a].first.second > wid) flag = 0;
if (! flag) printf("Robot %d crashes into the wall\n", a);
for (int x = 1; x <= n && flag; x++)
if (a != x && robot[a].first == robot[x].first) {
printf("Robot %d crashes into robot %d\n", a, x);
flag = 0;
}
}
}
}
if (flag) puts("OK");
}
return 0;
}
3、1573
#include
#include
using namespace std;
const int N = 110;
int vis[N][N];
char mat[N][N];
int n, m;
void init() {
for (int i = 0; i < n; i++) memset(vis[i], 0, 4*m);
}
void dfs(int x, int y, int cnt) {
vis[x][y] = cnt;
int nx = x, ny = y;
if (mat[x][y] == 'E') ny++;
else if (mat[x][y] == 'W') ny--;
else if (mat[x][y] == 'N') nx--;
else nx++;
if (nx < 0 || nx >= n || ny < 0 || ny >= m) printf("%d step(s) to exit\n", cnt);
else if (vis[nx][ny]) printf("%d step(s) before a loop of %d step(s)\n", vis[nx][ny]-1, cnt - vis[nx][ny] + 1);
else dfs(nx, ny, cnt+1);
}
int main() {
int s;
while (scanf("%d %d %d", &n, &m, &s), n) {
init();
for (int i = 0; i < n; i++) scanf("%s", mat[i]);
dfs(0, s-1, 1);
}
return 0;
}
4、2993
#include
#include
#include
#include
5、2996
这题多说一句,原因是因为做这题时被卡了半个小时。。最后居然是排序函数写错了。。感觉我是个脑残。。都没往那仔细检查。。然后在那各种修改还是样例过不了,其实就只有排序部分写错了。。代码的bug可能出在任何地方,哪怕是一个变量的定义。。
#include
#include
#include
#include
我感觉以上的基本算法更多的是一些解题时的基本技巧,比如说递推、递归、构造、模拟,当然也有些算法或者思想,比如说贪心、分治、枚举。每个部分都有其精华所在,这些东西在竞赛中是最常用的。