NOIP试题解析 by QTY_YTQ
noip2010关押罪犯(并查集)
题意是有n个罪犯关在两个监狱中,其中有m对罪犯有仇恨关系,如果有仇恨的罪犯关在一起会产生一定影响力的事件,要求安排罪犯位置使产生影响力最大的事件影响最小。
可以用并查集来做,每个罪犯抽象为两个点,一个表示该罪犯关押在1监狱,另一个表示该罪犯关押在2监狱,我们将罪犯仇恨关系按影响的大小排序,每次选取影响力最大的一对罪犯(x,y),尽可能不让它们在一个监狱内,将x1和y2合并,将x2和y1合并,继续往后做,知道两个罪犯已经在同一集合内,这时不能让这两个罪犯在不同监狱中,则他们之间的影响力就是最小的最大影响力,
代码:
program prison; var a,b,c:array[0..100000]of longint; f:array[0..40000]of longint; n,i,m,x,y,v,x1,y1,x2,y2:longint; function find(x:longint):longint; var i,j,k:longint; begin i:=x; j:=i; while f[i]<>i do i:=f[i]; while j<>i do begin k:=f[j];f[j]:=i; j:=k; end; exit(i); end; procedure qsort(l,r:longint); var i,j,m,t:longint; begin i:=l; j:=r; m:=c[(l+r) div 2]; repeat while c[i]>m do inc(i); while c[j]<m do dec(j); if i<=j then begin t:=c[i]; c[i]:=c[j]; c[j]:=t; t:=a[i]; a[i]:=a[j]; a[j]:=t; t:=b[i]; b[i]:=b[j]; b[j]:=t; inc(i); dec(j); end; until i>j; if j>l then qsort(l,j); if i<r then qsort(i,r); end; begin readln(n,m); for i:=1 to n*2 do f[i]:=i; for i:=1 to m do begin readln(x,y,v); a[i]:=x; b[i]:=y; c[i]:=v; end; qsort(1,m); for i:=1 to m do begin x1:=find(a[i]); x2:=find(a[i]+n); y1:=find(b[i]); y2:=find(b[i]+n); if (x1<>y1)and(x2<>y2) then begin f[x2]:=y1; f[y2]:=x1; end else begin writeln(c[i]); n:=-1;break; end; end; if n>=0 then writeln(0); end.
noip2011选择客栈(思考+rmq)
题意是有n个客栈排成一排,每种客栈有一个颜色,共k种颜色,有两个人要住在同种颜色的不同客栈中,并且要到这两个客栈之间(包括这两个客栈)的某家客栈喝咖啡,并且花费不超过p,求满足该条件的住宿方案数。
主要思路是,先rmqst算法得到任意两客栈之间客栈喝咖啡的最小花费,可以看出,如果选择i客栈和j客栈最小花费不超过p,则选择i客栈和j以后客栈的最小花费必然不超过p,选择i之前客栈和j客栈的最小花费也不超过p,那么我们从1做到n,对于客栈i,对应颜色x,先判断它左边最靠近它的颜色是x一个客栈与该客栈之间的最小花费是否超过p,超过则计数器直接加上之前满足某一方案且颜色为x的客栈数,没超过则加上i之前颜色为x的客栈总数,得到答案。
代码:
program hotel; var h:array[0..20,0..200000]of longint; a,b:array[0..200000]of longint; pl:array[0..20]of longint; r,c,u:array[0..50]of longint; n,i,m,j,k,p,x,s:longint; function min(x,y:longint):longint; begin if x<y then min:=x else min:=y; end; function find(x,y:longint):longint; var j:longint; begin j:=trunc(ln(y-x+1)/ln(2)); exit(min(h[j,x],h[j,y-pl[j]+1])); end; begin assign(input,‘hotel.in‘); reset(input); assign(output,‘hotel.out‘); rewrite(output); readln(n,k,p); for i:=1 to n do readln(b[i],a[i]); pl[0]:=1; for i:=1 to trunc(ln(n)/ln(2)) do pl[i]:=pl[i-1]*2; for i:=1 to n do h[0,i]:=a[i]; for i:=1 to trunc(ln(n)/ln(2)) do for j:=1 to n+1-2*i do h[i,j]:=min(h[i-1,j],h[i-1,j+pl[i-1]]); for i:=1 to n do begin x:=b[i]; if u[x]=0 then begin r[x]:=i;u[x]:=1; end else begin k:=find(r[x],i); if k<=p then begin c[x]:=u[x];u[x]:=u[x]+1;s:=s+c[x];r[x]:=i; end else begin s:=s+c[x]; u[x]:=u[x]+1; r[x]:=i; end; end; end; writeln(s); close(input); close(output); end.
noip2011聪明的质监员(二分)
题意什么的按题目来,需要注意的每个区间的值是由该区间上重量超过w矿石个数乘上该区间重量超过w矿石的价值和。
二分答案,然后前缀和得到区间上超过w矿石的价值和与数量和,然后判断比s大还是小,利用w越大,y越小的单调性变换上下界。
代码:
program qc; var w,v,x,y,s1,s2:array[0..200000]of int64; n,i,m,l,r,mid:longint; s,x1,x2:int64; function find(k:longint):int64; var i,j:longint; sum:int64; begin sum:=0; fillchar(s1,sizeof(s1),0); fillchar(s2,sizeof(s2),0); for i:=1 to n do begin s1[i]:=s1[i-1]+ord(w[i]>=k)*v[i];s2[i]:=s2[i-1]+ord(w[i]>=k); end; for i:=1 to m do sum:=sum+(s1[y[i]]-s1[x[i]-1])*(s2[y[i]]-s2[x[i]-1]); exit(sum); end; begin assign(input,‘qc.in‘); reset(input); assign(output,‘qc.out‘); rewrite(output); readln(n,m,s); for i:=1 to n do begin readln(w[i],v[i]); if w[i]>r then r:=w[i]; end; for i:=1 to m do readln(x[i],y[i]);l:=1; while l<=r do begin mid:=(l+r) div 2; if find(mid)<=s then r:=mid-1 else l:=mid+1; end; x1:=find(l); x2:=find(r); if abs(x1-s)<abs(x2-s) then writeln(abs(x1-s)) else writeln(abs(x2-s)); close(input); close(output); end.
noip2012借教室(二分+前缀和)
题意是每天有一些教室可以租借,有m份请求分别要租借一定数量教室一段时间,输出最先无法满足的请求序号。
二分答案,利用前缀和记录不同时间租借教室的个数,判断是否满足。
代码:
program classroom; var f,d,s,t,b:array[0..1000000]of int64; n,i,m,k,l,r,mid,ans:longint; sum:int64; begin assign(input,‘classroom.in‘); reset(input); assign(output,‘classroom.out‘); rewrite(output); readln(n,m); for i:=1 to n do read(f[i]); for i:=1 to m do readln(d[i],s[i],t[i]); l:=0; r:=m; while l<=r do begin mid:=(l+r) div 2; fillchar(b,sizeof(b),0); for i:=1 to mid do begin inc(b[s[i]],d[i]);inc(b[t[i]+1],-d[i]); end; sum:=0; k:=0; for i:=1 to n do begin sum:=sum+b[i]; if sum>f[i] then begin k:=1; break; end; end; if k=1 then r:=mid-1 else begin ans:=mid; l:=mid+1; end; end; if ans>=m then writeln(0) else begin writeln(-1); writeln(ans+1); end; close(input); close(output); end.
noip2013火柴排队(贪心+逆序对)
题意两组火柴,同组火柴高度不同,分别将两组火柴以两两交换形式得到新序列,使两组火柴同位置火柴高度差绝对值和最小,求最少交换次数。
显然1组第一高的火柴要对应2组第一高的火柴,第二高也对应第二高,以此类推,我们对火柴高度离散化后,以一组火柴为参照,求另一组火柴高度逆序对个数即可。
代码:
program match; type arr=array[0..100002]of longint; var a,b,u,v,bit:arr; n,i,m:longint; t:int64; procedure add(i,x:longint); begin while i<=n do begin bit[i]:=bit[i]+x; inc(i,i and -i); end; end; function sum(i:longint):longint; var s:longint; begin s:=0; while i>0 do begin s:=s+bit[i]; dec(i,i and -i); end; exit(s); end; procedure qsort(l,r:longint; var a,u:arr); var i,j,t,m:longint; begin i:=l; j:=r; m:=a[(i+j) div 2]; repeat while a[i]<m do inc(i); while m<a[j] do dec(j); if i<=j then begin t:=a[i]; a[i]:=a[j]; a[j]:=t; t:=u[i]; u[i]:=u[j]; u[j]:=t; inc(i);dec(j); end; until i>j; if i<r then qsort(i,r,a,u); if l<j then qsort(l,j,a,u); end; begin assign(input,‘match.in‘); reset(input); assign(output,‘match.out‘); rewrite(output); readln(n); for i:=1 to n do begin read(a[i]);u[i]:=i; end; readln; for i:=1 to n do begin read(b[i]);v[i]:=i; end; qsort(1,n,a,u); qsort(1,n,b,v); for i:=1 to n do begin a[i]:=i; b[v[i]]:=u[i]; end; for i:=n downto 1 do begin inc(t,sum(b[i]));add(b[i],1); end; writeln(t mod 99999997); close(input); close(output); end.
noip2014联合权值
题意很简单,不说了(其实是我懒得写)。
记录下每个点与之相邻所有点的权值和,权值平方和,最大值,次大值,所有点最大次大值乘积的最大值就是最大联合权值,权值和平方减权值平方和的和就是联合权值和。
代码:
program link; var a,b,v,max1,max2,sum,num:array[0..200000]of int64; n,i:longint; m,ans:int64; begin assign(input,‘link.in‘); reset(input); assign(output,‘link.out‘); rewrite(output); readln(n); for i:=1 to n-1 do readln(a[i],b[i]); for i:=1 to n do read(v[i]); for i:=1 to n-1 do begin if v[b[i]]>max1[a[i]] then begin max2[a[i]]:=max1[a[i]];max1[a[i]]:=v[b[i]]; end else if v[b[i]]>max2[a[i]] then max2[a[i]]:=v[b[i]]; if v[a[i]]>max1[b[i]] then begin max2[b[i]]:=max1[b[i]];max1[b[i]]:=v[a[i]]; end else if v[a[i]]>max2[b[i]] then max2[b[i]]:=v[a[i]]; inc(sum[a[i]],v[b[i]]); inc(num[a[i]],sqr(v[b[i]])); inc(sum[b[i]],v[a[i]]); inc(num[b[i]],sqr(v[a[i]])); end; for i:=1 to n do if max1[i]*max2[i]>m then m:=max1[i]*max2[i]; for i:=1 to n do begin ans:=(ans+(sqr(sum[i])-num[i]) mod 10007) mod 10007; end; writeln(m,‘ ‘,ans); close(input); close(output); end.
noip2014寻找道路
题意很简单不解释。
看清题意很重要,起点终点是由输入提供的,另外符合要求的点其所有出边指向的点与终点联通,无解输出-1.
做起来很简单,所有边反过来求一边终点到各点最短路径,判断每个点是否符合,然后在符合要求的点中求起点到终点最短路。
代码:
program road; var c,a:array[0..10000,0..2000]of longint; q:array[0..500001]of longint; dis:array[0..10000]of longint; g,r:array[0..10000]of boolean; n,i,m,j,t,x,y,z,h,u,v:longint; function min(x,y:longint):longint; begin if x<y then min:=x else min:=y; end; begin assign(input,‘road.in‘); reset(input); assign(output,‘road.out‘); rewrite(output); readln(n,t); for i:=1 to t do begin readln(x,y); a[x,0]:=a[x,0]+1; a[x,a[x,0]]:=y; c[y,0]:=c[y,0]+1; c[y,c[y,0]]:=x; end; readln(x,y); for i:=1 to n do dis[i]:=maxlongint div 3; fillchar(g,sizeof(g),false); h:=0; t:=1; dis[y]:=0; q[1]:=y; g[y]:=true; while h<t do begin h:=h+1; u:=q[h]; g[u]:=false; for i:=1 to c[u,0] do begin v:=c[u,i]; if dis[u]+1<dis[v] then begin dis[v]:=dis[u]+1; if g[v]=false then begin t:=t+1; q[t]:=v; g[v]:=true; end; end; end; end; for i:=1 to n do begin r[i]:=true; for j:=1 to a[i,0] do if dis[a[i,j]]>=maxlongint div 3 then begin r[i]:=false; break; end; end; for i:=1 to n do dis[i]:=maxlongint div 3; fillchar(g,sizeof(g),false); h:=0; t:=1; dis[x]:=0; q[1]:=x; g[x]:=true; while h<t do begin h:=h+1; u:=q[h]; g[u]:=false; for i:=1 to a[u,0] do begin v:=a[u,i]; if (dis[u]+1<dis[v])and(r[v]=true) then begin dis[v]:=dis[u]+1; if g[v]=false then begin t:=t+1; q[t]:=v; g[v]:=true; end; end; end; end; if (r[x]=false)or(dis[y]>=maxlongint div 3) then writeln(-1) else writeln(dis[y]); close(input); close(output); end.