码迷,mamicode.com
首页 > 其他好文 > 详细

NOIP模拟21题解

时间:2015-08-03 14:38:33      阅读:179      评论:0      收藏:0      [点我收藏+]

标签:noip   模拟   题解   

Contents

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 1001000
typedef 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 1001000
typedef 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 1000000
using 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;
}

版权声明:本文为博主原创文章,未经博主允许不得转载。

NOIP模拟21题解

标签:noip   模拟   题解   

原文地址:http://blog.csdn.net/z_mendez/article/details/47206673

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!