1.ksum(ksum)
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
【问题描述】 Peter喜欢玩数组。NOIP这天,他从Jason手里得到了大小为n的一个正整数 数组。 Peter求出了这个数组的所有子段和,并将这n(n+1)/2个数降序排序,他想 知道前k个数是什么。 【输入格式】 输入文件名为 ksum.in。 输入数据的第一行包含两个整数 n 和 k。 接下来一行包含 n 个正整数,代表数组。 【输出格式】 输出文件名为 ksum.out。 输出 k 个数,代表降序之后的前 k 个数,用空格隔开。 【输入输出样例】 ksum.in 3 4 1 3 4 3 3 10 2 7 ksum.out 8 7 4 4 19 12 10 【数据规模与约定】 对于所有数据,满足 ai≤10^9,k≤n(n+1)/2,n≤100000,k≤100000 测试点编号 n≤ k≤ 1 100 5000 2 500 100000 3 1000 80000 4 1000 100000 5 10000 50000 6 20000 80000 7 50000 80000 8 100000 80000 9 100000 100000 10 100000 100000
tag:贪心
思路:每一个数都是正整数,意味着子段的大小一定小于父段,那我们必须先选择父段再选择子段。于是我们从整个数组1~n开始贪心,将每次选择的子段l~r再分成l+1~r和l~r-1两个更小的子段放入优先队列。但是,有可能会选择到重复的子段,我的处理是将左右端点哈希。记得求前缀和。
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #include<cmath> #include<vector> #include<queue> #include<map> #define maxn 100010 #define ll long long using namespace std; map<ll,int> Hash; ll sum[maxn],ans[maxn]; int a[maxn],n,k,tot; struct NUM { int l,r; ll val; bool operator<(const NUM &x)const{return val<x.val;} }; priority_queue<NUM>Q; void Push(NUM x) { ll num=1ll*x.l+1ll*x.r*1000000; if(!Hash.count(num)){ Hash[num]=1; Q.push(x); } } int main() { //freopen("ksum.in","r",stdin); //freopen("ksum.out","w",stdout); scanf("%d%d",&n,&k); for(int i=1;i<=n;++i){ scanf("%d",&a[i]); sum[i]=sum[i-1]+a[i]; } NUM x; x.l=1,x.r=n,x.val=sum[n]; Push(x); while(1){ NUM now=Q.top(); Q.pop(); ans[++tot]=now.val; if(tot==k) break; int l=now.l,r=now.r; NUM x,y; x.l=l+1,x.r=r; y.l=l,y.r=r-1; x.val=sum[x.r]-sum[x.l-1]; y.val=sum[y.r]-sum[y.l-1]; Push(x); Push(y); } for(int i=1;i<k;++i) cout<<ans[i]<<" "; cout<<ans[k]<<endl; return 0; }
2.奇袭(raid)
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
【问题描述】 由于各种原因,桐人现在被困在Under World(以下简称UW)中,而UW马上 要迎来最终的压力测试——魔界入侵。 唯一一个神一般存在的Administrator被消灭了,靠原本的整合骑士的力量 是远远不够的。所以爱丽丝动员了UW全体人民,与整合骑士一起抗击魔族。 在UW的驻地可以隐约看见魔族军队的大本营。整合骑士们打算在魔族入侵 前发动一次奇袭,袭击魔族大本营! 为了降低风险,爱丽丝找到了你,一名优秀斥候,希望你能在奇袭前对魔 族大本营进行侦查,并计算出袭击的难度。 经过侦查,你绘制出了魔族大本营的地图,然后发现,魔族大本营是一个N ×N的网格图,一共有N支军队驻扎在一些网格中(不会有两只军队驻扎在一起)。 在大本营中,每有一个k×k(1≤k≤N)的子网格图包含恰好k支军队,我们 袭击的难度就会增加1点。 现在请你根据绘制出的地图,告诉爱丽丝这次的袭击行动难度有多大。 【输入格式】 第一行,一个正整数N,表示网格图的大小以及军队数量。 接下来N行,每行两个整数,Xi,Yi,表示第i支军队的坐标。 保证每一行和每一列都恰有一只军队,即每一个Xi和每一个Yi都是不一样 的。 【输出格式】 一行,一个整数表示袭击的难度。 【输入输出样例】 raid.in 5 1 1 3 2 2 4 5 5 4 3 raid.out 10 【样例解释】 显然,分别以(2,2)和(4,4)为左上,右下顶点的一个子网格图中有3支军队, 这为我们的难度贡献了1点。 类似的子网格图在原图中能找出10个。 【数据范围】 对于30%的数据,N ≤ 100 对于60%的数据,N ≤ 5000 对于100%的数据,N ≤ 50000
tag:分治、单调栈、玄学(雾)
思路:暴力可以二维前缀和或树状数组。这道题的模型有一定特殊性,每行每列都只有一个点,为了达到O(nlogn)的水平我们缩成一维。通过仔细观察发现,每一个符合条件的子段(子矩阵)都满足maxx-minx=len-1=r-l,原来是RMQ问题。
之后是玄学分治+单调栈,对于每个分治区间,用mid分成左右两侧,有四种情况,最小值和最大值在左左,左右,右左,右右,如果把子段反转,可以把右左和右右变成左右和左左,最后需要讨论两种情况,我们始终抓住函数的单调性来考虑,先以mid为分界线求出“前后缀最值”(min单减 ,max单增),既然要快速的查找,将公式移项,maxx-r=minx-l,我们造一个桶,将maxx-r放进去,让minx-l去找它对应的桶(建议结合代码理解)。接着是求“左左”的情况,为什么要满足j>mid?(注意,下面的几行我想了2h)像(1)、(1,2,3)这样的序列虽然满足条件,但不符合j>mid,他们会在分治的时候被解决,真正需要计算的是(1,5,2,3,4)这样,mid在2那里,1+(5-1)=5,1和5需要中间的2,3,4来补充,那么我们发现,所谓“左左”的情况并不是整个区间都在左哦。
接下来就是重头戏“左右”,也就是最小值在左最大值在右,之前求的前后缀最值就派上用场了,以最左端为标准求出满足条件的区间,最大值向右单增,也就是说越往右越大,越容易满足Rmax>Lmax,最小值相反(有些细节写在注释里了),遍历左区间,不断修改原来的范围,一一对应。
数组反转用stl的reserve,细节——(1,2,3,4,5)的mid在3,左(1,2,3),右(4,5),反转后(5,4,3,2,1)mid应该移到4的位置,此时左(5,4),右(3,2,1)。处理了另外两种情况。
1 #include<cstdio> 2 #include<iostream> 3 #include<algorithm> 4 #include<cmath> 5 #include<stack> 6 #define maxn 100010 7 #define inf 1<<29 8 #define ll long long 9 using namespace std; 10 int Lmax[maxn],Lmin[maxn],Rmax[maxn],Rmin[maxn],tong[maxn],a[maxn],n; 11 ll cal(int l,int r,int mid) 12 { 13 ll ret(0); 14 Lmax[mid]=Lmin[mid]=a[mid]; 15 Rmax[mid+1]=Rmin[mid+1]=a[mid+1]; 16 for(int i=mid-1;i>=l;--i){ 17 Lmax[i]=max(Lmax[i+1],a[i]); 18 Lmin[i]=min(Lmin[i+1],a[i]); 19 } 20 for(int i=mid+2;i<=r;++i){ 21 Rmax[i]=max(Rmax[i-1],a[i]); 22 Rmin[i]=min(Rmin[i-1],a[i]); 23 } 24 for(int i=l;i<=mid;++i){ 25 int j=i+Lmax[i]-Lmin[i]; 26 if(j>mid&&Rmax[j]<Lmax[i]&&Rmin[j]>Lmin[i]) ret++;//最大值和最小值都在左边 由于j可能比n还大就导致数组需要开2倍 27 } 28 int p1=mid+1,p2=mid;//p1 max指针 p2 min指针 29 while(p1<=r&&Rmax[p1]<Lmax[l]) tong[Rmax[p1]-p1]--,p1++;//左不符合 右符合 最后指到第一个符合的 30 while(p2<r&&Rmin[p2+1]>Lmin[l]) p2++,tong[Rmax[p2]-p2]++;//左符合 右不符合 最后指到最后一个不符合的 31 for(int i=l;i<=mid;++i){ 32 while(p1>mid+1&&Rmax[p1-1]>Lmax[i]) p1--,tong[Rmax[p1]-p1]++;//将原来不符合变为符合 33 while(p2>mid&&Rmin[p2]<Lmin[i]) tong[Rmax[p2]-p2]--,p2--;//将符合变为不符合 34 ret+=max(tong[Lmin[i]-i],0);//万一是负的 35 } 36 for(int i=mid+1;i<=r;++i) tong[Rmax[i]-i]=0;//区间清零 不要脑子一热memset了 37 return ret; 38 } 39 ll solve(int l,int r) 40 { 41 ll ret(0); 42 if(l==r) return 1ll; 43 int mid=(l+r)>>1; 44 ret=solve(l,mid)+solve(mid+1,r); 45 ret+=cal(l,r,mid); 46 reverse(a+l,a+r+1);//区间反转 47 if((r-l+1)&1) mid--;//mid位置修正 48 ret+=cal(l,r,mid); 49 reverse(a+l,a+r+1); 50 return ret; 51 } 52 int main() 53 { 54 //freopen("raid.in","r",stdin); 55 //freopen("raid.out","w",stdout); 56 int x,y; 57 scanf("%d",&n); 58 for(int i=1;i<=n;++i){ 59 scanf("%d%d",&x,&y); 60 a[x]=y;//转一维 61 } 62 printf("%I64d\n",solve(1,n)); 63 return 0; 64 }
3.十五数码(fifteen)
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
【题目描述】 给出起始顺序,要求通过 0 的移动(与上下左右交换),排成以下顺序: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 0 【输入格式】 从文件 fiften.in 中读入数据,四个数一行,共四行。 【输出格式】 输出到文件 fifteen.out 中。 输出最少移动次数。如果无解输出 No。 【样例 1 输入】 1 2 3 4 5 6 7 8 9 10 11 12 13 14 0 15 【样例 1 输出】 1 【样例 2 输入】 1 11 3 8 5 7 0 2 9 13 4 12 6 10 14 15 【样例 2 输出】 33 【数据范围】 对于 20%的数据,保证有解并且 Ans <= 12; 对于 50%的数据,保证有解并且 Ans <= 28; 存在 10%的数据无解。 对于 100%的数据,如果有解,Ans <= 50;
tag:搜索
思路:状态很多,数字很多,哈希很困难。可以用双向广搜,然后正解是一堆位运算的IDA*,因为有点想法我尽量说自己的思路……IDA*嘛,我做了一些估价,初始状态到末状态每个数字的“曼哈顿距离”(坐标距),加起来就是最少要移动的次数(重要剪枝!),作为我们枚举deep的起点,终点是50,到50可以不做直接输出结果(但是没骗到分)。
dfs的过程是我们需要重点研究的。估价分单个数字和整体(感觉整体的更重要),简要来说,如果明显无法在剩余步数找到答案就不动了。有int全部套register,有if的尽量减少,有函数的尽量不用(重要剪枝!亲测swap能直接拖1s),小函数define掉(不过据说考试的时候不太保险?),还有个“手刀保护sdbh”避免来回移动(重要剪枝!从4^x到3^x的变化)。还有dalao教的exit(0)直接结束程序,biao脸的卡时还特判最后才A掉……
无解的情况需要求逆序数+空位到(4,4)的坐标距(因为0还要移过去啊……另外之后提到的逆序数包括空位的情况),那么问题来了为什么要求逆序数,我们看末状态的逆序数是15,一个奇数,而每次移动无论横纵的改变量都是偶数,奇+-偶=奇,初状态也必须是奇数才行。
1 #include<cstdio> 2 #include<cstring> 3 #include<iostream> 4 #include<algorithm> 5 #include<cmath> 6 #include<ctime> 7 #define aabs(x) (x>=0?x:-(x)) 8 #define ok(x,y) x>0&&x<5&&y>0&&y<5 9 #define ll long long 10 using namespace std; 11 int dx[]={0,1,0,-1},dy[]={1,0,-1,0},mp[5][5],zb[20][2],dis[20],deep,S,T,tot,start,sum; 12 int cal(register int x,register int y){return (x-1)*4+y;} 13 void dfs(register int f,register int DIS,register int x,register int y,register int sdbh) 14 { 15 if(clock()-start>=1980){ 16 if(deep<48) printf("48"); 17 else printf("50"); 18 exit(0); 19 } 20 if(f==deep){ 21 if(!DIS){ 22 printf("%d\n",deep); 23 exit(0); 24 } 25 return; 26 } 27 for(register int i=0;i<4;++i){ 28 register int X=dx[i]+x,Y=dy[i]+y,num=mp[X][Y]; 29 if(num!=sdbh&&ok(X,Y)&&deep-f>=dis[num]){ 30 mp[x][y]=num; 31 mp[X][Y]=0; 32 register int change=aabs(x-zb[num][0])+aabs(y-zb[num][1])-aabs(X-zb[num][0])-aabs(Y-zb[num][1]); 33 dis[num]+=change; 34 if(DIS+change<=deep-f-1) dfs(f+1,DIS+change,X,Y,num); 35 dis[num]-=change; 36 mp[x][y]=0; 37 mp[X][Y]=num; 38 } 39 } 40 } 41 int main() 42 { 43 //freopen("fifteen.in","r",stdin); 44 //freopen("fifteen.out","w",stdout); 45 for(int i=1;i<=4;++i) 46 for(int j=1;j<=4;++j) 47 zb[cal(i,j)][0]=i,zb[cal(i,j)][1]=j; 48 for(int i=1;i<=4;++i) 49 for(int j=1;j<=4;++j){ 50 scanf("%d",&mp[i][j]); 51 if(!mp[i][j]) S=i,T=j; 52 else{ 53 dis[mp[i][j]]=aabs(i-zb[mp[i][j]][0])+aabs(j-zb[mp[i][j]][1]); 54 tot+=dis[mp[i][j]]; 55 } 56 } 57 start=clock(); 58 deep=tot; 59 for(int i=1;i<=4;++i) 60 for(int j=1;j<=4;++j) 61 for(int k=1;k<=4;++k) 62 for(int l=1;l<=4;++l) 63 if(cal(k,l)<cal(i,j)) 64 if(mp[k][l]>mp[i][j]) sum++; 65 sum+=4-S+4-T; 66 if(!(sum&1)){ 67 puts("No"); 68 return 0; 69 } 70 while(1){ 71 if(deep==50){ 72 printf("50"); 73 return 0; 74 } 75 dfs(0,tot,S,T,0); 76 deep++; 77 } 78 return 0; 79 }
——————————并不华丽的分割线————————————
芒果君:这次考试好像没翻车然后刚才CF炸了……所以说自己不努力能怪谁呢……