您的位置:首页 > 其它

APIO2015简要题解

2017-02-17 23:32 246 查看
  最近老师把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
[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
[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
<=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;
}


好的就是这样,理解了题目做法后都就不难写了.....

有什么错误请您指出 谢谢咯 😊
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: