NOIP模拟21题解

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 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;
}

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

时间: 2024-10-13 13:06:53

NOIP模拟21题解的相关文章

NOIP模拟 8-21 题解

一份模拟题...水得要命真是没谁了. 减法 (sub.c/.cpp) 题目描述 东东在幼儿园刚刚学会了 20 以内的减法,就迫不及待的跑回家要给爸爸出题了.问“10-1”来考东东爸,东东爸想也没想就说是“1”.东东顿时喜笑颜开,臭爸爸连这都不知道.那你知道为啥么?嘻嘻,当然这不是题目了. 这回换东东爸出题了: 两个整数 A 和 B,问 A-B 的结果是多少. 其中 1<=A<=101000, 0<=B<=A. 东东一脸懵圈,说:“臭爸爸,我不会,你告诉我结果吧.“ 这回轮到东东爸懵

NOIP模拟&#183;20141105题解

[A.韩信点兵] 结论题+模板题,用到了中国剩余定理,维基百科上讲的就比较详细,这里就不再赘述了…… 对于这题,我们先利用中国剩余定理($x \equiv \sum{a_i \times m_i (m_i^{-1} \mod p_i)}\, \mod (\prod{p_i})$)找到当前人数的最小可行解$x_0$,(如果$x_0$已经超过了$N$,直接输出无解即可)这时不难证明,对于任何一个可行解,都有 $$x_i = x_0 + k \times \prod{P_i},k \in \mathb

省选模拟21 题解

A. 灯 容易发现问题是连通块数,因为原图是树,可以用点数(为$1$的点)减边数(连接相邻的为$1$的点的边)表示. 点数是易于维护的,所以问题是维护边数. 考虑一个情况,每种颜色的出现次数都很少,那么可以直接在原序列上暴力. 另一种情况,每种颜色的出现次数都很多,并且颜色总数不多. 那么可以预处理出颜色两两之间的贡献,维护当前为$1$的颜色,每次暴力扫每种颜色,来得到当前获得的边数. 结合两种情况,考虑根号分治. 问题还有小颜色与大颜色之间的边的贡献. 这个顺便统计就好了,一个常用的套路是提前

NOIP模拟17.9.21

NOIP模拟17.9.21 1 任务安排manage.in/.out/.cpp1.1 问题描述你有N 个工作,同一时刻只能做一个任务, 其中每个工作有其所需时间, 及完成的Deadline(截止时间), 问要完成所有工作, 最迟要从什么时候开始.你最早可以从时间0 开始工作.1.2 输入格式第一行一个整数N,表示任务数量接下来n 行,每行两个整数,Ti; Si,分别表示该任务的持续时间和截止时间.1.3 输出格式输出一个整数,表示最晚的开始时间,如果不能完成,输出-1.1.4 样例输入43 58

noip模拟测试21

T1:折纸 这道写崩我也是没话说…… 模拟就完了,记录每次的折叠点,每次将之前的都扫一遍就完了 1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<cmath> 5 #include<algorithm> 6 #include<cstdlib> 7 #define ll long long 8 using namespace std; 9 con

liu_runda 给辣鸡蒟蒻做的 NOIP模拟赛 1.0 第二题 任(duty) 题解

问题 B: 任(duty) 时间限制: 2 Sec  内存限制: 512 MB 题目描述 liu_runda退役之后就失去梦想开始咸鱼生活了- Bilibili夏日画板活动中,所有人都可以在一块画板上进行像素画创作.UOJ群有一群无聊的人决定在画板上创作一个50*50的UOJ的LOGO.如下图. 这块画板实际上是很大的矩形网格.一个网格是一像素. 一个人每三分钟才能画一个像素.所以liu_runda的咸鱼生活非常无聊. 郭神表示他实在是看不下去liu_rudna这只颓狗了,于是随手出了一道神题,

NOIP模拟17.8.17

NOIP模拟17.8.17 A 小 G 的字符串文件名 输入文件 输出文件 时间限制 空间限制str.pas/c/cpp str.in str.out 1s 128MB[题目描述]有一天,小 L 给小 G 出了这样一道题:生成一个长度为 n 的.全由小写英文字母构成的字符串,只能使用 k 种字母.要求满足:• 字符串中相邻的两个字母不能相同.• 必须出现恰好 k 种不同的字母.这样的合法字符串可能有很多,小 L 让小 G 输出字典序最小的那个.小 G 太笨啦,不会做这道题,希望你帮帮他.[输入格

NOIP模拟 17.8.18

NOIP模拟17.8.18 A.小菜一碟的背包[题目描述]Blice和阿强巴是好朋友但萌萌哒Blice不擅长数学,所以阿强巴给了她一些奶牛做练习阿强巴有 n头奶牛,每头奶牛每天可以产一定量的奶,同时也需要一定量的草作为饲料对于第 i头奶牛来说,它每天可以产 vi升的奶,同时需要 wi千克的草作为饲料现在来自蚯蚓国的九条可怜想借一些奶牛,使借走的这些奶牛每天的总产奶量最大,但九条可怜很穷,每天最多只能提供W千克的草作为饲料,而且她还需要对付跳蚤国的神刀手,所以她把这个问题交给了阿强巴,不不不……阿

NOIP模拟17.9.22

NOIP模拟17.9.22 前进![问题描述]数轴的原点上有一只青蛙.青蛙要跳到数轴上≥ ??的位置去,但很不幸数轴上有??个区间是禁区,不能进入.青蛙会选择一个长度??,从原点开始每次向右跳长度为??的一段.一路上青蛙会停的位置是0, ??, 2??,…直到跳到了≥ ??的位置,任意一个位置都不能在禁区中.请求出??的最小值,注意??可以是实数.[输入格式]输入文件为susume.in.输入文件的第一行包含两个整数??和??,含义如问题描述中所述.接下来??行,每行描述一个禁区.每行有两个整数