挑战程序设计竞赛 3.2 常用技巧精选(一)
【Summarize】
1. 对区段和取绝对值的做法,可以转化成有序前缀和的差值,性质不变
2. 结论依赖于数据单调性的题基本可以尺取
3. 哈希表可以灵活应用于查找有非等限制条件的数据
POJ 2566:Bound Found
/* 题目大意:给出一个序列,求一个子段和,使得其绝对值最接近给出值, 输出这个区间的左右端点和区间和。 题解:因为原序列的前缀和不具有单调性,难以处理, 因此我们对前缀和进行排序,同时保留前缀和的右端点做标识作用, 题目要求区段和的绝对值最接近目标,因此排序不会造成前后顺序变化造成的影响 现在题目转化为在一个有序数列中,求一个数对,使得差值最接近给出数, 利用单调性,可以尺取解决问题。 */ #include <cstdio> #include <utility> #include <algorithm> #include <climits> using namespace std; const int N=100010,INF=INT_MAX; typedef pair<int,int> P; int n,m,s,t,ans,ansl,ansr; P p[N]; int main(){ while(scanf("%d%d",&n,&m),n&&m){ p[0]=P(s=0,0); for(int i=1;i<=n;i++)scanf("%d",&t),p[i]=P((s+=t),i); sort(p,p+n+1); while(m--){ scanf("%d",&t); int l=0,r=1,Min=INF; while(l<=n&&r<=n){ int tmp=p[r].first-p[l].first; if(abs(tmp-t)<Min){ Min=abs(tmp-t); ans=tmp; ansl=p[l].second; ansr=p[r].second; }if(tmp>t)l++;else if(tmp<t)r++;else break; if(l==r)r++; }if(ansl>ansr)swap(ansl,ansr); printf("%d %d %d\n",ans,ansl+1,ansr); } }return 0; }
POJ 2739:Sum of Consecutive Prime Numbers
/* 题目大意:求出一个数能被拆分为相邻素数相加的种类 题解:将素数筛出到一个数组,题目转化为求区段和等于某数的次数,尺取法即可。 */ #include <cstdio> #include <algorithm> using namespace std; int p[10010],cnt,a[10010],x; int main(){ for(int i=2;i<=10000;i++){ if(!a[i]){p[++cnt]=i;for(int j=i+i;j<=10000;j+=i)a[j]=1;} } while(scanf("%d",&x)&&x){ int l=1,r=0,ans=0,s=0; for(;l<=cnt;s-=p[l],l++){ while(r<cnt&&s<x){r++;s+=p[r];} if(s==x)ans++; if(p[r]>x)break; }printf("%d\n",ans); }return 0; }
POJ 2100:Graveyard Design
/* 题目大意:给出一个数,求将其拆分为几个连续的平方和的方案数 题解:对平方数列尺取即可。 */ #include <cstdio> using namespace std; typedef long long LL; const int N=10000010; LL n,ansl[N],ansr[N]; int main(){ while(~scanf("%lld",&n)){ LL l=1,r=0,s=0;int cnt=0; for(;l*l<=n;s-=l*l,l++){ while((r+1)*(r+1)<=n&s<n){r++;s+=r*r;} if(s==n){ansl[++cnt]=l;ansr[cnt]=r+1;} }printf("%d\n",cnt); for(int i=1;i<=cnt;i++){ printf("%lld",ansr[i]-ansl[i]); for(int j=ansl[i];j<ansr[i];j++)printf(" %d",j); puts(""); } }return 0; }
POJ 3185:The Water Bowls
/* 题目大意:给出一个01序列,在一个位置操作一次可以使得周围两个数和自己一起01取反。 求最小操作数使得所有数字变0 题解:我们只需要枚举第0个位置是否翻转,和第一个位置是否翻转即可,就能推导出后面的情况 两种取最小值即可。 */ #include <cstdio> #include <climits> #include <cstring> #include <algorithm> using namespace std; int p[21],f[21]; int cal(int x){ memset(f,0,sizeof(f)); int res=0,s=0; p[0]=x; for(int i=0;i<20;i++){ if((p[i]+s)&1)res++,f[i]=1,s++; if(i>=2)s-=f[i-2]; }if((p[20]+s)&1)return INT_MAX; return res; } int main(){ for(int i=1;i<=20;i++)scanf("%d",&p[i]); printf("%d\n",min(cal(0),cal(1))); return 0; }
POJ 1222:EXTENDED LIGHTS OUT
/* 题目大意:给出一个6*5的矩阵,由0和1构成,要求将其全部变成0, 每个格子和周围的四个格子联动,就是说,如果一个格子变了数字, 周围四格都会发生变化,变化即做一次与1的异或运算,输出每个格子的操作次数。 题解:对于每个格子的最终情况列一个方程, 一共三十个方程三十个未知数,用高斯消元求解即可 */ #include <cstdio> #include <algorithm> #include <cstring> using namespace std; int T,p[35][35],Cas=1; void Gauss(int n,int m){ int i,j,k,h,w; for(i=j=1;j<m;j++,w=0){ for(k=i;k<=n;k++)if(p[k][j])w=k; if(w){ for(k=j;k<=m;k++)swap(p[i][k],p[w][k]); for(k=1;k<=n;k++)if(k!=i&&p[k][j]){ for(h=j;h<=m;h++)p[k][h]^=p[i][h]; }i++; }if(i>n)break; } } int main(){ scanf("%d",&T); while(T--){ memset(p,0,sizeof(p)); for(int i=1;i<=30;i++){ p[i][i]=1; if(i>6)p[i-6][i]=1; if(i<25)p[i+6][i]=1; if(i%6!=1)p[i-1][i]=1; if(i%6!=0)p[i+1][i]=1; }for(int i=1;i<=30;i++){scanf("%d",&p[i][31]);} Gauss(30,31); printf("PUZZLE #%d\n",Cas++); for(int i=1;i<=30;i++){ printf("%d",p[i][31]); if(i%6==0)puts(""); else printf(" "); } }return 0; }
POJ 2674:Linear world
/* 题目大意:给出一些人在一线段,他们相碰的时候就会反向走,触碰到边界就停下来 每个人都有名字,问走的最长路程和创造纪录的人。 题解:首先第一问就是蚂蚁问题,碰撞当做穿越,很容易完成问题, 对于第二问,我们发现原位置序列中的每个人的路程其实等于终序列中这个位置的人 在穿越情况下的路程,所以我们求出这个序列,找到这个人即可。 */ #include <cstdio> #include <algorithm> #include <vector> using namespace std; const int N=50010; int n; double ans[N],d,v,pos; vector<double> P,Q; char name[N][300],op[10]; int main(){ while(scanf("%d",&n)&&n){ scanf("%lf%lf",&d,&v); double Max=0; P.clear();Q.clear(); for(int i=0;i<n;i++){ scanf("%s%lf%s",op,&pos,name[i]); if(op[0]=='p'||op[0]=='P'){ P.push_back(pos); Max=max(Max,(d-pos)/v); }else{ Q.push_back(pos); Max=max(Max,pos/v); } }for(int i=0;i<P.size();i++)ans[n-P.size()+i]=(d-P[i])/v; for(int i=0;i<Q.size();i++)ans[i]=Q[i]/v; double res=-1; int id=0; for(int i=0;i<n;i++){ if(ans[i]>res)res=ans[i],id=i; }Max=(int)(Max*100)/100.0; printf("%13.2lf %s\n",Max,name[id]); }return 0; }
POJ 3977:Subset
/* 题目大意:在n个数(n<36)中选取一些数,使得其和的绝对值最小. 题解:因为枚举所有数选或者不选,复杂度太高无法承受, 我们考虑减小枚举的范围,我们将前一半进行枚举,保存其子集和, 然后后一半枚举子集和取反在前一半中寻找最接近的,两部分相加用以更新答案。 */ #include <cstdio> #include <utility> #include <algorithm> #include <map> using namespace std; typedef long long LL; const int N=40; int n; LL a[N]; LL Abs(LL x){return x<0?-x:x;} int main(){ while(scanf("%d",&n)&&n){ for(int i=0;i<n;i++)scanf("%lld",&a[i]); map<LL,int> M; map<LL,int>::iterator it; pair<LL,int> ans(Abs(a[0]),1); for(int i=1;i<1<<(n/2);i++){ LL s=0; int cnt=0; for(int j=0;j*2<n;j++){if((i>>j)&1)s+=a[j],cnt++;} ans=min(ans,make_pair(Abs(s),cnt)); if(M[s])M[s]=min(M[s],cnt); else M[s]=cnt; } for(int i=1;i<1<<(n-n/2);i++){ LL s=0; int cnt=0; for(int j=0;j<(n-n/2);j++){ if((i>>j)&1)s+=a[j+n/2],cnt++; }ans=min(ans,make_pair(Abs(s),cnt)); it=M.lower_bound(-s); if(it!=M.end())ans=min(ans,make_pair(Abs(s+it->first),cnt+it->second)); if(it!=M.begin()){ it--; ans=min(ans,make_pair(Abs(s+it->first),cnt+it->second)); } }printf("%lld %d\n",ans.first,ans.second); }return 0; }
POJ 2549:Subsets
/* 题目大意:给出一个数集,从中选择四个元素,使得a+b+c=d,最小化d 题解:我们对a+b建立Hash_table,之后枚举c和d,寻找c-d且不由c和d构成的hash值是否存在 如果存在,那么d就可以用来更新答案 */ #include <cstdio> #include <algorithm> #include <cstring> #include <climits> using namespace std; const int N=1024,mod=1<<18; int n,x[N],head[mod],cnt; struct data{int a,b,sum,nxt;}g[N*N]; inline int hash(int x){return abs(x)&(mod-1);} void insert(int a,int b,int sum){ int key=hash(sum); for(int i=head[key];i!=-1;i=g[i].nxt){ if(g[i].sum==sum&&g[i].a==a&&g[i].b==b)return; }g[cnt].a=a;g[cnt].b=b;g[cnt].sum=sum; g[cnt].nxt=head[key]; head[key]=cnt++; } bool search(int a,int b,int sum){ int key=hash(sum); for(int i=head[key];i!=-1;i=g[i].nxt){ if(g[i].sum!=sum||g[i].a==a||g[i].a==b||g[i].b==a||g[i].b==b)continue; return 1; }return 0; } void init(){cnt=0;memset(head,-1,sizeof(head));} int main(){ while(scanf("%d",&n)&&n){ init(); for(int i=0;i<n;i++)scanf("%d",&x[i]); for(int i=0;i<n;i++)for(int j=i+1;j<n;j++)insert(i,j,x[i]+x[j]); int flag=0,ans=INT_MIN; for(int i=0;i<n;i++)for(int j=0;j<n;j++){ if(i==j)continue; if(search(i,j,x[i]-x[j])){ flag=1;ans=max(ans,x[i]);break; } }if(flag)printf("%d\n",ans); else puts("no solution"); }return 0; }
AOJ 0531:Paint Color
/* 题目大意:给出一张图,和一些矩形障碍物,求该图没被障碍物覆盖的部分被划分为几个连通块 题解:首先对图中的点进行离散化,对于一个障碍物来说, 我们将其看做左闭右开上闭下开的图形,所以在离散的时候只要离散障碍物的点即可。 之后我们利用imos积累法得出哪些部分是障碍物,就可以统计连通块了。 */ #include <cstdio> #include <vector> #include <algorithm> #include <cstring> #include <utility> #include <queue> using namespace std; const int N=1050,dx[]={1,-1,0,0},dy[]={0,0,1,-1}; int n,H,W,X1[N],X2[N],Y1[N],Y2[N]; int imos[2*N][2*N]; int compress(int *x1,int *x2,int w){ vector<int>xs; for(int i=0;i<n;i++){ int tx1=x1[i],tx2=x2[i]; if(1<=tx1&&tx1<w)xs.push_back(tx1); if(1<=tx2&&tx2<w)xs.push_back(tx2); }xs.push_back(0);xs.push_back(w); sort(xs.begin(),xs.end()); xs.erase(unique(xs.begin(),xs.end()),xs.end()); for(int i=0;i<n;i++){ x1[i]=find(xs.begin(),xs.end(),x1[i])-xs.begin(); x2[i]=find(xs.begin(),xs.end(),x2[i])-xs.begin(); }return xs.size()-1; } int bfs(){ int ans=0; for(int i=0;i<H;i++){ for(int j=0;j<W;j++){ if(imos[i][j])continue; ans++; queue<pair<int,int> >que; que.push(make_pair(j,i)); while(!que.empty()){ int nx=que.front().first,ny=que.front().second; que.pop(); for(int i=0;i<4;i++){ int tx=nx+dx[i],ty=ny+dy[i]; if(tx<0||W<tx||ty<0||H<ty||imos[ty][tx]>0)continue; que.push(make_pair(tx,ty)); imos[ty][tx]=1; } } } }return ans; } int main(){ while(~scanf("%d%d",&W,&H),W||H){ scanf("%d",&n); for(int i=0;i<n;i++)scanf("%d%d%d%d",&X1[i],&Y1[i],&X2[i],&Y2[i]); memset(imos,0,sizeof(imos)); W=compress(X1,X2,W);H=compress(Y1,Y2,H); for(int i=0;i<n;i++){ imos[Y1[i]][X1[i]]++; imos[Y1[i]][X2[i]]--; imos[Y2[i]][X1[i]]--; imos[Y2[i]][X2[i]]++; }for(int i=0;i<H;i++)for(int j=1;j<W;j++)imos[i][j]+=imos[i][j-1]; for(int j=0;j<W;j++)for(int i=1;i<H;i++)imos[i][j]+=imos[i-1][j]; printf("%d\n",bfs()); }return 0; }
愿你出走半生,归来仍是少年