APIO2015题解

感觉这套还是十分可做的……

巴厘岛的雕塑 Bali Sculptures

这题比较简单,看完就大概知道做法。显然要从高到低一位一位的贪心,问题是如何保证前面位也是合法的。如果已经确定了前 k k k位,显然只要当前状态的前 k k k位是其子集就行了。于是有一个暴力的DP: f i , j f_{i,j} fi,j表示前i个数,分了 j j j段是否可以使当前这位取到最大。这样是 O ( n 3 ) O(n^3) O(n3)的。来看一下最后一个点, A = 1 A=1 A=1,也就是我们要尽量让分的段数最少,于是改一下DP数组的定义, g i g_i gi表示前 i i i个数,在保证最优的情况下最少分的段数即可。

Code
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define pa pair<int,int>
const int Maxn=2010;
const int inf=2147483647;
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<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*f;
}
int n,L,R,a[Maxn],g[Maxn];bool v[Maxn],f[210][210];LL sum[Maxn],m=0,b[50];
int main()
{
	n=read(),L=read(),R=read();
	for(int i=1;i<=n;i++)a[i]=read();
	sum[0]=0;for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i];
	b[0]=(1LL<<43)-1;
	for(int i=1;i<=42;i++)b[i]=b[i-1]-(1LL<<(i-1));
	if(n<=100)
	{
		for(int i=36;i>=0;i--)
		{
			memset(f,false,sizeof(f));
			f[0][0]=true;
			for(int j=1;j<=n;j++)
			for(int k=1;k<=min(j,R);k++)
			{
				for(int l=0;l<j;l++)
				{
					LL t=(sum[j]-sum[l])&b[i];
					if((t&m)==t&&!((1LL<<i)&t)&&f[l][k-1])f[j][k]=true;
					if(f[j][k])break;
				}
			}
			bool flag=false;
			for(int j=L;j<=R;j++)flag|=f[n][j];
			if(!flag)m|=(1LL<<i);
		}
		printf("%lld",m);
		return 0;
	}
	for(int i=41;i>=0;i--)
	{
		memset(g,63,sizeof(g));
		g[0]=0;
		for(int j=1;j<=n;j++)
		{
			for(int k=0;k<j;k++)
			{
				LL t=(sum[j]-sum[k])&b[i];
				if((t&m)==t&&!((1LL<<i)&t))g[j]=min(g[j],g[k]+1);
			}
		}
		if(g[n]>R)m|=(1LL<<i);
	}
	printf("%lld",m);
}

雅加达的摩天楼 Jakarta Skyscrapers

这题看着就要分块。对于 p i &gt; n p_i&gt;\sqrt n pi>n ,边数是 m n m\sqrt n mn 级别的,无需优化。否则先对序列分块,然后对于每种 ≤ n \le \sqrt n n 的弹跳力 x x x,每个块再建 x x x个点,表示到这一块的第一个点离这个块端点距离。点数和边数都是根号级别,但是跑dijkstra并没有跑过去,反倒是spfa跑得飞快。

Code
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define pa pair<int,int>
const int Maxn=30010;
const int Maxm=175;
const int inf=2147483647;
char pbuf[100000] , *pp = pbuf;
inline void push(const char ch)
{
    if(pp == pbuf + 100000) fwrite(pbuf , 1 , 100000 , stdout) , pp = pbuf;
    *pp ++ = ch;
}
inline void flush()
{
    fwrite(pbuf , 1 , pp - pbuf , stdout);
}
inline char nc()
{
    static char buf[100000],*p1=buf,*p2=buf;
    return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
inline int read()
{
    char ch=nc();int sum=0;
    while(!isdigit(ch))ch=nc();
    while(isdigit(ch))sum=((sum+(sum<<2))<<1)+(ch^48),ch=nc();
    return sum;
}
int n,m,N,b[Maxn],p[Maxn],st,ed,f[Maxn*Maxm],bel[Maxn],L[Maxm],R[Maxm],cnt;
int id[Maxm][Maxm][Maxm][2],tot;
struct Edge{int y,d,next;}e[Maxn*Maxm*2];
int last[Maxn*Maxm],len=0;
void ins(int x,int y,int d)
{
	int t=++len;
	e[t].y=y;e[t].d=d;e[t].next=last[x];last[x]=t;
}
bool in[Maxn*Maxm];
void spfa()
{
	memset(in,false,sizeof(in));
	memset(f,63,sizeof(f));f[st]=0;
	queue<int>q;q.push(st);
	while(!q.empty())
	{
		int x=q.front();q.pop();in[x]=false;
		for(int i=last[x];i;i=e[i].next)
		{
			int y=e[i].y;
			if(f[x]+e[i].d<f[y])
			{
				f[y]=f[x]+e[i].d;
				if(!in[y])in[y]=true,q.push(y);
			}
		}
	}
}
int main()
{
	memset(L,-1,sizeof(L));
	n=read(),m=read();N=(int)sqrt(n);tot=n;
	for(int i=0;i<n;i++)
	{
		bel[i]=i/N+1;
		if(L[bel[i]]==-1)L[bel[i]]=i;
		R[bel[i]]=i;
	}
	cnt=bel[n-1];
	for(int i=1;i<=m;i++)
	{
		b[i]=read(),p[i]=read();
		if(i==1)st=b[i];
		if(i==2)ed=b[i];
		if(p[i]>N)
		{
			for(int j=b[i]+p[i];j<n;j+=p[i])ins(b[i],j,(j-b[i])/p[i]);
			for(int j=b[i]-p[i];j>=0;j-=p[i])ins(b[i],j,(b[i]-j)/p[i]);
		}
		else
		{
			for(int j=b[i]+p[i];j<=R[bel[b[i]]];j+=p[i])ins(b[i],j,(j-b[i])/p[i]);
			for(int j=b[i]-p[i];j>=L[bel[b[i]]];j-=p[i])ins(b[i],j,(b[i]-j)/p[i]);
			for(int j=bel[b[i]]+1;j<=cnt;j++)
			{
				int o=b[i]+(L[j]-b[i]+p[i]-1)/p[i]*p[i];
				if(o>R[j])continue;
				if(!id[j][p[i]][o-L[j]][0])
				{
					id[j][p[i]][o-L[j]][0]=++tot;
					for(int k=o;k<=R[j];k+=p[i])ins(tot,k,(k-o)/p[i]);
				}
				ins(b[i],id[j][p[i]][o-L[j]][0],(o-b[i])/p[i]);
			}
			for(int j=bel[b[i]]-1;j;j--)
			{
				int o=b[i]-(b[i]-R[j]+p[i]-1)/p[i]*p[i];
				if(o<L[j])continue;
				if(!id[j][p[i]][R[j]-o][1])
				{
					id[j][p[i]][R[j]-o][1]=++tot;
					for(int k=o;k>=L[j];k-=p[i])ins(tot,k,(o-k)/p[i]);
				}
				ins(b[i],id[j][p[i]][R[j]-o][1],(b[i]-o)/p[i]);
			}
		}
	}
	spfa();
	if(f[ed]!=1061109567)printf("%d",f[ed]);
	else puts("-1");
}

巴邻旁之桥 Palembang Bridges

这题……一开始想的东西以为很对,但是网上没有一个做法是这样的……不知道到底行不行。就是确定了一座桥之后,另一座桥的最优位置是可以三分的,而且是随着第一座桥的移动单调移动的,于是我就想枚举第一座桥的位置,然后移动第二座桥寻找最优,但是有个问题,就是决策点可以回退一个位置,而一个位置可能有很多信息,于是就GG了……
网上有个题解是三分套三分,在这里就不讲了。主流都是这样做的:考虑 k = 1 k=1 k=1时,设桥的位置在 X X X,我们要求 min ⁡ ∑ ∣ S − X ∣ + ∣ T − X ∣ \min \sum|S-X|+|T-X| minSX+TX,就是数轴上找一个点到各点距离之和最小,这是个经典问题,就是找中位数。考虑 k = 2 k=2 k=2,有一个容易证明的结论,就是每组选择的桥都是离 S + T 2 S+T\over 2 2S+T较近的,所以可以按照 S + T S+T S+T排序,那么一定是前面一段选择第一座桥,后面一段选择第二座桥,用一个权值线段树维护中位数就可以了。

Code
#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define pa pair<int,int>
const int Maxn=100010,N=1e9;
const LL inf=(1LL<<60);
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<<3)+(x<<1)+(ch^48),ch=getchar();
	return x*f;
}
int k,n,m=0,h[Maxn<<1];LL ans=inf,sum[Maxn];
struct P{int x,y;}p[Maxn];
bool cmp(P a,P b){return a.x+a.y<b.x+b.y;}
char S1[3],S2[3];
int root[Maxn],tot=0,lc[Maxn*65],rc[Maxn*65],c[Maxn*65];LL s[Maxn*65];
void insert(int &u,int l,int r,int p)
{
	if(!u)u=++tot;
	c[u]++,s[u]+=p;
	if(l==r)return;
	int mid=l+r>>1;
	if(p<=mid)insert(lc[u],l,mid,p);
	else insert(rc[u],mid+1,r,p);
}
void merge(int &u1,int u2)
{
	if(!u1){u1=u2;return;}
	if(!u2)return;
	c[u1]+=c[u2],s[u1]+=s[u2];
	merge(lc[u1],lc[u2]),merge(rc[u1],rc[u2]);
}
int query_kth(int L,int R,int l,int r,int k)
{
	if(l==r)return l;
	int mid=l+r>>1;
	if(c[lc[R]]-c[lc[L]]>=k)return query_kth(lc[L],lc[R],l,mid,k);
	return query_kth(rc[L],rc[R],mid+1,r,k-(c[lc[R]]-c[lc[L]]));
}
LL C,S;
void query(int L,int R,int l,int r,int fl,int fr)
{
	if(!R)return;
	if(l==fl&&r==fr){C+=c[R]-c[L],S+=s[R]-s[L];return;}
	int mid=l+r>>1;
	if(fr<=mid)query(lc[L],lc[R],l,mid,fl,fr);
	else if(fl>mid)query(rc[L],rc[R],mid+1,r,fl,fr);
	else query(lc[L],lc[R],l,mid,fl,mid),query(rc[L],rc[R],mid+1,r,mid+1,fr);
}
int main()
{
	k=read(),n=read();LL o=0;
	for(int i=1;i<=n;i++)
	{
		int x,y;
		scanf("%s",S1);x=read();scanf("%s",S2);y=read();
		if(x>y)swap(x,y);
		if(S1[0]==S2[0])o+=y-x;
		else p[++m].x=x,p[m].y=y,h[(m<<1)-1]=x,h[m<<1]=y;
	}
	n=m;
	if(!n)return printf("%lld",o),0;
	if(k==1)
	{
		ans=n+o;
		sort(h+1,h+1+(n<<1));
		for(int i=1;i<=n;i++)ans+=((LL)abs(p[i].x-h[n])+abs(p[i].y-h[n]));
		printf("%lld",ans);
		return 0;
	}
	root[0]=0;
	sort(p+1,p+1+n,cmp);
	sum[0]=0;for(int i=1;i<=n;i++)sum[i]=sum[i-1]+p[i].x+p[i].y;
	for(int i=1;i<=n;i++)insert(root[i],0,N,p[i].x),insert(root[i],0,N,p[i].y),merge(root[i],root[i-1]);
	for(int i=1;i<n;i++)
	{
		LL ln=query_kth(root[0],root[i],0,N,i),rn=query_kth(root[i],root[n],0,N,n-i);
		LL L,R;
		C=S=0;query(root[0],root[i],0,N,0,(int)ln);L=C*ln-S+(sum[i]-S)-(i*2-C)*ln;
		C=S=0;query(root[i],root[n],0,N,0,(int)rn);R=C*rn-S+(sum[n]-sum[i]-S)-((n-i)*2-C)*rn;
		ans=min(ans,L+R);
	}
	printf("%lld",ans+n+o);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值