8.22模拟赛(无聊的数列、金字塔、新型计算机)

T1
1、无聊的数列
有一个不下降序列 a1≤a2≤…≤an。bi 定义为数列中有多少个数严格小于 ai,ci 定义为
数列中有多少个数严格大于 ai。
你的秘书把 n 个数对{(bi, ci) | 1≤i≤n}打乱顺序抄写在一张纸上。由于她很粗心,致
使不少数对被抄错了。
你的任务是:改动最少的数对,使得存在与这些数对对应的原数列{ai | 1≤i≤n}。
输入格式:第一行 n(n<=1000),表示 n 个数对
接下来 n 行,每行两个数 b,c。意思和题目描述一样
输出格式:一行,表示要改动的最少数列
Sample Input
第 2 页 共 4 页
5
0 2
0 3
2 1
1 2
4 0
Sample Output
2

题解:
对于输入的每一个数对b,c,就知道某数严格大于b个数,严格小于c个数,那么意味着在区间【b+1,n-c】是连续的几个相等的数,对于每一个数对,我们都能转化成这样的区间,然后计数这个区间内都相等出现了多少次,如果计数超过了此区间长度,那么说明是不合法的,不在计数。然后即,可以转化为01背包,表示此区间选不选,且选的区间是否相互覆盖,dp【i】表示前i位已经成为合法序列的最小代价。转移很简单dp[i]=max(dp[i],dp[j]+sum[j+1][i]);

代码很短:

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<cstring>
using namespace std;
const int maxn=1005;
int dp[maxn],sum[1005][1005],b,c,n;
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%d%d",&b,&c);
        sum[b+1][n-c]++;
        if(sum[b+1][n-c]>((n-c)-(b+1)+1)) sum[b+1][n-c]=(n-c)-(b+1)+1;
    }
    for(int i=1;i<=n;i++)
    for(int j=0;j<i;j++)
    dp[i]=max(dp[i],dp[j]+sum[j+1][i]);
    printf("%d",n-dp[n]);
}

T2
2、金字塔
Jaguar 国王在一场战役大胜后决定建造一个金字塔,一方面作为纪念战争胜利的纪念碑,
同时亦用作埋葬在战斗中阵亡将士们的墓地。该金字塔将建在战场的所在地,有一个 a 列 b 行
的矩形底部。在金字塔的底层内将有一个较小的 c 列 d 行的矩形墓室,用来存放阵亡将士们的
遗体以及他们所用过的武器。
国王的建筑师将该战场分为 m 列 n 行小方格的网格。每个小方格的高度用一个整数表示。
金字塔及其内部的墓室均应覆盖网格的完
整的小方格,而且其边必须与战场的边平
行。金字塔内墓室小方格的高度必须保持
原有的高度不变,但金字塔底部的其余地
面则需要平整,平整的方法是将高度较高
的小方格内的砂土移到较低的小方格内,这样一来,金字塔底部的最后高度将是底部所覆盖的所
有小方格(墓室小方格除外)高度的平均值。建筑师有权选择墓室在金字塔内的位置,但墓室四
周要有至少一小方格厚度的墙围绕。
建筑师希望在战场上找到一块最佳的位置来建造该金字塔及其内部的墓室,使得对于给定大
小的金字塔,其底部的高度尽可能高。
图中所示是一个战场的例子,每个小方格内的整数表示该格所处区域内的高度。灰色部份表
示金字塔的底部,灰色部份所包围的白色部份则表示墓室的位置。该图给出了一个最佳位置。
第 4 页 共 4 页
任务
对于给定尺寸的战场、金字塔和墓室大小以及战场上所有小方格的高度,请编写一程序找出建造
金字塔及其墓室的最佳位置,使得金字塔底部的高度为最高。 限制条件
3 ≤ m ≤ 1000
3 ≤ n ≤ 1000
3 ≤ a ≤ m
3 ≤ b ≤ n
1 ≤ c ≤ a – 2
1 ≤ d ≤ b – 2
所有的高度值都是 1-100 之间的整数。 输入
你的程序必须从文件 pyramid.in 中读入所需数据,其格式如下:
pyramid.in 描述
8 5 5 3 2 1
1 5 10 3 7 1 2 5
6 12 4 4 3 3 1 5
2 4 3 1 6 6 19 8
1 1 1 3 4 2 4 5
6 6 3 3 3 2 2 2
第一行:共有 6 个以空格分开的正整数,它们顺次分别为:m, n, a, b, c
及 d。
随后 n 行:每行有 m 个用空格分开的整数,每行表示一横行小方格的
高度。其中,第一行表示战场小方格最上面的一行(第一行),而最
第 5 页 共 4 页
后一行表示最下面的一行(第 n 行)。每行的 m 个整数依次表示该
行上从第一列开始的小方格的高度。 输出
你的程序必须将结果数据输出到 pyramid.out 的文件中。
pyramid.out 描述
4 1
6 2 第一行:必须输出两个由空格分隔的整数,它们表示金字塔的底部的左
上角位置,其中第一个整数代表列的坐标,而第二个整数则代表行
的坐标。
第二行:必须输出两个由空格分隔的整数,它们表示金字塔内部的墓室
的左上角位置,其中第一个整数表示列 的坐标,而第二个数字则表
示行 的坐标。
注:如果存在多个最优位置,那么你输出其中任意一个均被视为正确。

题解:
本题蛮考验代码能力,与知识记忆程度,像我这种蒟蒻就只能打个暴力,而根本没想到用二维st表优化。。
所以先N2暴力枚举每一个大矩形的左上端点。
然后暴力就会想这个矩形内小矩形的左端点,然后暴力算。
但明显n4会炸。于是会想有没有办法优化小矩形,于是用到2维st表,
st[i][j][k]表示在左上端点为i,j,边长为2的k次方的正方形内,小矩形的最小值。
一遍初始化一边查询即可,查询时注意是行长还是列长计算即可。

AC代码:

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<cstring>
#include<queue>
using namespace std;
const int maxn=1005;
int mp[maxn][maxn],sum[maxn][maxn],st[maxn][maxn][11],lo[maxn],res,x,y,xx,yy,m,n,a,b,c,d;
int cal(int x1,int y1,int x2,int y2)
{
    return sum[x2][y2]-sum[x2][y1-1]-sum[x1-1][y2]+sum[x1-1][y1-1];
}
inline int query(int x1,int y1,int x2,int y2){
    int tmp=min(fabs(x2-x1),fabs(y2-y1));
    int t=lo[tmp],r=(1<<t);
    int ans=2000000000;
    if(fabs(x2-x1)<fabs(y2-y1))
    {
        while(y1+r<=y2){
            ans=min(ans,st[x1][y1][t]);
            ans=min(ans,st[x2-r+1][y1][t]);
            y1+=r;
        }
        ans=min(ans,st[x1][y2-r+1][t]);
        ans=min(ans,st[x2-r+1][y2-r+1][t]);
        return ans;
    }
    else 
    {
        while(x1+r<=x2){
            ans=min(ans,st[x1][y1][t]);
            ans=min(ans,st[x1][y2-r+1][t]);
            x1+=r;
        }
        ans=min(ans,st[x2-r+1][y1][t]);
        ans=min(ans,st[x2-r+1][y2-r+1][t]);
        return ans;
    }
}
int main()
{
    for(int i=2;i<maxn;i++)
    lo[i]=lo[i>>1]+1;
    scanf("%d%d%d%d%d%d",&m,&n,&a,&b,&c,&d);
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    scanf("%d",&mp[i][j]),sum[i][j]=mp[i][j]+sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
    memset(st,0x3f3f3f,sizeof(st));
    for(int i=1;i+d-1<=n;i++)
    for(int j=1;j+c-1<=m;j++)
    st[i][j][0]=cal(i,j,i+d-1,j+c-1);
    for(int k=1;(1<<k)<=min(m,n);k++)
    for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        st[i][j][k]=st[i][j][k-1];
        if(i+(1<<(k-1)<=n))
            st[i][j][k]=min(st[i][j][k],st[i+(1<<(k-1))][j][k-1]);
        if(j+(1<<(k-1))<=m)
            st[i][j][k]=min(st[i][j][k],st[i][j+(1<<(k-1))][k-1]);
        if(i+(1<<(k-1))<=n&&j+(1<<(k-1))<=m)
            st[i][j][k]=min(st[i][j][k],st[i+(1<<(k-1))][j+(1<<(k-1))][k-1]);
    }
    for(int i=1;i+b-1<=n;i++)
    for(int j=1;j+a-1<=m;j++)
    {
        int num=query(i+1,j+1,i+b-1-d,j+a-1-c);
        num=cal(i,j,i+b-1,j+a-1)-num;
        if(num>res)
            res=num,x=i,y=j;
    }
    res=0x3f3f3f3f;
    for(int i=x+1;i<=x+b-1-d;i++)
    for(int j=y+1;j<=y+a-1-c;j++)
    {
        if(st[i][j][0]<res)
            res=st[i][j][0],xx=i,yy=j;
    }
    printf("%d %d\n",y,x);
    printf("%d %d\n",yy,xx);
}

T3
3、新型计算机
T 小朋友正在摆弄着他设计的“计算机”,他认为这台计算机原理很独特,因此利用它可
以解决许多难题。
但是,有一个难题他却解决不了,是这台计算机的输入问题。新型计算机的输入也很独
特,假设输入序列中有一些数字(都是自然数),计算机先读取第一个数字 S1,然后顺序向
后读入 S1 个数字。接着再读一个数字 S2,顺序向后读入 S2个数字……依此类推。不过只有
计算机正好将输入序列中的数字读完,它才能正确处理数据,否则计算机就会进行自毁性操
作!
T 小朋友现在有一串输入序列。但可能不是合法的,也就是可能会对计算机造成破坏。
于是他想对序列中的每一个数字做一些更改,加上一个数或者减去一个数,当然,仍然保持
其为自然数。使得更改后的序列为一个新型计算机可以接受的合法序列。
不过 T 小朋友还希望更改的总代价最小,所谓总代价,就是对序列中每一个数操作的参
数的绝对值之和。
写一个程序:
 从文件中读入原始的输入序列;
 计算将输入序列改变为合法序列需要的最小代价;
 向输出文件打印结果。
数据范围:数列长度 N≤106。
输入格式:
第一行是一个数 n,表示长度为 n
接下来是 n 个数
第 7 页 共 4 页
输出格式:一行,表示最小的合法代价。
样例输入;10
1 1 1 1 1 1 1 1 1 1
样例输出:0

题解:
看完题想到n2暴力dp应该不难,用dp[i]表示前i个全是合法的最小代价,
转移: for(int i=3;i<=n;i++)
for(int j=1;j<=i-1;j++)
dp[i]=min(dp[i],dp[j-1]+abs(i-j-a[j]));

但明显会炸。但被dp限制了思路。
可以将题意转化为最短路模型,对于每一个数字i,将其于i+x+1连边,代价0,从i+x+1向左向右与i连边,距离每次+1,于是题意转化为从1走到n的最短路是多少,这下复杂度就降成nlogn。

#include<iostream>
#include<algorithm>
#include<stdio.h>
#include<cstring> 
#include<queue>
#define ll long long
using namespace std;
const int maxn=1e6+5;
int n,dis[maxn],h[maxn],nxt[maxn*2],to[maxn*2],val[maxn*2],cnt,x;
void add(int u,int v,int w)
{
    to[++cnt]=v;
    nxt[cnt]=h[u];
    val[cnt]=w;
    h[u]=cnt;
}
queue<int> q;
bool vis[maxn],vis1[maxn],vis2[maxn];
void dfs()
{
    memset(dis,0x3f,sizeof(dis));
    memset(vis1,0,sizeof(vis1));
    dis[1]=0,q.push(1);
    while(!q.empty())
    {
        int now=q.front();q.pop();
        for(int i=h[now];i;i=nxt[i])
        if(dis[now]+val[i]<dis[to[i]])
        {
            dis[to[i]]=dis[now]+val[i];
            q.push(to[i]);
        }
    }
}
int main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>x;
        for(int j=i+1;j<=min(i+x+1,n)&&vis[j]==0;j++)
        vis[j]=1,add(j,j-1,1);
        for(int j=i+x+1;j<=n&&!vis2[j];j++) vis2[j]=1,add(j,j+1,1);
        if(i+x+1<=n+1)
        add(i,i+x+1,0);
        else add(i,n+1,i+x-n);
    }
    dfs();
    printf("%d\n",dis[n+1]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值