2018级软院算法 第一次上机

2018级软院算法 第一次上机


A 冒泡排序

题意:使用冒泡排序需要进行多少次元素交换。

思路:考虑每一次交换,都是相邻两个元素前面那个比后面那个大,而在交换前(可能一开始这两个元素并不相邻),这两个元素是一对逆序对。所以总共进行交换的次数就是数列逆序对数。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;

int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=5e5+5;
int a[maxn],b[maxn],n;
LL ans;

void solve(int l,int r)
{
    if(l>=r) return;
    int mid=(l+r)>>1,i=l,j=mid+1,q=l;
    solve(l,mid);solve(mid+1,r);
    while(i<=mid && j<=r)
    {
        if(a[i]<=a[j]) b[q++]=a[i++];
        else ans+=mid-i+1,b[q++]=a[j++];
    }
    while(i<=mid) b[q++]=a[i++];
    while(j<=r) b[q++]=a[j++];
    //copy(b+l,b+r+1,a+l);
    REP(i,l,r) a[i]=b[i];
}

int main()
{
    //freopen("input.txt","r",stdin);
    n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    solve(1,n);
    cout<<ans;

	return 0;
}

B Half

题意:有n个数。每次操作可以使一个数的值减半(除以2向下舍入)。问k次操作后,这些数的和最小为多少。

思路:可以看出这题需要用贪心思想,每次将数列中最大那个数减半,这样每次可以减得尽量多,最后的和也就最小。可以用C++STL中的优先队列维护数列中的最大值。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;

int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

int main()
{
    int k,n;
    while(~scanf("%d %d",&n,&k))
    {
        priority_queue<int> que;
        while(n--) que.push(read());
        while(k--)
        {
            int x=que.top();
            que.pop();
            que.push(x/2);
        }
        LL ans=0;
        while(!que.empty()) ans+=que.top(),que.pop();
        cout<<ans<<endl;
    }

	return 0;
}

C pair

题意:给出pair、int序列,问怎样添加’<’、’>’、’,'可以使之合法(pair<type,type>为合法)。合法还要输出最终字符串。

思路:使用存储当前序列。每次读入新元素(一个字符串)时,一直循环检查栈顶三个元素是否可以组成一个合法的type,也即栈顶三个元素是否分别是pair、type、type三种类型(int也是type类型),如果可以的话就将三个元素取出然后再添加一个type入栈,直到不行时执行下一次循环。

因为还要输出最终序列,所以我们在合并时要记录每个位置需要添加哪些字符,所以栈中的元素还要记录这一个元素的结尾位置,每次合并时对应在相应位置增添字符,最后输出即可。

本题中还要特判当n特别小(n<3)的情况。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;

typedef pair<int,int> P;
P st[1005];
int t,n,top,cnt[1005],fl[1005];
char s[10],in[1005][500];

int change(char *s)
{
    if(s[0]=='i') return 0;
    else return 1;
}

int main()
{
    //freopen("input.txt","r",stdin);
    cin>>t;
    while(t--)
    {
        mem(cnt,0);
        top=0;
        scanf("%d",&n);
        REP(i,1,n)
        {
            scanf("%s",s);
            fl[i]=change(s);
            st[top++]=P(fl[i],i);
            while(top>=3 && !st[top-1].first && !st[top-2].first && st[top-3].first)
            {
                int x=st[top-3].second,y=st[top-2].second,z=st[top-1].second;
                in[x][cnt[x]++]='<';
                in[y][cnt[y]++]=',';
                in[z][cnt[z]++]='>';
                top-=3;
                st[top++]=P(0,z);
            }
        }
        if(!(top==1 && !st[0].first && n>2)) printf("Error occurred\n");
        else
        {
            REP(i,1,n)
            {
                printf("%s",fl[i]?"pair":"int");
                REP(j,0,cnt[i]-1) putchar(in[i][j]);
            }
            puts("");
        }
    }

	return 0;
}

D 希尔伯特曲线

题意:https://accoding.cn/problem/2488/index

思路:根据希尔伯特曲线的构造方式,可以思考,如果我们现在要求第n个曲线的编号为a的位置的坐标,我们可以处理出这个位置对应哪一个1/4块,而如果我们已经知道第n-1个曲线对应编号(a减去相应的一个值,比如说在第n个曲线中这个位置对应右下角那个1/4块,则a应当减去两个1/4块的格子数量)的坐标的话,就可以根据其在第n个曲线是哪一个1/4块,对此坐标作相应的对称和平移运算。可以看出这是一个递归过程。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <cstring>
#include <stack>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;

int n;
LL a,b;
typedef pair<LL,LL> P;
stack<int> st;	//记录这个块在下个块的位置

void hhh(LL x,int n)
{
    if(!n) return;
    int q=(x-1)/(1ll<<(n*2-2))+1;	//当前编号x处于哪个1/4块
    st.push(q);
    hhh(x-(1ll<<(n*2-2))*(q-1),n-1);
}

P cor(stack<int> st)	//对称平移变换
{
    LL x=0,y=0;
    int n=0;
    while(!st.empty())
    {
        int q=st.top();
        st.pop();
        LL d=1<<n;
        n++;
        if(!x)
        {
            if(q==1) x=1,y=1;
            else if(q==2) x=2,y=1;
            else if(q==3) x=2,y=2;
            else x=1,y=2;
        }
        else
        {
            if(q==1) swap(x,y);
            else if(q==2) x+=d;
            else if(q==3) x+=d,y+=d;
            else
            {
                LL xx=d-y+1,yy=d-x+1;
                x=xx,y=yy+d;
            }
        }
    }
    return P(x,y);
}

int main()
{
    //freopen("input.txt","r",stdin);
    while(~scanf("%d %lld %lld",&n,&a,&b))
    {
        while(!st.empty()) st.pop();
        hhh(a,n);
        P x=cor(st);
        while(!st.empty()) st.pop();
        hhh(b,n);
        P y=cor(st);
        //printf("%lld %lld   %lld %lld\n",x.first,x.second,y.first,y.second);
        printf("%lld\n",(x.first-y.first)*(x.first-y.first)+(x.second-y.second)*(x.second-y.second));
    }

	return 0;
}

E 点灯

题意:https://accoding.cn/problem/2489/index

思路:这道题的n的范围特别大,所以不能用朴素的差分思想,但是可以考虑离散化,这个先不作分析。我们可以考虑每一个灯的亮和灭与什么有关,嗯,和这个灯的操作次数有关,如果操作次数为奇数对应亮,反之为灭。我们可以把每次操作区间的左端点和右端点+1存入数组中(为什么+1待会儿说),然后排序,每遇到一个左端点,说明当前往后的一段区间内操作次数+1,而每遇到一个右端点,说明当前往后一段区间内操作次数-1,每当操作次数为偶数时,就根据当前位置和上一个位置统计答案即可。可以发现,其实还可以更简便,因为每次遇到端点操作次数都会奇偶互换,所以我们只用0和1记录奇偶即可。

现在说明为什么右端点储存时要+1。我们可以这样理解:每当遇到一个端点时,说明这个端点前面的所有元素已经处理完了(奇偶已经确定),而对于一个区间,如果遇到左端点没有问题,而如果遇到右端点,假如没有+1的话,右端点对应的元素实际上被处理为“未确定”,会在下一次继续变化。这就是右端点+1的原因。结合差分的思想,或许会有更好的理解。

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;

int read()
{
    int x=0,flag=1;
    char c=getchar();
    while((c>'9' || c<'0') && c!='-') c=getchar();
    if(c=='-') flag=0,c=getchar();
    while(c<='9' && c>='0') {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return flag?x:-x;
}

const int maxn=2e5+5;
typedef pair<int,int> P;
P a[maxn];
int tot;

int main()
{
    //freopen("input.txt","r",stdin);
    int n,k,l,r;
    while(~scanf("%d %d",&n,&k))
    {
        tot=0;
        REP(i,1,k)
        {
            l=read();
            r=read();
            a[tot++]=P(l,0);
            a[tot++]=P(r+1,1);
        }
        sort(a,a+tot);
        int now=0,last=0,ans=0;
        REP(i,0,tot-1)
        {
            if(!now) now=1,last=a[i].first;
            else now=0,ans+=a[i].first-last;
        }
        printf("%d\n",ans);
    }

	return 0;
}

F Zexal的过河

题意:对于多组输入n,问从0开始,每次只能+1、+2或+3,有多少种方法达到n,其中不能连续两次+3。(上台阶问题)

思路:考虑dp。因为这相比于传统的上台阶问题有一个条件限制,我们考虑增加一个维度记录当前这次是否使用+3,具体来说: d p [ i ] [ j ] dp[i][j] 表示本次使用+3情况为j的条件下(j=1代表使用,0代表不使用),达到i的方式总数。转移方程如下:

d p [ i ] [ 0 ] = d p [ i 1 ] [ 0 ] + d p [ i 1 ] [ 1 ] + d p [ i 2 ] [ 0 ] + d p [ i 2 ] [ 1 ] dp[i][0]=dp[i-1][0]+dp[i-1][1]+dp[i-2][0]+dp[i-2][1]
d p [ i ] [ 1 ] = d p [ i 3 ] [ 0 ] dp[i][1]=dp[i-3][0]

具体意义结合dp数组的意义应该很好理解。

初始条件 d p [ 0 ] [ 0 ] = 1 dp[0][0]=1

代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#define mem(a,b) memset(a,b,sizeof(a))
#define REP(i,a,b) for(int i=(a);i<=(int)(b);i++)
#define REP_(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
typedef long long LL;

const int maxn=55;
LL f[maxn][2],ans[maxn];
int n;

int main()
{
    //freopen("input.txt","r",stdin);
    f[0][0]=f[1][0]=1;
    REP(i,2,50)
    {
        f[i][0]=f[i-1][0]+f[i-1][1]+f[i-2][0]+f[i-2][1];
        if(i>=3) f[i][1]=f[i-3][0];
    }
    REP(i,1,50) ans[i]=f[i][0]+f[i][1];
    while(~scanf("%d",&n)) printf("%lld\n",ans[n]);

	return 0;
}
发布了12 篇原创文章 · 获赞 5 · 访问量 531

猜你喜欢

转载自blog.csdn.net/dragonylee/article/details/102479631