1. 程式人生 > >3.2常用技巧精選(一) 挑戰程式設計競賽

3.2常用技巧精選(一) 挑戰程式設計競賽

本文來自《挑戰程式設計競賽》3.2常用技巧精選(一)

1.尺取法

尺取法通常是指對陣列儲存一對下標(起點、終點),然後根據實際情況交替推進兩個端點直到得出答案的方法。

1.Subsequence(Poj 3061)

1.題目原文:

Language: Subsequence
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 12584 Accepted: 5309

Description

A sequence of N positive integers (10 < N < 100 000), each of them less than or equal 10000, and a positive integer S (S < 100 000 000) are given. Write a program to find the minimal length of the subsequence of consecutive elements of the sequence, the sum of which is greater than or equal to S.

Input

The first line is the number of test cases. For each test case the program has to read the numbers N and S, separated by an interval, from the first line. The numbers of the sequence are given in the second line of the test case, separated by intervals. The input will finish with the end of file.

Output

For each the case the program has to print the result on separate line of the output file.if no answer, print 0.

Sample Input

2
10 15
5 1 3 5 10 7 4 9 2 8
5 11
1 2 3 4 5

Sample Output

2
3

Source

題目意思:給定長度為n的數列整數a[0],a[1],a[2]……,a[n-1]以及整數S。求出總和不小於S的連續子序列的長度的最小值。如果解不存在,則輸出0.

2.解題思路1:

由於所有的元素都大於0,如果子序列[s,t)滿足a[s]+a[s+1]+……a[t-1]>=S,那麼對於任何t'>t一定有a[s]+a[s+1]+……+a[t'-1]>=S。此外對於區間[s,t)上的總和來說,如果令sum[i]=a[0]+a[1]+……s[i-1],則a[s]+a[s+1]+……a[t-1]=sum[t]-sum[s]。因此可以預先以O(n)的時間計算好sum,就可以在O(1)的時間內計算區間和。這樣以來子序列的起點確定了,利用二分搜尋就很容易確定序列和不小於S的終點t的最小值,時間複雜度為O(nlogn)。

3.AC程式碼1:

#include <iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
#define maxn 100005
int n,S;
int a[maxn];
int sum[maxn+1];
//sum[i]=a[0]+a[1]+……a[i-1]
//a[s]+a[s+1]+a[s+2]+……a[t-1]=sum[t]-sum[s]
void solve()
{
    int res=n;
    sum[0]=0;
    for(int i=0;i<n;i++){
        sum[i+1]=sum[i]+a[i];
    }
    if(sum[n]<S){
        printf("0\n");
        return;
    }
    for(int s=0;sum[s]+S<=sum[n];s++){
        int t=lower_bound(sum+s,sum+n,sum[s]+S)-sum;
        res=min(res,t-s);
    }
    printf("%d\n",res);

}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&S);
        for(int i=0;i<n;i++){
            scanf("%d",&a[i]);
        }
        solve();
    }
    return 0;
}

4.解題思路2:

設以a[s]開始總和最初大於S時的連續子序列是a[s]+a[s+1]+……a[t-1],那麼以a[s+1]開始總和最初超過S的連續子序列是a[s+1]+a[s+2]+……a[t'-1]的話,則必然有t<t'。利用這一性質可以設計如下演算法: (1)以s=t=sum=0初始化; (2)只要依然有sum<S,就不斷將sum增加a[t],然後將t增加1; (3)如果(2)中無法滿足sum>=S則終止,否則更新res=min(res,t-s); (4)將sum減去a[s],s增加1然後回到(2)。 這個演算法,t最多變化n次,所以時間複雜度為O(n),更高效(但是為何時間一樣……)

5.AC程式碼2:

#include <iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
#define maxn 100005
int n,S;
int a[maxn];
int sum[maxn+1];
void solve()
{
    int s=0,t=0,sum=0;
    int res=n+1;
    for(;;){
        while(t<n&&sum<S){
            sum+=a[t++];
        }
        if(sum<S) break;
        res=min(res,t-s);
        sum-=a[s++];
    }
    if(res>n){
        //解不存在
        res=0;
    }
    printf("%d\n",res);

}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d",&n,&S);
        for(int i=0;i<n;i++){
            scanf("%d",&a[i]);
        }
        solve();
    }
    return 0;
}

2.Jessica's Reading Problem(Poj 3320)

1.題目原文

Jessica's Reading Problem
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 10803 Accepted: 3620

Description

Jessica's a very lovely girl wooed by lots of boys. Recently she has a problem. The final exam is coming, yet she has spent little time on it. If she wants to pass it, she has to master all ideas included in a very thick text book. The author of that text book, like other authors, is extremely fussy about the ideas, thus some ideas are covered more than once. Jessica think if she managed to read each idea at least once, she can pass the exam. She decides to read only one contiguous part of the book which contains all ideas covered by the entire book. And of course, the sub-book should be as thin as possible.

A very hard-working boy had manually indexed for her each page of Jessica's text-book with what idea each page is about and thus made a big progress for his courtship. Here you come in to save your skin: given the index, help Jessica decide which contiguous part she should read. For convenience, each idea has been coded with an ID, which is a non-negative integer.

Input

The first line of input is an integer P (1 ≤ P ≤ 1000000), which is the number of pages of Jessica's text-book. The second line contains P non-negative integers describing what idea each page is about. The first integer is what the first page is about, the second integer is what the second page is about, and so on. You may assume all integers that appear can fit well in the signed 32-bit integer type.

Output

Output one line: the number of pages of the shortest contiguous part of the book which contains all ideals covered in the book.

Sample Input

5
1 8 8 8 1

Sample Output

2

Source

2.題目意思:

為了準備考試,Jessica開始讀一本很厚的課本,要想通過考試,必須把課本中所有的知識點都掌握,這本書總共有P頁,第i頁恰好有一個知識點a[i],全書中同一個知識點可能會多次提到,所以她希望閱讀其中連續的一些頁把所有的知識點都覆蓋到。給定每頁寫到的知識點,求出要閱讀的最少頁數。

3.解法分析:

我們假設從某一頁s開始閱讀,為了覆蓋所有的知識點需要閱讀到t。這樣的話可以知道若從s+1開始閱讀,那麼必須閱讀到t'>t為止。因此可以採用尺取法。 在某個區間s[,t]已經覆蓋了所有的知識點的情況下,下一個區間[s+1,t']如何求出呢? 所有的知識點都被覆蓋→ 每個知識點出現的次數不少於1次。 由以上的等價關係,我們可以利用適當的資料結構儲存[s,t]區間上每個知識點的出現次數,這樣把最開頭的頁s去掉後便可以判斷[s+1,t]是否滿足條件。 把區間的最開頭s去掉之後,頁s上的知識點出現次數就要減1,如果此時這個知識點的出現次數為0,在同一個知識點出現之前,不停地將末尾t向後推緊即可。每次在追加頁t時,將頁t上知識點出現的次數加1,就完成了下一個區間各個知識點出現次數的更新。通過這一操作可以以OPlogP)的複雜度求出最小區間。

4.AC程式碼:

#include <iostream>
#include<algorithm>
#include<cstdio>
#include<set>
#include<map>
using namespace std;
#define maxp 1000005
int P;
int a[maxp];
void solve()
{
    //計算全部知識點的總數
    set<int> all;
    for(int i=0;i<P;i++){
        all.insert(a[i]);
    }
    //知識點的總數
    int n=all.size();
    //利用尺取法求解
    int s=0,t=0,num=0;
    map<int,int> count;//知識點→出現次數的對映
    int res=P;
    for(;;){
        while(t<P&&num<n){
            if(count[a[t++]]++==0){
                //出現新的知識點
                num++;
            }
        }
        if(num<n) break;
        res=min(res,t-s);
        if(--count[a[s++]]==0){
            //某個知識的出現次數是0
            num--;
        }
    }
    printf("%d\n",res);
}
int main()
{
    scanf("%d",&P);
    for(int i=0;i<P;i++){
        scanf("%d",&a[i]);
    }
    solve();
    return 0;
}

2.開關問題

1.Face The Right Way Poj 3276

1.題目原文

Face The Right Way
Time Limit: 2000MS Memory Limit: 65536K
Total Submissions: 4244 Accepted: 1954

Description

Farmer John has arranged his N (1 ≤ N ≤ 5,000) cows in a row and many of them are facing forward, like good cows. Some of them are facing backward, though, and he needs them all to face forward to make his life perfect.

Fortunately, FJ recently bought an automatic cow turning machine. Since he purchased the discount model, it must be irrevocably preset to turn K (1 ≤ K ≤ N)cows at once, and it can only turn cows that are all standing next to each other in line. Each time the machine is used, it reverses the facing direction of a contiguous group of K cows in the line (one cannot use it on fewer than K cows, e.g., at the either end of the line of cows). Each cow remains in the same *location* as before, but ends up facing the *opposite direction*. A cow that starts out facing forward will be turned backward by the machine and vice-versa.

Because FJ must pick a single, never-changing value of K, please help him determine the minimum value of K that minimizes the number of operations required by the machine to make all the cows face forward. Also determine M, the minimum number of machine operations required to get all the cows facing forward using that value of K.

Input

Line 1: A single integer: N 
Lines 2..N+1: Line i+1 contains a single character, F or B, indicating whether cow i is facing forward or backward.

Output

Line 1: Two space-separated integers: K and M

Sample Input

7
B
B
F
B
F
B
B

Sample Output

3 3

Hint

For K = 3, the machine must be operated three times: turn cows (1,2,3), (3,4,5), and finally (5,6,7)

Source

2.題目意思:

N頭牛排成一列,每頭牛向前或向後,有一臺機器可以使K頭連續的牛轉向,但是機器在購買時就必須設定K值,機器不能使少於K頭連續的牛轉向。請求出為了讓所有牛都面向前方最少的操作次數M和對應的K。

3.解法與思路分析

首先交換區間反轉的順序是無關緊要的,此外可以知道對一個區間進行兩次以上(含兩次)的的翻轉操作是多餘的,因此問題就轉化成求需要被翻轉的區間。我們可以先考慮最左邊的區間,這個很容易確定。 定義f[i]:區間[i,i+K-1]進行了反轉的話則為1,否則為0。這樣在考慮第i頭牛時,如果從i-K+1到i-1∑f[j]為奇數的話,第i頭牛的方向與起始方向相反,否則方向不變。 由於從(i+1)-K+1到i∑f[j]=從i-K+1到i-1∑f[j]+f[i]-f[i-K+1]。所以這個和每一次都可以用常數時間計算出來,時間複雜度為O(N^2)。

4.AC程式碼

#include <iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define maxn 5005
int N;
int dir[maxn];//0:F,1:B
int f[maxn];//區間[i,i+k-1]是否翻轉
//固定k,求對應的最小運算元
//無解時,返回-1
int calc(int K)
{
    memset(f,0,sizeof(f));
    int res=0;
    int sum=0;//f的和
    for(int i=0;i+K<=N;i++){
        //計算區間[i,i+K-1]
        if((dir[i]+sum)%2!=0){
            //前端的牛面向後方
            //sum為奇數時,第i頭牛的方向與初始方向相反
            //dir[i]為1時不需要翻轉,dir[i]為0時需要翻轉
            //sum為偶數時,第i頭牛的方向與初始方向相同
            //dir[i]為1時需要翻轉,dir[i]為0時不需要翻轉
            res++;
            f[i]=1;
        }
        sum+=f[i];
        if(i-K+1>=0){
            sum-=f[i-K+1];
        }
    }
    //檢查剩下的牛是否有面朝後方的情況
    for(int i=N-K+1;i<N;i++){
        if((dir[i]+sum)%2!=0){
            //無解,因為機器不能讓少於K頭連續的牛轉向
            return -1;
        }
        if(i-K+1>=0){
            sum-=f[i-K+1];
        }
    }
    return res;
}
void solve()
{
    int K=1,M=N;
    for(int k=1;k<=N;k++){
        int m=calc(k);
        if(m>=0&&M>m){
            K=k;
            M=m;
        }
    }
    printf("%d %d\n",K,M);
}
int main()
{
    scanf("%d",&N);
    for(int i=0;i<N;i++){
        char c;
        cin>>c;
        if(c=='F') dir[i]=0;
        else dir[i]=1;
    }
    solve();
    return 0;
}

2.Fliptile Poj 3279

1.題目原文

Fliptile
Time Limit: 2000MS Memory Limit: 65536K
Total Submissions: 7687 Accepted: 2894

Description

Farmer John knows that an intellectually satisfied cow is a happy cow who will give more milk. He has arranged a brainy activity for cows in which they manipulate an M × N grid (1 ≤ M ≤ 15; 1 ≤ N ≤ 15) of square tiles, each of which is colored black on one side and white on the other side.

As one would guess, when a single white tile is flipped, it changes to black; when a single black tile is flipped, it changes to white. The cows are rewarded when they flip the tiles so that each tile has the white side face up. However, the cows have rather large hooves and when they try to flip a certain tile, they also flip all the adjacent tiles (tiles that share a full edge with the flipped tile). Since the flips are tiring, the cows want to minimize the number of flips they have to make.

Help the cows determine the minimum number of flips required, and the locations to flip to achieve that minimum. If there are multiple ways to achieve the task with the minimum amount of flips, return the one with the least lexicographical ordering in the output when considered as a string. If the task is impossible, print one line with the word "IMPOSSIBLE".

Input

Line 1: Two space-separated integers: M and N 
Lines 2..M+1: Line i+1 describes the colors (left to right) of row i of the grid with N space-separated integers which are 1 for black and 0 for white

Output

Lines 1..M: Each line contains N space-separated integers, each specifying how many times to flip that particular location.

Sample Input

4 4
1 0 0 1
0 1 1 0
0 1 1 0
1 0 0 1

Sample Output

0 0 0 0
1 0 0 1
1 0 0 1
0 0 0 0

Source

2.題目意思

有一個M*N的格子,每個格子可以翻轉正反面,一面是黑色,一面是白色。遊戲的目標是把所有的格子都翻轉成白色。每次翻轉一個格子,與它上下左右的格子也會被翻轉。目的是通過儘可能少的次數把所有的格子翻轉成白色。現在給定每個格子的顏色,求最小步數完成時每個格子的翻轉次數。最小解有多個時,輸出字典序最小的一組。若不存在,則輸出IMPOSSIBLE。

3.解法與思路分析

首先,同一個格子翻轉兩次就會恢復原狀,所以多次翻轉是多餘的。此外,翻轉的格子相同時,次序是無關緊要的。因此總共有2^NM種翻轉方法。 回顧前一個問題,最左邊的牛反轉的方法只有一種,於是直接判斷即可。同樣的方法考慮這一題。對於最左上角的格子,除了翻轉(1,1)之外,還可以翻轉(2,1)、(1,2)。所以上述方法行不通。 於是不妨指定最上面一行的翻轉方法,此時能翻轉(1,1)的格子只有(2,1)了。所以可以直接判斷(2,1)是否需要翻轉。類似的(2,1)到(2,N)都能這樣判斷,如此反覆下去所有的格子的翻轉方法都可以確定,最後判斷最後一行,若非全白色,則不可能。 像這樣,先確定第一行的翻轉方式,然後可以很容易判斷這樣是否存在解以及解的最小步數,這樣將第一行的所有翻轉方式都嘗試一次就能求出整個問題的最小步數。演算法的時間複雜度為(NM2^N)。 另外注意本題涉及位運算相關知識。

4.AC程式碼

#include <iostream>
#include<cstdio>
#include<utility>
#include<cstring>
using namespace std;
#define MAX_N 20
#define MAX_M 20
const int dx[]={-1,0,0,0,1};
const int dy[]={0,1,0,-1,0};

int M,N;
int tile[MAX_M][MAX_N];

int opt[MAX_M][MAX_N];//儲存最優解
int flip[MAX_M][MAX_N];//儲存中間結果

//查詢(x,y)的顏色
int get(int x,int y)
{
    int c=tile[x][y];
    for(int d=0;d<5;d++){
        int x2=x+dx[d],y2=y+dy[d];
        if(0<=x2&&x2<M&&0<=y2&&y2<N){
            c+=flip[x2][y2];
        }
    }
    return c%2;
}
//求出在第一行確定情況下的最小操作次數
//若不存在返回-1
int calc()
{
    for(int i=1;i<M;i++){
        for(int j=0;j<N;j++){
            if(get(i-1,j)!=0){
                //(i-1,j)是黑色,必須翻轉(i,j)這個格子
                flip[i][j]=1;
            }
        }
    }
    //判斷最後一行是否全白
    for(int j=0;j<N;j++){
        if(get(M-1,j)!=0){
            return -1;
        }
    }
    //統計翻轉的次數
    int res=0;
    for(int i=0;i<M;i++){
        for(int j=0;j<N;j++){
            res+=flip[i][j];
        }
    }
    return res;
}
void solve()
{
    int res=-1;
    //按照字典序嘗試第一行的所有可能性
    for(int i=0;i<1<<N;i++){
        memset(flip,0,sizeof(flip));
        for(int j=0;j<N;j++){
            flip[0][N-1-j]=i>>j&1;
        }
        int num=calc();
        if(num>=0&&(res<0||res>num)){
            res=num;
            memcpy(opt,flip,sizeof(flip));
        }
    }
    if(res<0){
        printf("IMPOSSIBLE\n");
    }
    else{
        for(int i=0;i<M;i++){
            for(int j=0;j<N;j++){
                printf("%d%c",opt[i][j],j+1==N?'\n':' ');
            }
        }
    }
}
int main()
{
    scanf("%d%d",&M,&N);
    for(int i=0;i<M;i++){
        for(int j=0;j<N;j++){
            scanf("%d",&tile[i][j]);
        }
    }
    solve();
    return 0;
}
補充一道例題,https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=2459

5.座標離散化

1.例題

1.題目原文

w*h的格子上畫了n條或垂直或水平的寬度為1的直線。求出這些線將格子劃分成了多少個區域。 限制條件 1<=w,h<=1000000 1<=n<=500

2.解題思路

w*h過大,沒法建立w*h的陣列,可以採用座標離散化,前後沒有變化的行列消除後並不影響區域的總數。 陣列只需要儲存有直線的行列以及前後的行列就足夠了,這樣的話大小最多6n*6n。 因此可以建立陣列並利用搜索求出區域的個數。

3.程式碼

#include<algorithm>
#include<cctype>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iomanip>
#include<iostream>
#include<map>
#include<queue>
#include<string>
#include<set>
#include<vector>
#include<cmath>
#include<bitset>
#include<stack>
#include<sstream>
using namespace std;
#define INF 0x7fffffff
const int maxn=505;

int dx[]={1,-1,0,0};
int dy[]={0,0,1,-1};

int W,H,N;
int X1[maxn],X2[maxn],Y1[maxn],Y2[maxn];

//填充用
bool fld[6*maxn][6*maxn];

//對x1和x2進行座標離散化
//返回離散化之後的寬度
int compress(int *x1,int *x2,int w)
{
    vector<int> xs;

    for(int i=0;i<N;i++){
        for(int d=-1;d<=1;d++){
            int tx1=x1[i]+d;
            int tx2=x2[i]+d;
            if(1<=tx1&&tx1<=W) xs.push_back(tx1);
            if(1<=tx2&&tx2<=W) xs.push_back(tx2);
        }
    }
    
    //排序去重
    sort(xs.begin(),xs.end());
    xs.erase(unique(xs.begin(),xs.end()),xs.end());

    for(int i=0;i<N;i++){
        x1[i]=find(xs.begin(),xs.end(),x1[i])-xs.begin();
        x2[i]=find(xs.begin(),xs.end(),x2[i])-xs.begin();
    }
    return xs.size();
}

void solve()
{
    //座標離散化
    W=compress(X1,X2,W);
    H=compress(Y1,Y2,H);

    //填充有直線的部分
    memset(fld,0,sizeof(fld));
    for(int i=0;i<N;i++){
        for(int y=Y1[i];y<=Y2[i];y++){
            for(int x=X1[i];x<=X2[i];x++){
                fld[y][x]=true;
            }
        }
    }

    //求區域的個數
    int ans=0;
    for(int y=0;y<H;y++){
        for(int x=0;x<W;x++){
            if(fld[y][x]) continue;
            ans++;

            //寬度優先搜尋
            queue<pair<int,int> > que;
            que.push(make_pair(x,y));
            while(!que.empty()){
                int sx=que.front().first;
                int sy=que.front().second;
                que.pop();

                for(int i=0;i<4;i++){
                    int tx=sx+dx[i];
                    int ty=sy+dy[i];
                    if(tx<0||tx>=W||ty<0||ty>=H) continue;
                    if(fld[ty][tx]) continue;
                    que.push(make_pair(tx,ty));
                    fld[ty][tx]=true;
                }
            }
        }
    }
    printf("%d\n",ans);
}

int main()
{
    scanf("%d%d%d",&W,&H,&N);
    for(int i=0;i<N;i++){
        scanf("%d",&X1[i]);
    }
    for(int i=0;i<N;i++){
        scanf("%d",&X2[i]);
    }
    for(int i=0;i<N;i++){
        scanf("%d",&Y1[i]);
    }
    for(int i=0;i<N;i++){
        scanf("%d",&Y2[i]);
    }
    solve();
    return 0;
}
補充一道題目:http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=0531