Noip模拟考试6:解题报告

Peter喜欢玩数组。NOIP这天,他从Jason手里得到了大小为n的一个正整数
数组。
Peter求出了这个数组的所有子段和,并将这n(n+1)/2个数降序排序,他想
知道前k个数是什么。

不难想到,从最大字段和向下更新。

用set实现

介绍一下set:用法与priority_queue基本相同,区别是,set有自动去重,而priority_queue将所有元素放入。

然后通过三元组,分别记录权值,左端与右端。

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<set>
#define st first
#define se second
#define ll long long
#define mp(a,b) make_pair(a,b)
#define pr pair< ll,pair<int,int> > 
using namespace std;
set<pr>s;
ll sum;
int a[100010];
int main()
{
    //freopen("ksum.in","r",stdin);
    //freopen("ksum.out","w",stdout);
    int n,k;
    scanf("%d%d",&n,&k);
    for(int i = 1 ; i <= n ; ++i)
    {
        scanf("%d",&a[i]);
        sum+=a[i];
    }
    s.insert(mp(sum,mp(1,n)));
    while(k--)
    {
        pr now=*s.rbegin();
        printf("%I64d ",now.st);
        s.erase(now);
        int l=now.se.st;
        int r=now.se.se;
        if(l!=r)
        {
            s.insert(mp(now.st-a[l],mp(l+1,r)));
            s.insert(mp(now.st-a[r],mp(l,r-1)));
        }
    }
    return 0;
}

2. . 奇袭
(raid.cpp\c\pas)
【问题描述】
由于各种原因,桐人现在被困在Under World(以下简称UW)中,而UW马上
要迎来最终的压力测试——魔界入侵。
唯一一个神一般存在的Administrator被消灭了,靠原本的整合骑士的力量
是远远不够的。所以爱丽丝动员了UW全体人民,与整合骑士一起抗击魔族。
在UW的驻地可以隐约看见魔族军队的大本营。整合骑士们打算在魔族入侵
前发动一次奇袭,袭击魔族大本营!
为了降低风险,爱丽丝找到了你,一名优秀斥候,希望你能在奇袭前对魔
族大本营进行侦查,并计算出袭击的难度。
经过侦查,你绘制出了魔族大本营的地图,然后发现,魔族大本营是一个N
×N的网格图,一共有N支军队驻扎在一些网格中( 不会有两只军队驻扎在一起)。
在大本营中,每有一个k×k(1≤k≤N)的子网格图包含恰好k支军队,我们
袭击的难度就会增加1点。
现在请你根据绘制出的地图,告诉爱丽丝这次的袭击行动难度有多大。
【输入格式】
第一行,一个正整数N,表示网格图的大小以及军队数量。
接下来N行,每行两个整数,Xi,Yi,表示第i支军队的坐标。
保证每一行和每一列都恰有一只军队, , 即每一个 Xi 和每一个 Yi 都是不一样
的。
【输出格式】
一行,一个整数表示袭击的难度。

样例输入:

5
1 1
3 2
2 4
5 5
4 3

样例输出:

10

解题:

重要信息:保证每一行和每一列都恰有一只军队, , 即每一个 Xi 和每一个 Yi 都是不一样
的。

由此可将二维首先优化至一维。

样例则分别为 :1 4 2 3 5

观察发现,若某一区间最大值减最小值的差等于(这段区间的长度-1)那么该段区间可选。

若左右端点分别为i,j,那么 满足的条件为 i-j+1=max[i,j]-min[i,j]。

分治算法:

在每次solve 中,会出现两种情况:

1、最大值与最小值都出现在同一端(l<x<mid或者mid+1<x<r),只需要预处理max与min.O(n)处理即可

2、最大值与最小值出现不同端,处理起来就比较复杂了。

首先,lmax[i]表示从mid到i点的最大值 ,同理rmax[i]表示从mid+1到i点的最大值

lmin与rmin同理。

分情况考虑:

1、[l,mid],从l到mid 过程中lmax是下降序列,lmin 为上升序列。

2、[mid+1,r] 从mid+1到r 的过程中rmax 是上升序列,rmin 是下降序列。

由上面公式,设最大值出现在右端 则 j-i+1=max[j]-min[i]+1;

-->max[j]-j=min[i]-i;

还有两个条件:max[j]>max[i],min[i]<min[j]。

那么对max[j]-j进行预处理 ,再用min[i]-i进行对应,O(n)处理。

在代码里再进行解释吧。

#include<cstdio>
#include<algorithm>
#include<cstring>
#define ll long long
using namespace std;
const int maxn = 50010;
int TAX[maxn<<1];//设置一个桶 ,用来存放max[j]-j预处理得到的值。
int *tax=&(TAX[maxn]),q[maxn];//max[j]-j会出现负值,所以将数组开到0以下。
int Lmax[maxn],Lmin[maxn],Rmax[maxn],Rmin[maxn];
int maxx = - (1<<30);
int minn = 1 << 30;
ll calc(int l,int r,int mid)
{
    ll ret=0;
    Lmax[mid]=Lmin[mid]=q[mid];
    Rmax[mid+1]=Rmin[mid+1]=q[mid+1];
    for(int i = mid-1 ; i >= l ;--i)
    {
        Lmax[i]=max(Lmax[i+1],q[i]);
        Lmin[i]=min(Lmin[i+1],q[i]);
    }
    for(int i = mid+2 ; i <= r ;++i)
    {
        Rmax[i]=max(Rmax[i-1],q[i]);
        Rmin[i]=min(Rmin[i-1],q[i]);
    }//预处理max和min
    for(int i = l ; i <= mid ; ++i)
    {
        int j=i+Lmax[i]-Lmin[i];
        if(j <= r&&j>mid&&Rmax[j]<Lmax[i]&&Rmin[j]>Lmin[i])ret++;
    }//最大值与最小值出现在同一端的情况
    int q=mid,p=mid+1;
    while(q<r&&Rmin[q+1]>Lmin[l])q++,tax[Rmax[q]-q]++;//如果右端下一个位置仍然大于最左端的min值,那么当前值入桶
    while(p<=r&&Rmax[p]<Lmax[l])tax[Rmax[p]-p]--,p++;//如果当前位置小于最左端max值,那么当前值出桶
    for(int i = l ; i <= mid ; ++i)//通过min[i]-i来对应max[j]-j
    {
        while(q>mid &&Rmin[q]<Lmin[i])tax[Rmax[q]-q]--,q--;//如果当前q位置Min小于i位置min,则该情况不合法,出桶
        while(p>mid+1&&Rmax[p-1]>Lmax[i])p--,tax[Rmax[p]-p]++;//如果下一位置仍然合法,那么将下一位置入桶。
        ret+=max(0,tax[Lmin[i]-i]);
    }//像是一个动态右移的过程,先预处理右端所有情况针对左端最左值得情况,然后通过左端右移,来实现桶的入与出。
    for (int i = mid + 1 ; i <= r ; i ++ )//清空
        tax[Rmax[i]-i] = 0 ;
    return ret;
}
ll Solve(int l,int r)//分治部分
{
    ll ret;
    if(l==r)return 1ll;
    int m=(l+r)>>1;
    ret=Solve(l,m)+Solve(m+1,r);
    ret+=calc(l,r,m);
    reverse(q+l,q+1+r);//将队列反置,下面计算max与min出现在另一端,或者max出现在左端,min出现在右端的情况。
    if((r-l+1)%2)m--;//自己手推一推就明白啦~
    ret+=calc(l,r,m);
    reverse(q+l,q+1+r);
    return ret;
}
int main()
{
    freopen( "raid.in" , "r" , stdin ) ;
    freopen( "raid.out" , "w" , stdout ) ;
    int n,x,y;
    scanf("%d",&n);
    for(int i = 1 ; i <= n ; ++i)
    {
        scanf("%d%d",&x,&y);
        q[x]=y;
    }
    printf("%I64d\n",Solve(1,n));
    return 0;
}

很有意思的一道分治题,不过对于我这种蒟蒻到不行的蒟蒻来说,只能依靠题解苟活才会写。

3.十五数码
(fifteen.cpp/c/pas)

给出起始顺序,要求通过 0 的移动(与上下左右交换),排成以下顺序:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 0

输出最少移动次数。如果无解输出 No。

这个单独介绍一篇吧,看一下这个:

 

posted @ 2017-08-19 21:18  傅judge  阅读(461)  评论(0编辑  收藏  举报