poj初期基本算法

想想接触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 
#include 
using namespace std;

const int N = 1010;
map mp;
set a, tmp;
typedef pair p;
char s[N];
void init() {
    a.clear(); mp.clear();
}
p cal(int i) {
    if (a.count(s[i])) return p(mp[s[i]], i+1);
    p t = cal(i+1);
    if (s[i] == 'N') return p(!t.first, t.second);
    p tmp = cal(t.second);
    if (s[i] == 'K') return p(t.first && tmp.first, tmp.second);
    if (s[i] == 'A') return p(t.first || tmp.first, tmp.second);
    if (s[i] == 'C') return p(!t.first || tmp.first, tmp.second);
    if (s[i] == 'E') return p(t.first == tmp.first, tmp.second);
}
int main() {
    tmp.insert('p'); tmp.insert('q');
    tmp.insert('r'); tmp.insert('s');
    tmp.insert('t');
    while (scanf("%s", s), s[0] != '0') {
        init();
        int len = strlen(s);
        for (int i = 0; i < len; i++)
            if (tmp.count(s[i])) a.insert(s[i]);
        int flag = 1;
        for (int i = 0; i < 1 << a.size() && flag; i++) {
            int cnt = 0;
            for (set::iterator j = a.begin(); j != a.end(); j++, cnt++) mp[*j] = (i & (1 << cnt)) > 0;
            if (!cal(0).first) flag = 0;
        }
        puts(flag ? "tautology" : "not");
    }
    return 0;
}


(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 
#include 
#include 
using namespace std;

const int N = 60;
string ans[N];
char s[N];
int main() {
    for (int i = 1; i <= 8; i++) {
        ans[i] += ' '; ans[i] += '|';
        for (int j = 0; j < 8; j++) ans[i] += i & 1 ? (j & 1 ? "...|" : ":::|") : (j & 1 ? ":::|" : "...|");
    }
    for (int i = 0; i < 2; i++) {
        gets(s);
        int k, len = strlen(s);
        for (int j = 0; j < len; j++)
            if (s[j] == ',' && islower(s[j+1])) { k = j+1; break; }
        for (int j = 7; j < k; j += 4) ans[s[j+2]-'0'][(s[j+1]-'a'+1)*4-1] = i & 1 ? tolower(s[j]) : s[j];
        for (int j = k; j < len; j += 3) ans[s[j+1]-'0'][(s[j]-'a'+1)*4-1] = i & 1 ? 'p' : 'P';
    }
    puts("+---+---+---+---+---+---+---+---+");
    for (int i = 8; i; i--) printf("%s\n+---+---+---+---+---+---+---+---+\n", ans[i].c_str()+1);
    return 0;
}

5、2996


这题多说一句,原因是因为做这题时被卡了半个小时。。最后居然是排序函数写错了。。感觉我是个脑残。。都没往那仔细检查。。然后在那各种修改还是样例过不了,其实就只有排序部分写错了。。代码的bug可能出在任何地方,哪怕是一个变量的定义。。

#include 
#include 
#include 
#include 
#include 
#include 
using namespace std;

const int N = 60;
map mp;
struct piece{
    int r, c, f;
    char val;
    piece(int r, int c, int f, char v) : r(r), c(c), f(f), val(v) {}
    piece() {}
    bool operator < (const piece& x) const{
        if (f != x.f) return f > x.f;
        if (val != x.val) return mp[val] < mp[x.val];
        if (f) return r == x.r ? c < x.c : r < x.r;
        return r == x.r ? c < x.c : r > x.r;
    }
};
char chess[N][N];
vector ans;

int main() {
    mp['K'] = 1; mp['Q'] = 2; mp['R'] = 3; mp['B'] = 4; mp['N'] = 5; mp['P'] = 6;
    mp['k'] = 1; mp['q'] = 2; mp['r'] = 3; mp['b'] = 4; mp['n'] = 5; mp['p'] = 6;
    gets(chess[0]);
    for (int i = 8; i > 0; i--) gets(chess[i]), gets(chess[0]);
    for (int i = 1; i <= 8; i++) {
        int cnt = 0;
        char *p = strtok(chess[i]+1, "|");
        while (p) {
            ++cnt;
            if (isalpha(p[1])) ans.push_back(piece(i, cnt, isupper(p[1]) , p[1]));
            p = strtok(NULL, "|");
        }
    }
    sort(ans.begin(), ans.end());
    int k, cnt = 0;
    for (int i = 0; i < ans.size(); i++)
        if (!ans[i].f) { k = i; break; }
    for (int i = 0; i < k; i++) {
        if (cnt++) putchar(',');
        else printf("White: ");
        if (ans[i].val != 'P') printf("%c", ans[i].val);
        printf("%c%d", ans[i].c-1 + 'a', ans[i].r);
    }
    putchar('\n');
    cnt = 0;
    for (int i = k; i < ans.size(); i++) {
        if (cnt++) putchar(',');
        else printf("Black: ");
        if (ans[i].val != 'p') printf("%c", toupper(ans[i].val));
        printf("%c%d", ans[i].c-1 + 'a', ans[i].r);
    }
    return 0;
}


我感觉以上的基本算法更多的是一些解题时的基本技巧,比如说递推、递归、构造、模拟,当然也有些算法或者思想,比如说贪心、分治、枚举。每个部分都有其精华所在,这些东西在竞赛中是最常用的。

 

你可能感兴趣的:(poj分类专题训练)