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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值