在杭州集训五天了,现在才想起来写博客.....
主要还是因为今天的题目我写出来了,想发篇博客记录下。
第一题其实还挺简单的,只不过当时我认为我的算法天衣无缝,结果只拿到了中间的30分;
下面来说下正解:
O(nlogn)算法:一开始的朴素算法就是开一个二维数组,暴力枚举每一个的情况,但这种方法只限于数组范围小的情况,后面几组测试点的数据都很大,显然这么做是不完美的,仔细思考一番后我们发现:对于行和列,对于一个点,我们只要判断其是否存在在同一行同一列的前驱和后继就好了,我们可以通过双关键字排序做到。
对于行:以x为第一关键字,y为第二关键字排序,判断它前一个点x值是否相同,后一个点x值是否相同。列也一样。
对于斜着的情况,我们也可以用双关键字排序做到,如何修改排序操作,我们要把每一斜线上的点放在一起。
对于左上向右下斜:以x-y为第一关键字,x+y为第二关键字排序.
对于右上向左下斜:以x+y为第一关键字,x-y为第二关键字排序。
做4遍排序,再统计答案。
代码:
#include<bits/stdc++.h> using namespace std; struct knight{ int x,y,num; }k[100005]; int n,m; int wudi[100005]; int num2[9]; bool cmp1(knight a,knight b) { if(a.y==b.y) return a.x<b.x; else return a.y<b.y; } void Solve1() { for(int i=1;i<=m;i++) { if(k[i].y==k[i-1].y) wudi[k[i].num]++; if(k[i].y==k[i+1].y) wudi[k[i].num]++; } } bool cmp2(knight a,knight b) { if(a.x==b.x) return a.y<b.y; else return a.x<b.x; } void Solve2() { for(int i=1;i<=m;i++) { if(k[i].x==k[i-1].x) wudi[k[i].num]++; if(k[i].x==k[i+1].x) wudi[k[i].num]++; } } bool cmp3(knight a,knight b) { if(a.x+a.y==b.x+b.y) return a.x-a.y<b.x-b.y; else return a.x+a.y<b.x+b.y; } void Solve3() { for(int i=1;i<=m;i++) { if(k[i].x+k[i].y==k[i-1].x+k[i-1].y) wudi[k[i].num]++; if(k[i].x+k[i].y==k[i+1].x+k[i+1].y) wudi[k[i].num]++; } } bool cmp4(knight a,knight b) { if(a.x-a.y==b.x-b.y) return a.x+a.y<b.x+b.y; else return a.x-a.y<b.x-b.y; } void Solve4() { for(int i=1;i<=m;i++) { if(k[i].x-k[i].y==k[i-1].x-k[i-1].y) wudi[k[i].num]++; if(k[i].x-k[i].y==k[i+1].x-k[i+1].y) wudi[k[i].num]++; } } int main(){ scanf("%d %d",&n,&m); for(int i=1;i<=m;i++) { cin>>k[i].x>>k[i].y; k[i].num=i; } sort(k+1,k+1+m,cmp1); Solve1(); sort(k+1,k+1+m,cmp2); Solve2(); sort(k+1,k+1+m,cmp3); Solve3(); sort(k+1,k+1+m,cmp4); Solve4(); for(int i=1;i<=m;i++) num2[wudi[i]]++; for(int i=0;i<=8;i++) cout<<num2[i]<<" "; return 0; }
这份代码当初我写的时候一直爆零,后来发现是n和m搞混了....
第二题这个题面和那张图真是让人无力吐槽啊.....
考试的时候我可能有点脑残了,竟然没想到可以用DP写,我只看见了那个的30%的数据,
显然可知的情况说明整个序列是降序的,此时把除了最大的那个点即第一个点之外的点都
移一遍即可。
当∑n<=1000时,我们考虑简化题意,我们可以得到 :最优解不可能把同一个数字移动 次及以上。既然我们可以一次把它移到正确的位置上,那么这个
数字就可以理解为直接被移走了。
那么问题就变成了:删除几个数字,使得数列单调递增,所求的是移走数字的最小和。 使得删除的和最小,就意味
着留下的和最大。
那么问题就变成了:保留几个单调递增的数字,使得和最大。
这个直接O(n²)DP就可以解决
而对于100%的数据,我们考虑优化O(n²)的DP;发现前面更新f[i]时
当j<i&&a[j]≤a[i]时f[j]可以转移到f[i]
用线段树维护一个前缀max,每做完一个i,将f[i]加入线段树。
时间复杂度O(nlogn)。
代码:
#include<bits/stdc++.h> using namespace std; int T,n; int ji[1000000+10]; bool flag,hhh; long long uuu,sum,cnt; long long b[1000000+10],c[1000000+10]; map<int,int>q; long long f; struct super_tree{ int l,r; long long sum; }t[4000000+10]; long long rd() { long long a=0; char s; s=getchar(); while(s<'0'||s>'9') s=getchar(); while(s>='0'&&s<='9') { a=a*10+s-'0'; s=getchar(); } return a; } void build(int p,int l,int r) { t[p].l=l; t[p].r=r; t[p].sum=0; if(l==r) return; int mid=(l+r)>>1; build(p*2,l,mid); build(p*2+1,mid+1,r); } void change(int p,int l,int r,long long v) { if(t[p].l>=l&&t[p].r<=r) { t[p].sum=max(v,t[p].sum); return; } int mid=(t[p].l+t[p].r)>>1; if(mid>=l) change(p*2,l,r,v); if(mid<r) change(p*2+1,l,r,v); t[p].sum=max(t[p*2].sum,t[p*2+1].sum); } long long query(int p,int l,int r) { if(t[p].l>=l&&t[p].r<=r) { return t[p].sum; } long long ans=0; int mid=(t[p].l+t[p].r)>>1; if(mid>=l) ans=max(ans,query(p*2,l,r)); if(mid<r) ans=max(ans,query(p*2+1,l,r)); return ans; } void Solve(){ n=rd(); for(int i=1;i<=n;i++) { ji[i]=rd(); b[i]=ji[i]; sum+=ji[i]; } cnt=0; sort(b+1,b+1+n); for(int i=1;i<=n;i++) { if(i==1||b[i]!=b[i-1]) q[b[i]]=++cnt; else q[b[i]]=cnt; } for(int i=1;i<=n;i++) c[i]=q[ji[i]]; build(1,1,n); for(int i=1;i<=n;i++) { f=query(1,1,c[i])+ji[i]; change(1,c[i],c[i],f); uuu=max(uuu,f); } cout<<sum-uuu<<endl; for(int i=1;i<=n;i++) q[ji[i]]=0; } int main(){ T=rd(); while(T--) { uuu=0; sum=0; Solve(); } return 0; }
这就是11号的所有题目,我理解的还比较透彻。只是如果能在考试的的时候写出来就好了。