1.ksum
【问题描述】Peter喜欢玩数组。NOIP这天,他从Jason手里得到了大小为n的一个正整数数组。Peter求出了这个数组的所有子段和,并将这n(n+1)/2个数降序排序,他想知道前k个数是什么。【输入格式】输入文件名为 ksum.in。输入数据的第一行包含两个整数 n 和 k。接下来一行包含 n 个正整数,代表数组。【输出格式】输出文件名为 ksum.out。输出 k 个数,代表降序之后的前 k 个数,用空格隔开。【输入输出样例】ksum.in 3 41 3 4ksum.out8 7 4 4ksum.in 3 310 2 7ksum.out19 12 10【数据规模与约定】对于所有数据,满足 ai≤109 k≤n(n+1)/2,n≤100000,k≤100000测试点编号 n ≤ k ≤1 100 50002 500 1000003 1000 800004 1000 1000005 10000 500006 20000 800007 50000 800008 100000 800009 100000 10000010 100000 100000
题解:算是个模拟题吧。最大字段肯定是整个数列。进行如下操作:取出当前最大子段,然后放入这个子段的左端-1和其右端-1两个字段(这两段肯定是它之内比它小的最大子段了)重复直到结束。脑补一下就可以出来,或者手推。这里用到一个叫set的东西非常好,因为可能从两个子段同时得到同一个小子段,所以会有重复情况,为了避免这个情况,set就行。set集合其实就相当于自动去重的优先队列,自动避免了上述情况。更多set的东西可以百度。对了,结构体用set需要用重载运算符重载"<"。
代码:
#include#include #include #include using namespace std;#define ll long longstruct num{ ll l,r,val; bool operator<(const num &b)const{ num a=*this; if(a.val!=b.val) return a.val 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
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 51 13 22 45 54 3raid.out10【样例解释】显然,分别以(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#include #include #include #include using namespace std ;#define N 600000 + 10typedef 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] lmin[i]&&j>m){ ret++; } } int p=m+1,q=m; while(q lmin[l])q++,tax[rmax[q]-q]++; while(p<=r&&rmax[p] m+1&&rmax[p-1]>lmax[i])p--,tax[rmax[p]-p]++; while(q>m&&rmin[q] >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 45 6 7 89 10 11 1213 14 15 0【输入格式】从文件 fiften.in 中读入数据,四个数一行,共四行。【输出格式】输出到文件 fifteen.out 中。输出最少移动次数。如果无解输出 No。【样例 1 输入】1 2 3 45 6 7 89 10 11 1213 14 0 15【样例 1 输出】1【样例 2 输入】1 11 3 85 7 0 29 13 4 126 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#include #include 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);}