APIO2015简要题解
最近老师把apio的题目拿出来了,然后由于我实在是菜,分数还没三位数......
----------------我是分割线
1.巴厘岛的雕塑
N个数,分成连续的A-B个组,让每个组的和或起来最小,求最小值。
对于Task1 n<=100
由于涉及到位运算,所以很容易想到按二进制位来做。要让答案最小,显然要从二进制高位到低位判断,能取0就取0。
所以我们考虑一个n^3 dp 用 f[i][j] 表示在当前的值之下,前i个分j组能否符合条件,用x表示当前判断的数字。
f[i][j]|=f[k][j-1] (k=[0,i-1] , s[k+1..i] or x=x)
很显然如果满足第k+1到第i个数的和或上x等于x,那么这个数字和没有一个二进制位比x大。
那么就先把所有位设成1,从高位开始,把它设成0,进行dp。如果在f[n][A..B]中有一个true,那么就可以分,不然就把这一位改回1。这样一直做下去就可以得到答案。
复杂度n^3logAi
对于Task 2 n<=2000, A=1
n的范围扩大了,但是A=1很关键,这意味着我不考虑分组是否大等A了,只要<=B即可(显然这样我们就要让分组最小)。
那么我们改良一下dp,用f[i]表示前i个数,满足条件的情况下,至少分几组。
f[i]=min(f[j])+1;(j=[0,i-1],s[j+1..i] or x=x)
最后的答案和B比较一下即可,这样就dp变成了n^2了,其他过程不变。
复杂度n^2logAi。
附很丑的代码
#include<iostream> #include<cstdio> #include<cstring> #define ll long long using namespace std; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } bool f[105][105]; int f2[2005]; int n; ll ans; ll s[2005]; int a,b; bool check(ll x) { memset(f,0,sizeof(f)); f[0][0]=1; for(int i=1;i<=n;i++) for(int k=1;k<=i;k++) for(int j=0;j<i;j++) { if(((s[i]-s[j])|x)==x) f[i][k]|=f[j][k-1]; } for(int i=a;i<=b;i++) if(f[n][i]) return true; return false; } bool check2(ll x) { f2[0]=0;ll nown=0; for(int i=1;i<=n;i++) { f2[i]=n+1; for(int j=0;j<i;j++) { if(((s[i]-s[j])|x)==x) f2[i]=min(f2[i],f2[j]+1); } } if(f2[n]<=b) return true; return false; } int main() { freopen("sculpture.in","r",stdin); freopen("sculpture.out","w",stdout); n=read();a=read();b=read(); for(int i=1;i<=n;i++) s[i]=read(); for(int i=1;i<=n;i++) s[i]+=s[i-1]; if(n<=100) { ans=(ll)1<<50;--ans; for(int i=49;i>=0;--i) { ans-=(ll)1<<i; if(!check(ans)) ans+=(ll)1<<i; } } else { ans=(ll)1<<50;--ans; for(int i=49;i>=0;--i) { ans-=(ll)1<<i; if(!check2(ans)) ans+=(ll)1<<i; } } printf("%I64d\n",ans); return 0; }
2.雅加达的摩天楼
有n个点m只doge(0-m-1),每只doge给定初始位置和跳跃力,每次跳跃,位置为b,跳跃力为p的doge可以跳到b+p或者b-p。
现在doge0有信息要传到doge1 求最少的跳跃数。n,m<=30000
网上有其他的最短路的做法,但是我们仔细分析分析,是可以暴力的.....
设一个状态(b,p)表示现在信息传到了在b点跳跃力为p的doge上。从出题人卡代码的角度上,显然要状态数最多,只要加上了判重,那么状态数就最多是
30000+2*15000+3*10000+....
也就是说每个跳跃力状态数最多只有n种,但是对于跳跃力p,每个能增加的状态数辆只有n/p....
那么这样数量最多的时候, doge跳跃力从1,2,2,3,3,3,一直到k, k*(k-1)/2=m,状态数最多n*k,大约七百多万....
所以直接bfs,到达一个没到过的点时候把那里的doge全部入队,加上哈希判重即可。(map跑太慢了,听了一个大佬的建议手写了一个在这道题理论上o(1)的哈希表...)
附上好丑好丑的代码
#include<iostream> #include<cstdio> #include<queue> #define ll long long using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int n,m,cnt=0,cnt2=0; int head[30005]; bool has[30005]; int front[9875322]; int hx[7300001]; int next[7300001]; struct edge{ int m,next; }e[30005]; int q[3][7300001]; int top,tail; inline void push(int b,int p,int f) { q[0][++top]=b; q[1][top]=p; q[2][top]=f; } inline void ins(int x,int y) { e[++cnt].next=head[x]; head[x]=cnt; e[cnt].m=y; } void rel(int num,int f)//释放所有doge { has[num]=1; for(int i=head[num];i;i=e[i].next) push(num,e[i].m,f); } inline void insw(int h,int b) { next[++cnt2]=front[h]; front[h]=cnt2; hx[cnt2]=b; } bool check(int hash,int b) { for(int i=front[hash];i;i=next[i]) if(hx[i]==b) return false; insw(hash,b); return true; } inline void get(int&b,int&p,int&f) { b=q[0][++tail]; p=q[1][tail]; f=q[2][tail]; } inline void add(int b,int p,int f) { int hash=(b<<15)+p;hash%=9875321; if(check(hash,b)) { push(b,p,f); if(!has[b]) rel(b,f); } } int main() { freopen("skyscraper.in","r",stdin); freopen("skyscraper.out","w",stdout); n=read();m=read();int from,to; for(register int i=1;i<=m;++i) { q[0][i]=read();q[1][i]=read(); ins(q[0][i],q[1][i]); }from=q[0][1];to=q[0][2]; rel(from,0); if(from==to) return 0*puts("0"); register int b,p,f; while(top!=tail) { get(b,p,f); if(b+p==to||b-p==to) {printf("%d\n",f+1);return 0;} if(b>=p) add(b-p,p,f+1); if(b+p<n) add(b+p,p,f+1); } puts("-1"); return 0; }
3.巴邻旁之桥
从前有条河,河旁住着人....
有n个人,每个人有要从一个坐标到另一个坐标,这些位置可能在河的两边。现在你要在河的某些位置修建垂直于河的k座桥,让所有人的路径长之和最短。
对于Task 1 k=1,n<=100000
不过河的人直接统计答案。
剩下的人,很显然,只修一座桥的情况下,一定要修在坐标的中位数那里。(设桥修在x,每个坐标产生的距离为|pos-x|,脑补一下就知道了)。
对于Task 2 k=2 n<=100000
这下预算高了一点 要修两座桥了,对于每一个人(x,y),肯定走离x,y的中点(x+y)/2更近的桥。
那么一定会存在一个分割点,使得左边的人走左边的桥,右边的人走右边的桥,距离和最短。这样的话,两边的桥就按照Task1修在中位数就可以了。
那么就要考虑枚举分割点。从一段中删掉或者插入一个点,我们发现由于每次都选在中位数,实际上产生影响的只有被操作的那个点。所以我们用两个权值线段树(或者平衡树),维护左右两段的中位数即可啦。
复杂度nlogn
附上最丑的代码
#include<iostream> #include<cstdio> #include<algorithm> #define ll long long using namespace std; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0'; ch=getchar();} return x*f; } int T1[800005],T2[800005]; inline ll myabs(ll x) { return x<0?-x:x; } struct gg{ int l,r,mid,idl,idr; }home[200005]; int n,cnt=0; char c1,c2; ll s[200005]; ll ext=0,ans=0,ans2=0,minn,num; int solve1() { int a,b; for(int i=1;i<=n;i++) { scanf("%c",&c1);a=read(); scanf("%c",&c2);b=read(); if(c1==c2) ext+=myabs(a-b); else { s[++cnt]=a; s[++cnt]=b; ++ext; } } sort(s+1,s+cnt+1); ll mid=s[(cnt+1)/2]; for(int i=1;i<=cnt;i++) ans+=myabs(mid-s[i]); printf("%lld",ans+ext); return 0; } ll query(int*T,int x,int l,int r,int rk) { if(l==r) return s[l]; if(T[x<<1]>=rk) return query(T,x<<1,l,(l+r)>>1,rk); else return query(T,x*2+1,(l+r)/2+1,r,rk-T[x<<1]); } void ins(int*T,int x,int p,int l,int r,int ad) { if(l!=r) { int mid=(l+r)>>1; if(x<=mid) ins(T,x,p<<1,l,mid,ad); else ins(T,x,(p<<1)+1,mid+1,r,ad); } T[p]+=ad; } bool cmp(gg x,gg y){return x.mid<y.mid;} int main() { freopen("bridge.in","r",stdin); freopen("bridge.out","w",stdout); int type=read();n=read(); if(type==1) return 0*solve1(); int a,b; for(int i=1;i<=n;i++) { scanf("%c",&c1);a=read(); scanf("%c",&c2);b=read(); //cout<<c1<<" "<<c2<<endl; if(c1==c2) ext+=myabs(a-b); else { home[++cnt].l=min(a,b); home[cnt].r=max(a,b); home[cnt].mid=(a+b)/2; s[++num]=a; s[++num]=b; ++ext; } } sort(s+1,s+num+1); for(int i=1;i<=cnt;i++) { home[i].idl=lower_bound(s+1,s+num+1,home[i].l)-s; home[i].idr=lower_bound(s+1,s+num+1,home[i].r)-s; } sort(home+1,home+cnt+1,cmp); if(cnt<=2) { ans=0; for(int i=1;i<=cnt;i++) ans+=home[i].r-home[i].l; printf("%lld\n",ans+ext);return 0; } for(int i=1;i<=cnt;i++) ins(T2,home[i].idl,1,1,num,1),ins(T2,home[i].idr,1,1,num,1); ans=ans2=0; ll pos=query(T2,1,1,num,cnt+1); for(int i=1;i<=cnt;i++) ans2+=myabs((ll)home[i].r-pos)+myabs((ll)home[i].l-pos); minn=ans+ans2; ll pos1,pos2=pos; for(int i=1;i<=cnt;i++) { ans2-=myabs((ll)home[i].l-pos2)+myabs((ll)home[i].r-pos2); ins(T1,home[i].idl,1,1,num,1); ins(T2,home[i].idl,1,1,num,-1); ins(T1,home[i].idr,1,1,num,1); ins(T2,home[i].idr,1,1,num,-1); pos1=query(T1,1,1,num,i); pos2=query(T2,1,1,num,cnt-i+1); ans+=myabs((ll)home[i].l-pos1)+myabs((ll)home[i].r-pos1); if(ans+ans2<minn) minn=ans+ans2; } printf("%I64d\n",minn+ext); return 0; }
好的就是这样,理解了题目做法后都就不难写了.....
有什么错误请您指出 谢谢咯 😊