NOIP模拟21题解

来源:互联网 发布:高中毕业旅行知乎 编辑:程序博客网 时间:2024/04/29 11:19

Contents

  • Contents
    • 六元组
      • Description
      • Input
      • Output
      • 数据范围与约定
      • Solution
    • 牛排序
      • Description
      • Input
      • Output
      • 样例解释
      • Solution
        • Step1
        • Step2
        • Step3
        • Step4
    • 打砖块
      • Description
      • Input
      • Output
      • 数据范围与约定
      • Solution

1.六元组

(six.c/.cpp/.pas)

Description

有n个整数,现在想知道有多少个六元组(a,b,c,d,e,f)
满足:(a × b + c) ÷ d – e = f

Input

输入文件名为(six.in)。
第一行:n(1<=n<=10)
第二行n个数ai(-30000<=ai<=30000)

Output

输出文件名为(six.out)。
输出这样的六元组的个数
2
2 3 4

数据范围与约定

对于30%的数据,1 <= n <= 10
对于100%的数据,1 <= n <= 100

Solution:

刚开始以为题意是:给定的ai就是a,所以要找出满足所有ai的b,c,d,e,f
鬼能做出来啊。。
最后才理清了题意,a,b,c,d,e,f就是n个数的任意一个,只要满足要求就好。这样就变得十分简单了。但是看数据范围就可以知道,六维的枚举只能得30分,而要想得100分就必须压到至多三维。但这样也并不麻烦,化简可以得到
a*b+c=d*(e+f)这样,就可以同时枚举三个数a[i],a[j],a[k]。假如把等式左边的数放到数组l[],右边放到r[]中,如果l[]中的一个数在r[]中出现过,累加次数就是最后的答案了。
但是需要注意的是,不能直接枚举,这样会TLE,可以用STL中的map或者multiset,但事实证明后者不可行。。同样超时。下面是两种方法的代码:
Code1【map】:

#include <stdio.h>#include <string.h>#include <map>#define MAXN 1001000typedef long long ll;using namespace std;int n,ans;int a[110],l[MAXN],r[MAXN];map<int,int>mp;int main(){    freopen("six.in","r",stdin);    freopen("six.out","w",stdout);    scanf("%d",&n);    for(int i=1;i<=n;i++)    {        scanf("%d",&a[i]);    }    for(int i=1;i<=n;i++)    {        for(int j=1;j<=n;j++)        {            for(int k=1;k<=n;k++)            {                mp[a[i]*a[j]+a[k]]++;            }        }    }    for(int i=1;i<=n;i++)    {        if(!a[i])            continue;        for(int j=1;j<=n;j++)        {            for(int k=1;k<=n;k++)            {                int x=a[i]*(a[j]+a[k]);                ans+=mp[x];            }         }    }    printf("%d\n",ans);    return 0;}

Code2:【multiset】

#include <stdio.h>#include <string.h>#include <set>#define MAXN 1001000typedef long long ll;using namespace std;ll n,ans;ll a[110],l[MAXN],r[MAXN];multiset<int>s;int main(){    freopen("six.in","r",stdin);    freopen("six.out","w",stdout);    scanf("%lld",&n);    for(ll i=1;i<=n;i++)    {        scanf("%lld",&a[i]);    }    ll cnt_l=0,cnt_r=0;    for(ll i=1;i<=n;i++)    {        for(ll j=1;j<=n;j++)        {            for(ll k=1;k<=n;k++)            {                s.insert(a[i]*a[j]+a[k]);            }        }    }    for(int i=1;i<=n;i++)    {        for(int j=1;j<=n;j++)        {            for(int k=1;k<=n;k++)            {                ll x=a[i]*(a[j]+a[k]);                if(s.count(x)!=0)                {                    ans+=s.count(x);                }            }        }    }    printf("%lld\n",ans);    return 0;}

2.牛排序

(cowsort.c/.cpp/.pas)

Description

农夫JOHN准备把他的 N(1 <= N <= 10,000)头牛排队以便于行动。因为脾气大的牛有可能会捣乱,JOHN想把牛按脾气的大小排序。每一头牛的脾气都是一个在1到100,000之间的整数并且没有两头牛的脾气值相同。在排序过程中,JOHN可以交换任意两头牛的位置。因为脾气大的牛不好移动,JOHN需要X+Y秒来交换脾气值为X和Y的两头牛。
请帮JOHN计算把所有牛排好序的最短时间。

Input

输入文件名为(cowsort.in)。
第1行: 一个数N。
第2~N+1行: 每行一个数,第i+1行是第i头牛的脾气值。

Output

输出文件名为(cowsort.out)。
第1行: 一个数,把所有牛排好序的最短时间。
3
2
3
1 7

样例解释

队列里有三头牛,脾气分别为 2,3, 1。
2 3 1 : 初始序列
2 1 3 : 交换脾气为3和1的牛(时间=1+3=4).
1 2 3 : 交换脾气为1和2的牛(时间=2+1=3).

Solution:

刚开始以为这道题跟快排好像啊,然后就手写了一个快排,然后加上变化的脾气值。但是想想就知道了,这样只维护了交换次数最少,但不一定是交换的总价值和最小。
唉(。・∀・)ノ゙,还是介绍一下正解吧:
一共分为4步:

Step1:

Two Example

8 4 5 3 2 7 1 8 9 7 6 2 3 4 5 7 8 1 6 7 8 9

第一步,很简单了~单独排个序。

Step2:

可以感受一下,(3 4 5)(8 2 7)是第一组数据的两个循环;(1)(8 9 7 6)是第二组数据的两个循环。学名叫做置换群(这样更好理解?)
所以我们的第二步就是找到所谓的置换群

Step3:

通常在一个循环中,用min置换其余的所有数一定为最优方案。总代价为:
sum-min+(len-1)*min=>sum+(len-2)*min;

Step4:

但是如1 8 9 7 6上述方法并不成立,与其令(1)单独成为一组不如把6替换出来。也就是(6)(1 8 9 7)这样的代价为sum+min+(len-1)*smallest;

因此对于每一个循环来说,ans+=min{sum+(len-2)*min ,sum+min+(len-1)*smallest}
Code:

#include <stdio.h>#include <string.h>#include <algorithm>#define INF 9999999#define MAXN 1000000using namespace std;struct node{    int len;    int mi;    int sum;}cir[MAXN];int n,cnt,ans;int a[MAXN],tmp[MAXN],pos[MAXN],visit[MAXN];int cmp(int a,int b){return a<b?1:0;}int min(int a,int b){return a<b?a:b;}void pre_init(){    for(int i=1;i<=n;i++)    {        cir[i].mi=INF;    }}int find(int x){    int l=1,r=n;    while(l<r)    {        int mid=(l+r)>>1;        if(tmp[mid]>=x)        {            r=mid;        }        else l=mid+1;    }    return r;}int main(){    //freopen("cowsort.in","r",stdin);    //freopen("cowsort.out","w",stdout);    scanf("%d",&n);    pre_init();    for(int i=1;i<=n;i++)    {        scanf("%d",&a[i]);        tmp[i]=a[i];    }    sort(tmp+1,tmp+1+n,cmp);    for(int i=1;i<=n;i++)    {        pos[i]=find(a[i]);    }    for(int i=1;i<=n;i++)    {        if(!visit[i]&&a[i]!=tmp[i])        {            visit[i]=1,cir[++cnt].len=1;            cir[cnt].sum+=a[i];            cir[cnt].mi=min(cir[cnt].mi,a[i]);            int t=i;            while(a[pos[t]]!=a[i])            {                t=pos[t];visit[t]=1;                cir[cnt].len++;cir[cnt].sum+=a[t];cir[cnt].mi=min(cir[cnt].mi,a[t]);            }        }    }    for(int i=1;i<=cnt;i++)    {        int t1=cir[i].sum+(cir[i].len-2)*cir[i].mi;        int t2=cir[i].sum+cir[i].mi+(cir[i].len+1)*tmp[1];        ans+=min(t1,t2);    }    printf("%d\n",ans);    return 0;}

3.打砖块

(brike.c/.cpp/.pas)

Description:

在一个凹槽中放置了n层砖块,最上面的一层有n块砖,第二层有n-1块,……,最下面一层仅有一块砖。第i层的砖块从左至右编号为1,2,……,i,第i层的第j块砖有一个价值a[i,j](a[i,j]<=50)。下面是一个有5层砖块的例子:

如果要敲掉第i层的第j块砖的话,若i=1,可以直接敲掉它,若i>1,则必须先敲掉第i-1层的第j和第j+1块砖。
你的任务是从一个有n(n<=50)层的砖块堆中,敲掉(m<=500)块砖,使得被敲掉的这些砖块的价值总和最大。

Input:

输入文件名为(brike.in)。
第一行为两个正整数,分别表示n,m,接下来的第i每行有n-i+1个数据,分别表示a[i,1],a[i,2]……a[i,n – i + 1]。

Output:

输出文件名为(brike.out)。
仅有一个正整数,表示被敲掉砖块的最大价值总和。
4 5
2 2 3 4
8 2 7
2 3
49 19

数据范围与约定

对于20%的数据,满足1≤n≤10,1≤m≤30
对于100%的数据,满足1≤n≤50,1≤m≤500

Solution:

直接粘题解吧:
发现同一行可以被打掉的砖块是可以无规律分布的,但每一列只能打掉从上往下连续的砖块,所以考虑按照每一列DP。
设计状态f[i][j][k]表示打到第i列时、当前这一列打掉从上往下连续j块砖、包括这一列打掉的这一列在内共计打掉了k块砖时,最大的价值总和。对于打掉砖的条件,是这块砖上面所有的砖都被打掉、以及前一列在这块砖上面的砖也都被打掉,前者在状态中已经表示了,后者可以在转移时加以限制。
对于计算每一列前j块砖的价值总和,前缀和处理后就可以O(1)得到了。
状态转移方程:f[i][j][k]=Max(f[i-1][p][k-j]+sum[i][j]),j-1≤p≤i-1,其中sum[i][j]表示第i列打掉前j块砖的价值总和。

Code:

#include <stdio.h>#include <string.h>int n,m,ans=-1;int a[60][60],sum[60][60],s[50],f[60][60][550];int max(int a,int b){return a>b?a:b;} //f[i][j][k]打到第i列时,从上往下打掉了j块砖,共计打了k块砖的最大价值 //sum[i][j] 表示第i行打掉前j块砖的价值 int main(){    //freopen("brike.in","r",stdin);    //freopen("brike.out","w",stdout);    scanf("%d%d",&n,&m);    for(int i=1;i<=n;i++)    {        for(int j=i;j<=n;j++)        {            scanf("%d",&a[i][j]);            sum[i][j]=sum[i-1][j]+a[i][j];        }    }    for(int i=1;i<=n;i++)    {        s[i]=s[i-1]+i;    }     for(int i=1;i<=n;i++)    {        for(int j=0;j<=i;j++)        {            for(int p=max(j-1,0);p<=i-1;p++)            {                for(int k=j+s[p];k<=m;k++)                {                    f[i][j][k]=max(f[i][j][k],f[i-1][p][k-j]+sum[j][i]);                    ans=max(ans,f[i][j][m]);                }            }        }    }    printf("%d\n",ans);    return 0;}
0 0
原创粉丝点击