浅谈宏定义——循环宏定义

· · 个人记录

题目:浅谈宏定义——循环宏定义

作者:\texttt{Eason\underline{~}AC}

0 前言及鸣谢

宏定义,大家都可能会听说,而且也会用,比如像以下的代码:

//代码0.1 数组逆序输出I
#include <bits/stdc++.h>
#define maxn 10005  //宏定义在这里哦!
using namespace std;

int a[maxn];
int main() {
    int n;
    cin>>n;
    for(int i=1; i<=n; i++)
        cin>>a[i];
    for(int i=n; i>=1; i--)
        cout<<a[i]<<" ";
    return 0;
}

这个代码中,#define maxn 10005就是一个最普通的宏定义,它将maxn赋值为了10005。不过,你可能不知道(重点来了!),这个程序里的for循环竟然也可以通过宏定义简化!接下来,我们开始进一步了解宏定义,等到所有讲解了结后,我们在对这个程序进行进一步的简化。

在开始讲解之前,我谨在此特别感谢陈锋dalao所编写的《算法竞赛入门经典——习题与解答》,是本书的第一章教会了我如何实现宏定义!(有兴趣的可以去买一下)

1 一个简单的例子

让我们先看一下这个题目:UVa1379(由于篇幅原因,不再对题目进行赘述,有兴趣的可以去做一下这道题) 这个题目的代码如下:

//代码1.1 UVA1379(未使用宏定义)
#include <bits/stdc++.h>
using namespace std;

const int MAXN=104,MAXG=200+10+4;
struct WinP {
    int p,pit;
    WinP():p(0),pit(0){}
    bool operator<(const WinP& s) const {
        return p>s.p;
    }
}; 

WinP winps[MAXN][MAXN];
int N,M,numG,G[MAXG],DP[2][6][6][6][6];

inline int getPi(int op, int i) {
    return winps[op][i].pit;
}

double solve() {
    memset(DP,0,sizeof(DP));
    int ans=0,cur=0;
    for(int i=1; i<=numG; i++) {
        int prev=cur,now=1-cur;
        cur=1-cur;
        memset(DP[now],0,sizeof(DP[now]));

        int op=G[i];
        if(op==0) {
            for(int p1=0; p1<=5; p1++) {
                for(int p2=0; p2<=5; p2++) {
                    for(int p3=0; p3<=5; p3++) {
                        for(int p4=0; p4<=5; p4++) {
                            int& d=DP[now][0][p1][p2][p3];
                            d=max(d,DP[prev][p1][p2][p3][p4]);
                            ans=max(d,ans);
                        }
                    }
                }
            }
            continue;
        }
        for(int i0=1; i0<=5; i0++) {
            int opi=getPi(op,i0);
            for(int p1=0; p1<=5; p1++) {
                if(i>1 && getPi(G[i-1],p1)==opi)    continue;
                for(int p2=0; p2<=5; p2++) {
                    if(i>2 && getPi(G[i-2],p2)==opi)    continue;
                    for(int p3=0; p3<=5; p3++) {
                        if(i>3 && getPi(G[i-3],p3)==opi)    continue;
                        for(int p4=0; p4<=5; p4++) {
                            if(i>4 && getPi(G[i-4],p4)==opi)    continue;
                            int& d=DP[now][i0][p1][p2][p3];
                            d=max(d,DP[prev][p1][p2][p3][p4]+winps[op][i0].p);
                            ans=max(d,ans);
                        }
                    }
                }
            }
        }
    }
    double d=ans*.01;
    return d;
}

int main() {
    int T;  scanf("%d",&T);
    while(T--) {
        scanf("%d%d%d",&N,&M,&numG);
        numG+=10;
        for(int i=1; i<=M; i++) {
            for(int j=1; j<=N; j++) {
                scanf("%d",&(winps[i][j].p));
                winps[i][j].pit=j;
            }
            sort(winps[i]+1,winps[i]+N+1);
        }
        G[0]=0;
        for(int i=1; i<=numG; i++)
            scanf("%d",&(G[i]));
        double ans=solve();
        printf("%.2lf\n",ans);
    }
    return 0;
}

是不是很长,而且看上去很烦人?

接下来,我来教教大家怎么使用宏定义简化这个程序中的for循环(敲黑板)

在算法比赛中,写的最多的代码就是像这样的代码:for(int~i=0;i<n; i++){...} 这里的N也可能是一个STL中集合的大小,如vector.size之类的,所以可以使用大量的宏定义来简化代码,如:

#define _for(i,a,b) for(int i=(a); i<(b); i++)
#define _rep(i,a,b) for(int i=(a); i<=(b); i++)
//注意a和b一定要打括号!

这样写循环,就会将上述for循环简化成:_for(i,0,N),这里的a,b两个参数都可以传入表达式。宏使用恰当,可以大量简化代码,例如:

vector b;
_for(i,1,a.size()) {...}

有了宏定义,UVA1379的代码可简化成:

//代码1.2 UVA1379(使用宏定义)
#include <bits/stdc++.h>
#define _for(i,a,b) for(int i=(a); i<(b); i++)
#define _rep(i,a,b) for(int i=(a); i<=(b); i++)
using namespace std;

const int MAXN=104,MAXG=200+10+4;
struct WinP {
    int p,pit;
    WinP():p(0),pit(0){}
    bool operator<(const WinP& s) const {
        return p>s.p;
    }
}; 

WinP winps[MAXN][MAXN];
int N,M,numG,G[MAXG],DP[2][6][6][6][6];

inline int getPi(int op, int i) {
    return winps[op][i].pit;
}

double solve() {
    memset(DP,0,sizeof(DP));
    int ans=0,cur=0;
    _rep(i,1,numG) {
        int prev=cur,now=1-cur;
        cur=1-cur;
        memset(DP[now],0,sizeof(DP[now]));

        int op=G[i];
        if(op==0) {
            _rep(p1,0,5) _rep(p2,0,5) _rep(p3,0,5) _rep(p4,0,5){
                int& d=DP[now][0][p1][p2][p3];
                d=max(d,DP[prev][p1][p2][p3][p4]);
                ans=max(d,ans);
                        }
                    }
                }
            }
            continue;
        }
        _rep(i0,1,5) {
            int opi=getPi(op,i0);
            _rep(p1,0,5){
                if(i>1 && getPi(G[i-1],p1)==opi)    continue;
                _rep(p2,0,5){
                    if(i>2 && getPi(G[i-2],p2)==opi)    continue;
                    _rep(p3,0,5) {
                        if(i>3 && getPi(G[i-3],p3)==opi)    continue;
                        _rep(p4,0,5) {
                            if(i>4 && getPi(G[i-4],p4)==opi)    continue;
                            int& d=DP[now][i0][p1][p2][p3];
                            d=max(d,DP[prev][p1][p2][p3][p4]+winps[op][i0].p);
                            ans=max(d,ans);
                        }
                    }
                }
            }
        }
    }
    double d=ans*.01;
    return d;
}

int main() {
    int T;  scanf("%d",&T);
    while(T--) {
        scanf("%d%d%d",&N,&M,&numG);
        numG+=10;
        _rep(i,1,M) {
            _rep(j,1,N) {
                scanf("%d",&(winps[i][j].p));
                winps[i][j].pit=j;
            }
            sort(winps[i]+1,winps[i]+N+1);
        }
        G[0]=0;
        _rep(i,1,numG)
            scanf("%d",&(G[i]));
        double ans=solve();
        printf("%.2lf\n",ans);
    }
    return 0;
}

咋样,这程序看上去简洁多了吧? 接下来,咱们再看一个例子,看看宏定义怎样使程序简洁——

2 宏定义的应用

洛谷P1428 小鱼比可爱

这个题目的源代码如下:

//代码2.1 小鱼比可爱(未使用宏定义)
#include <bits/stdc++.h>
using namespace std;

const int MAXN=10000;
int a[MAXN],b[MAXN],n;

int main() {
    cin>>n;
    for(int i=1; i<=n; i++)
        cin>>a[i];
    for(int i=1; i<=n; i++) {
        for(int j=i-1; j>0; j--) {
            if(a[j]<a[i])
                b[i]++;
        }
    }
    for(int i=1; i<=n; i++)
        cout<<b[i]<<" ";
    return 0;
}

怎样,看上去,很多for循环,烦人吧?

那么咱们开始用吸星大法了:

//代码2.2 小鱼比可爱(使用宏定义)
#include <bits/stdc++.h>
#define _rep(i,a,b) for(int i=(a); i<=(b); i++)
#define _les(i,a,b) for(int i=(a); i>(b); i--)//重中之重!
using namespace std;

const int MAXN=10000;
int a[MAXN],b[MAXN],n;

int main() {
    cin>>n;
    for(int i=1; i<=n; i++)
        cin>>a[i];
    _rep(i,1,n)
        _les(j,i-1,0)
            if(a[j]<a[i])
                b[i]++;
    _rep(i,1,n)
        cout<<b[i]<<" ";
    return 0;
}

是不是看上去很舒服呢?

那接下来,咱们再看到代码0.1,学完宏定义,是不是可以简化这个程序呢?那咱们来开始给它整整容:

//代码2.3 数组逆序输出II
#include <bits/stdc++.h>
#define maxn 10005
#define _repup(i,a,b) for(int i=(a); i<=(b); i++)
#define _reples(i,a,b) for(int i=(a); i>=(b); i--)
using namespace std;

int a[maxn];
int main() {
    int n;
    cin>>n;
    _repup(i,1,n)
        cin>>a[i];
    _reples(i,n,1)
        cout<<a[i]<<" ";
    return 0;
}

3 一些警告

1.本文中所有程序均为AC代码,若抄袭,屎名后果自负。

2.本文有些语言采自陈锋的《算法经典入门经典——习题与解答》,但非抄袭,若有疑问,可私信给我(本蒟蒻UID:112917)。

4 结尾

那好了,本次宏定义的讲解到此结束,希望各位OIer们在解题时,多多简化代码,这样,别人看得懂,自己也不会晕。加油哦!QAQ233

2018.9.29于长沙市麓山国际实验学校机房写