【jzoj】2018.2.7NOIP普及组——某【B&C】组模拟赛

前言

……终于改完了,像之前小L一样崩溃。今天C组和B组一起做题,所以……


正题


题目1:教主的花园(jzoj1792)

一平面直角坐标系,在x轴的位置建立一堵墙,墙上有n道门,给出门的位置,询问两个坐标的距离(曼哈顿)。

输入输出(建议无视)

Input

输入的第1行为一个正整数N,为屏障上入口的个数。
第2行有N个整数,a1, a2, …, aN,之间用空格隔开,为这N个入口的横坐标。
第3行为一个正整数M,表示了M个询问。
接下来M行,每行4个整数x1, y1, x2, y2,有y1与y2均不等于0,表示了一个询问从(x1, y1)到(x2, y2)的最短路。

Output

输出共包含m行,第i行对于第i个询问输出从(x1, y1)到(x2, y2)的最短路距离是多少。

Sample Input

2
2 -1
2
0 1 0 -1
1 1 2 2

Sample Output

4
2

解题思路

有三种情况
1. 坐标之间不隔墙
2. 坐标之间隔墙,并且最近的墙在两个坐标之间:
草图
3. 坐标之间隔墙,并且最近的墙在两个坐标之间
草图
然后前两种情况之接输出曼哈顿距离,第三种情况输出两个距离门的距离和。
用二分确定最近的门

代码

#include<cstdio>
#include<algorithm>
using namespace std;
int n,a[100001],m,x,y,zx,zy,l,r,mid,last;
bool ok;
int abs(int x)
{
    if (x<0) return 0-x;
    else return x;
}
int main()
{
    scanf("%d",&n);
    for (int i=1;i<=n;i++)
      scanf("%d",&a[i]);
    sort(a+1,a+1+n);//排序
    scanf("%d",&m);
    for (int i=1;i<=m;i++)
    {
        scanf("%d%d%d%d",&x,&y,&zx,&zy);//输如
        if (y<0 && zy>0 || y>0 && zy<0)//如果中间隔墙
        {
            l=1;r=n;last=2147483647;//last记录离门最近距离
            ok=true;
            while (l<=r)
            {
                mid=(int)(l+r)/2;
                if (zx>a[mid] && x<a[mid] || zx<a[mid] && x>a[mid])//如果门在中心
                {
                    l=mid;
                    last=0;
                    break;//退出
                }
                if (min(abs(a[mid]-x),abs(a[mid]-zx))*2<last)//如果找到更近的距离
                {
                    last=min(abs(a[mid]-x),abs(a[mid]-zx))*2;//记录
                    if (a[mid]<x) l=mid;
                    else r=mid;
                }
                else
                  if (a[mid]<x) l=mid+1;
                    else r=mid-1;
            }
            printf("%d\n",last+abs(x-zx)+abs(zy-y));//输出
        }
        else printf("%d\n",abs(zx-x)+abs(zy-y));//输出
    }
}

题目2:教主泡嫦娥(jozj1793)

有一个环形山地,有n个落脚点,可以从任意落脚点任意状态出发。每个落脚点的代价为

当教主从第i个落脚点,走到第j个落脚点的时候(i和j相邻)
j的海拔高于i的海拔:如果教主处于上升状态,教主需要耗费两段高度差的绝对值的体力;否则耗费高度差平方的体力。
j的海拔低于i的海拔:如果教主处于下降状态,教主需要耗费两段高度差的绝对值的体力;否则耗费高度差平方的体力。

求走一圈的最小代价

输入输出(建议无视)

Input

输入的第一行为两个正整数N与M,即落脚点的个数与切换状态所消耗的体力。
接下来一行包含空格隔开的N个正整数,表示了每个落脚点的高度,题目保证了相邻落脚点高度不相同。

Output

输出仅包含一个正整数,即教主走一圈所需消耗的最小体力值。
注意:C++选手建议使用cout输出long long类型整数。

Sample Input

6 7
4 2 6 2 5 6

Sample Output

27

解题思路

如果枚举落脚点肯定会超时,所以我们就不能这么做。
用f[i][j][k]表示在第i个落脚点,状态为j,在当前有没有改变状态。
然后O(n)的时间复杂度轻易过
动态转移方程:
f[i][j][0]=f[i-1][j][0]+abs(a[i]-a[i-1]) 直接走过
f[i][j][1]=min(f[i-1][j][1],min(f[i-1][j^1][0],f[i-1][j^1][1])+m)+abs(a[i]-a[i-1]) 改变状态

f[i][j][0]=f[i-1][j][0]+pows(a[i]-a[i-1])
f[i][j][1]=min(f[i-1][j][1],min(f[i-1][j^1][0],f[i-1][j^1][1])+m)+pows(a[i]-a[i-1])
在这题被逼疯,默默AC不想说话

代码

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
long long f[10001][2][2],ans;
int a[10001],n,m;
long long abs(long long x)
{
    if (x<0) return 0-x;
    else return x;
}
long long pows(long long x)
{return x*x;}
void dp()
{
    f[0][1][1]=f[0][0][1]=1e16-1;
    for (int i=1;i<=n;i++)
      for (int j=0;j<2;j++)
        if ((a[i]<a[i-1])^j)
        {
            f[i][j][0]=f[i-1][j][0]+abs(a[i]-a[i-1]);
            f[i][j][1]=min(f[i-1][j][1],min(f[i-1][j^1][0],f[i-1][j^1][1])+m)+abs(a[i]-a[i-1]);
        }
        else
        {
            f[i][j][0]=f[i-1][j][0]+pows(a[i]-a[i-1]);
            f[i][j][1]=min(f[i-1][j][1],min(f[i-1][j^1][0],f[i-1][j^1][1])+m)+pows(a[i]-a[i-1]);
        }//动态转移
}
int main()
{
    scanf("%d%d",&n,&m);
    for (int i=0;i<n;i++)
    {
        scanf("%d",&a[i]);
    }
    a[n]=a[0];
    memset(f,0ll,sizeof(f));
    dp();//dp一次
    ans=min(min(f[n][0][0],f[n][0][1]),min(f[n][1][0],f[n][1][1]));//取最小情况
    memset(f,0ll,sizeof(f));
    f[0][0][0]=1e16-1;//从f[0][1][0]推过去
    dp();//dp一次
    ans=min(ans,f[n][1][1]-m);//取值
    memset(f,0ll,sizeof(f));
    f[0][1][0]=1e16-1;//从f[0][0][0]推过去
    dp();//dp一次
    ans=min(ans,f[n][0][1]-m);//取值
    cout<<ans<<endl;
}

题目3:保镖排队(jzoj1794)

有n个保镖,除第一个保镖外每个保镖都有一个上司,每个上司的下属都有在上司心目中的武力值。排队要求:

①互为上司-下属的两个保镖,上司在前,下属在后
②对于一个保镖的所有下属,武功实力较强的在前,较弱的在后。

求排队方案数

输入输出(建议无视)

Input

输入的第一行为一个正整数T,表示了数据组数。
对于每组数据:
第一行为一个正整数N。
接下来N行,每行描述一个保镖。
第i+1行,会有一个整数K,代表第i个保镖的下属个数,接下来K个数,代表第i个保镖的下属按照武功实力从高到低的编号。

Output

输出包括C行,每行对于每组数据输出方案数mod 10007后的结果。

Sample Input

2
5
2 2 3
2 4 5
0
0
0
7
2 2 3
2 4 5
2 6 7
0
0
0
0

Sample Output

3
10

解题思路

首先想到的是树形dp,由于保镖一没有上司所以一定是根。然后就是插空问题了,将3个保镖插入到3个空中,用组合的方法求插空方案数。用杨辉三角求组合。然后树形dp

代码

#include<cstdio>
#include<cstring>
using namespace std;
struct line{
    int y,next;
}a[10001];//邻接表
int f[1001],s,ls[1001],n,k,t,w,head,num[1001];
int cost[1002][1002];
void dp(int x)
{
    int q=ls[x];
    f[x]=1;
    if (q==0)
    {
        num[x]=1;
        return;//没有子树
    }
    while (q!=0)
    {
        dp(a[q].y);
        num[x]+=num[a[q].y];//累计
        f[x]=((f[x]*f[a[q].y])%10007)*(cost[num[x]-1][num[a[q].y]-1])%10007;//求方案数
        q=a[q].next;
    }
    num[x]++;//算上自己
}
int main()
{
    cost[0][0]=1;
    for (int i=1;i<=1001;i++)
      for (int j=1;j<=i;j++)
      {
        cost[i][0]=1;
        cost[i][j]=(cost[i-1][j]+cost[i-1][j-1])%10007;
      }//杨辉三角
    scanf("%d",&t);
    for (int ti=1;ti<=t;ti++)
    {
        w=0;s=0;
        memset(ls,0,sizeof(ls));
        memset(f,0,sizeof(f));
        memset(num,0,sizeof(num));//初始化
        scanf("%d",&n);
        for (int i=1;i<=n;i++)
        {
            scanf("%d",&k);
            for(int j=1;j<=k;j++)
            {
                scanf("%d",&a[++w].y);
                a[w].next=ls[i];
                ls[i]=w;
            }
        }
        dp(1);//dp
        printf("%d\n",f[1]);
    }
}

题目4:教主的别墅(jzoj1795)

有N个人,有男有女,分成m段连续的部分,求分割的最小和最大字典序。

输入输出(建议无视)

Input

  输入的第1行为两个正整数N与M,用空格分隔。
  第2行包含一个长度为N的串,仅由字符组成,第i 个字符为0表示在这个位置上的LHXee为女生,若为1则为男生。

Output

  输出文件包含两行,每行M个正整数,正整数之间用空格隔开,行末无多余空格。这M个正整数从左到右描述了你所分的每个组的人数。
  第1行为字典序最小的方案,第2行为字典序最大的方案。

Sample Input

8 3
11001100

Sample Output

1 2 5
5 2 1

解题思路

这道题贪心竟然能过???

深搜TLE,贪心只能过样例

听某dalao讲用前缀和到第i个房间的男女差值(绝对值),然后每个房间最大差值为:

summsumm

然后正推一次反推一次。

#include<cstdio>
#include<iostream>
using namespace std;
int n,m,sum[5000002],ans,anss,t,last,w,a[100002];
char c;
int abs(int x)
{
    if (x<0) return 0-x;
    else return x;
}//绝对值
int main()
{
    scanf("%d%d\n",&n,&m);
    for (int i=1;i<=n;i++)
    {
        cin>>c;
        if (c=='0') sum[i]-=1;
        else sum[i]+=1;
        sum[i]+=sum[i-1];
    }
    ans=abs(sum[n])/m;//求最大差值
    if (abs(sum[n])%m!=0) ans++;//向上取整
    if (sum[n]==0)
    {
        int l=0;
        for (int i=1;i<=n;i++)
          if (sum[i]==0) l++;
        if (l>=m) ans=0;
        else ans=1;//能否做到没有差值
    }
    t=m;last=0;
    for (int i=1;i<=n;i++)
    {
        anss=abs(sum[n]-sum[i])/(t-1);
        if (abs(sum[n]-sum[i])%(t-1)!=0) anss++;
        if (abs(sum[i]-sum[last])<=ans && anss<=ans)
        {
            printf("%d ",i-last);
            last=i;
            t--;
            if (t==1) break;
        }
    }//查找
    printf("%d\n",n-last);
    ans=abs(sum[n])/m;
    if (abs(sum[n])%m!=0) ans++;
    if (sum[n]==0)
    {
        int l=0;
        for (int i=1;i<=n;i++)
          if (sum[i]==0) l++;
        if (l>=m) ans=0;
        else ans=1;
    }
    t=m;last=n;w=0;
    for (int i=n-1;i>=1;i--)
    {
        anss=abs(sum[i])/(t-1);
        if (abs(sum[i])%(t-1)!=0) anss++;
        if (abs(sum[last]-sum[i])<=ans && anss<=ans)
        {
            a[++w]=last-i;
            last=i;
            t--;
            if (t==1) break;
        }
    }//倒着查找
    a[++w]=last;//记录
    for (int i=w;i>=1;i--) printf("%d ",a[i]);
}

转载于:https://www.cnblogs.com/sslwyc/p/9218588.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值