天天看點

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 <cstdio>
#include <algorithm>
#include <cstring>
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 <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
#include <utility>
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<int, int> p;
vector<p> 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 <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
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 <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <iostream>
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 <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
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 <cstdio>
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 <cstdio>
#include <cstring>
#include <vector>
using namespace std;

struct dir{ int x, y; };
const int N = 3005;
char ans[N][N], tem[7][7];
vector<dir> d;
vector<int> 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 <bits/stdc++.h>
using namespace std;

typedef pair<double, double> 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 <bits/stdc++.h>
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 <bits/stdc++.h>
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 <cstdio>
#include <utility>
#include <set>
#include <map>
#include <cstring>
using namespace std;

const int N = 1010;
map<char, int> mp;
set<char> a, tmp;
typedef pair<int, int> 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<char>::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 <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;

const int N = 1010;
int a[N], s[N], vis[N];
vector<int> 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 <cstdio>
#include <utility>
using namespace std;

pair<pair<int, int>, 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 <cstdio>
#include <cstring>
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 <cstdio>
#include <cstring>
#include <vector>
#include <map>
#include <algorithm>
#include <string>
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 <cstdio>
#include <cstring>
#include <vector>
#include <map>
#include <algorithm>
#include <cctype>
using namespace std;

const int N = 60;
map<char, int> 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<piece> 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;
}
           

我感覺以上的基本算法更多的是一些解題時的基本技巧,比如說遞推、遞歸、構造、模拟,當然也有些算法或者思想,比如說貪心、分治、枚舉。每個部分都有其精華所在,這些東西在競賽中是最常用的。