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。
这个单独介绍一篇吧,看一下这个: