17.8.12第六次测试
1.ksum
【问题描述】 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 ksum.out 8 7 4 4 ksum.in 3 3 10 2 7 ksum.out 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
题解:算是个模拟题吧。最大字段肯定是整个数列。进行如下操作:取出当前最大子段,然后放入这个子段的左端-1和其右端-1两个字段(这两段肯定是它之内比它小的最大子段了)重复直到结束。脑补一下就可以出来,或者手推。这里用到一个叫set的东西非常好,因为可能从两个子段同时得到同一个小子段,所以会有重复情况,为了避免这个情况,set就行。set集合其实就相当于自动去重的优先队列,自动避免了上述情况。更多set的东西可以百度。对了,结构体用set需要用重载运算符重载"<"。
代码:
#include<cstdio> #include<cstring> #include<algorithm> #include<set> using namespace std; #define ll long long struct num{ ll l,r,val; bool operator<(const num &b)const{ num a=*this; if(a.val!=b.val) return a.val<b.val; if(a.l != b.l) return a.l < b.l; return a.r < b.r; } }e[1000100]; set<num>q; long long a[100010],sum,cnt; int n,k,t; 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("%I64d",&a[i]); sum+=a[i]; } e[++cnt].l=1; e[cnt].r=n; e[cnt].val=sum; q.insert(e[1]); while(k--){ num x=*q.rbegin();q.erase(x); printf("%I64d ",x.val); if(x.l<x.r){ e[++cnt].l=x.l+1; e[cnt].r=x.r; e[cnt].val=x.val-a[x.l]; q.insert(e[cnt]); e[++cnt].l=x.l; e[cnt].r=x.r-1; e[cnt].val=x.val-a[x.r]; q.insert(e[cnt]); } } return 0; }
2.奇袭
【问题描述】 由于各种原因,桐人现在被困在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
题解:二维压一维后,题目可化简为:给定 N 个数的一个排列,问这个序列中有多少个子区间的数恰好是连续的。这个画图可得。
进一步可以化为:有多少种情况使得,相邻的 k 个数中最大值和最小值的差小于等于 k-1。
大致有两种解法,一种是分治,一种是线段树。
这里主要讲一下分治的解法。
考虑分治,对于当前分治区间[L,R],记区间中点为 mid。当
前区间的答案就是Ans[L..mid]+Ans[mid+1..R]+跨过中点的合
法区间数,然后就分为两种情况了:
1.最小值和最大值在同侧。
2.最小值和最大值在异侧。
下面只考虑最值同在左,和最小值在左,最大值在右的情况。
其余两种是对称的。
对于最值同在左侧的情况,我们枚举左边界在哪,然后可以计算出右边界的位置,在判断是否合法,统计答案。
时间复杂度:
O(N).对于最小值在左侧,最大值在右侧的情况,如果一个区间满足我们所要求的关系的话话,就一定有: max(a[mid 1]...a[right]) min(a[left]...a[mid]) = right -left
移项可得
max(a[mid 1]...a[right]) -right = min(a[left]..a[mid])-left然后可以用单调栈+桶来完成这个任务。时间复杂度:O(N). 如果加一些黑科技可以大大减少代码量,但是复杂度会多一 个 log。 总的时间复杂度:O(NlogN)/O(NlogN^2) 简单提一下,线段树解法的思路大致也是维护一个单调栈, 然后进行区间修改和查询,统计答案。 时间复杂度:O(NlogN).
代码:
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> using namespace std ; #define N 600000 + 10 typedef long long ll ; int TAX[N] ; int A[N] ,lmax[N] , lmin[N] , rmax[N] , rmin[N] ,a,b; int *tax = TAX + 300005 ; int n ; ll cal(int l,int r,int m){ ll ret=0; lmax[m]=lmin[m]=A[m]; rmax[m+1]=rmin[m+1]=A[m+1]; for(int i=m-1;i>=l;i--){ lmax[i] = max( lmax[i+1] , A[i] ) ; lmin[i] = min( lmin[i+1] , A[i] ) ; } for(int i=m+2;i<=r;i++){ rmax[i] = max( rmax[i-1] , A[i] ) ; rmin[i] = min( rmin[i-1] , A[i] ) ; } for(int i=l;i<=m;i++){ int j=lmax[i]-lmin[i]+i; if(rmax[j]<lmax[i]&&rmin[j]>lmin[i]&&j>m){ ret++; } } int p=m+1,q=m; while(q<r&&rmin[q+1]>lmin[l])q++,tax[rmax[q]-q]++; while(p<=r&&rmax[p]<lmax[l])tax[rmax[p]-p]--,p++; for(int i=l;i<=m;i++){ while(p>m+1&&rmax[p-1]>lmax[i])p--,tax[rmax[p]-p]++; while(q>m&&rmin[q]<lmin[i])tax[rmax[q]-q]--,q--; ret+=max(tax[lmin[i]-i],0); } for (int i = m + 1 ; i <= r ; i ++ ) tax[rmax[i]-i] = 0 ; return ret; } ll solve(int l,int r){ if(l==r)return 1ll; int m=(l+r)>>1; ll ret=0; ret+=solve(l,m); ret+=solve(m+1,r); ret+=cal(l,r,m); reverse (A+l,A+r+1); if((r-l+1)%2)m--; ret+=cal(l,r,m); reverse(A+l,A+r+1); return ret; } int main(){ freopen( "raid.in" , "r" , stdin ) ; freopen( "raid.out" , "w" , stdout ) ; scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d%d",&a,&b); A[a]=b; } printf("%I64d\n",solve(1,n)); return 0; }
3.十五数码
【题目描述】 给出起始顺序,要求通过 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;
题解:丧病的八数码的升级版。然而当时我连八数码都不会打。orz。
就是搜,使劲搜。IdA*算法的搜。
这题时限相当坑,两个点死活过不去,最后加长到三秒了才过orz。
这题的check函数判断无解,就是数列转换为一维后,如果包含0在内计算每一个数之前小于自己的数(含0)的个数之和加上0当前位置到其应在位置的曼哈顿距离为奇数则有解,反之无解。
剩下就是搜索,界限bound为搜索深度,每次更新为大于当前值最小的(step+h)step为当前步数,h是估价函数。
h是当前每个数到其应在位置的曼哈顿距离和。(不含0)
差不多就这些。
代码:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; char str[10]; int xx[17],yy[17],bound,flg; int dx[4] = {0,0,1,-1}; int dy[4] = {1,-1,0,0}; char s[4] ={'l','r','u','d'}; int mm,xq,yq,r; inline int caldis(int aa,int x,int y) { return abs(xx[aa]-x) + abs(yy[aa]-y); } struct node { int pos,mat[20]; char out[100]; int H() { register int ret = 0; for(register int i = 0; i < 15; i ++) ret += abs(xx[mat[i]] - (i>>2)) + abs(yy[mat[i]] - (i&3)); return ret; } bool check() { int tot = 0; for(int i = 0; i < 16; i ++){ if(!mat[i]) continue; for(int j = 0; j < i; j ++) if(mat[j] < mat[i]) tot++; } tot+=mm; if(!(tot&1)) return false; else return true; } void output() { int len =strlen(out); for(int i = len-1; i >=0; i --) putchar(out[i]); } }a; bool valid(int x,int y) { return 0 <= x && x <= 3 && 0 <= y && y <= 3; } bool ok(int aa,int bb) { if(aa > bb) swap (aa,bb); if(aa==0&&bb==1) return false; if(aa==2&&bb==3) return false; return true; } int dfs(register int step,register int h,register int las) { if(step + h > bound) return step + h; if(!h) { flg=1; return step; } register int pos =a.pos; int x = (a.pos>>2),y = (a.pos&3),ret =127; for(int k = 0; k < 4; k ++) { int tx = x + dx[k]; int ty = y + dy[k]; if(!valid(tx,ty)||!ok(k,las)) continue; int tar = (tx<<2) + ty; swap(a.mat[pos],a.mat[tar]); a.pos = tar; int ht = h - caldis(a.mat[pos],tx,ty) + caldis(a.mat[pos],x,y); int tmp = dfs(step+1,ht,k) ; if(flg) return tmp;if(ret>tmp)ret = tmp; swap(a.mat[pos],a.mat[tar]); a.pos = pos; } return ret; } int main() freopen("fifteen.in","r",stdin); freopen("fifteen.out","w",stdout); { for(int i = 0; i < 16; ++i){ scanf("%d",&r); if(r==0) { a.pos = i; xq = (i>>2); yq = (i&3); mm=3-xq+3-yq; } else { a.mat[i] = r; xx[r] = (i>>2); yy[r] = (i&3); } } if(!a.check()) { printf("No\n"); return 0; } for(int i = 0; i < 15; ++i) a.mat[i] = i + 1; a.mat[15] = 0; a.pos = 15; for(bound = a.H(); bound <= 55&&!flg ; bound = dfs(0,a.H(),4) ); if(!flg) { printf("No\n"); return 0; } printf("%d",bound); }