zoukankan      html  css  js  c++  java
  • APIO2015&2014题解

    传送门:似乎uoj都有

    思路:

    APIO2015:

    巴厘岛的雕塑:

    看到位运算,又要求结果最小,最外层肯定是个从高位到低位的按位贪心

    这里有两个部分分,

    task1:N<=100,1<=A<=B<=N

    task2:N<=2000,A=1,1<=B<=N


    先考虑task1

    令sum[i]表示雕塑权值的前缀和

    假设我们考虑到了第bit位

    那么我们怎么知道在前面位数满足要求的前提下,当前位能否是0

    DP即可

    设f[i][j]表示前i个雕塑,有j个组,并且前面位数满足要求,当前位是否能为0

    那么f[i][j]|=f[k][j-1]&&((nowans|(sum[i]-sum[j]))==nowans)

    为了方便判断,我们先把nowans后面的位都赋成1


    最后如果f[n][A--B]中有为1的,则该位可取0,继续下一位的DP

    复杂度O(n^3*log(M))


    对于task2,n变大了,但是有了一个限制条件,A=1

    这就是说,组数没有了下限,只有上限

    我们可以转变状态设计,令g[i]表示前i个,在满足前面位数满足要求的前提下,最少要多少组

    转移更简单了,g[i]=min(g[j]+1)     ((nowans|(sum[i]-sum[j]))==nowans)

    最后判断g[n]是否<=B即可


    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    const int maxn=2010;
    typedef long long ll;
    using namespace std;
    int n,A,B,a[maxn],len,g[maxn];ll sum[maxn],pw[50];bool f[105][105];
    
    void work1(){
    	ll ans=0;
    	for (int bit=len;bit>=0;bit--){
    		ans+=(pw[bit]-1);
    		memset(f,0,sizeof(f)),f[0][0]=1;
    		for (int i=1;i<=n;i++)
    			for (int j=1;j<=i;j++){
    				for (int k=0;k<i;k++){
    					ll s=sum[i]-sum[k];
    					f[i][j]|=(f[k][j-1]&&((s|ans)==ans));
    					if (f[i][j]) break;
    				}
    			}
    		bool can=0;
    		for (int i=A;i<=B;i++) if (f[n][i]){can=1;break;}
    		if (!can) ans++;
    		else ans-=(pw[bit]-1);
    	}
    	printf("%lld
    ",ans);
    }
    
    void work2(){
    	ll ans=0;
    	for (int bit=len;bit>=0;bit--){
    		ans+=(pw[bit]-1);
    		memset(g,63,sizeof(g)),g[0]=0;
    		for (int i=1;i<=n;i++)
    			for (int j=0;j<i;j++)
    				if (((sum[i]-sum[j])|ans)==ans)
    					g[i]=min(g[i],g[j]+1);
    		if (g[n]<=B) ans-=(pw[bit]-1);
    		else ans++;
    	}
    	printf("%lld
    ",ans);
    }
    
    int main(){
    	scanf("%d%d%d",&n,&A,&B);
    	for (int i=1;i<=n;i++) scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
    	for (ll tmp=1;tmp<=sum[n];tmp<<=1) len++;
    	pw[0]=1;for (int i=1;i<=len;i++) pw[i]=pw[i-1]<<1;
    	if (A==1) work2();
    	else work1();
    	return 0;
    }
    

    雅加达的摩天大楼

    思路:显然是一个最短路模型

    首先点数为O(n+m),边数为O(n*m)的建图很好像

    doge在一边,楼在一边

    每只doge向它所在的楼连权值为0的双向边,向所有它能到的楼连权值为步数的单向边,跑一遍最短路即可

    然后我们发现,每个doge连出去的边是n/p的,那么p很大时我们可以用这种暴力连边

     

    那p很小时呢?

    每栋楼多建出p个辅助点,相当于一栋楼的很多层,i层代表的意义是它能跳到x-i和x+i栋楼

    我们发现一只doge的步长是固定的

    那么这只doge只能跳到它能跳到的楼的对应层

    到楼底才可以见到这栋楼的狗


    因为卡空间,p的分界线设为100即可。

    spfa在uoj会被卡........

    bzoj可过

    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    const int maxb=105,maxn=30010*105,maxm=maxn*5;
    using namespace std;
    int n,m,b[30010],p[30010],dis[maxn],pre[maxm],now[maxn],son[maxm],val[maxm],tot,sz=100,S,T,q[maxn+10],head,tail,inf;bool bo[maxn];
    int id(int a,int b){return a*n+b;}
    void add(int a,int b,int c){pre[++tot]=now[a],now[a]=tot,son[tot]=b,val[tot]=c;}
    
    void spfa(){
    	memset(dis,63,sizeof(dis)),inf=dis[0];
    	dis[S]=head=0,bo[S]=1,q[tail=1]=S;
    	while (head!=tail){
    		if (++head>maxn) head=1;
    		int x=q[head];
    		//printf("%d %d
    ",x,dis[x]);
    		for (int y=now[x];y;y=pre[y])
    			if (dis[son[y]]>dis[x]+val[y]){
    				dis[son[y]]=dis[x]+val[y];
    				if (!bo[son[y]]){
    					if (++tail>maxn) tail=1;
    					q[tail]=son[y],bo[son[y]]=1;
    				}
    			}
    		bo[x]=0;
    	}
    	printf("%d
    ",dis[T]==inf?-1:dis[T]);
    }
    
    int main(){
    	//freopen("test.in","r",stdin);freopen("test.out","w",stdout);
    	scanf("%d%d",&n,&m),sz=min((int)sqrt(n),100);
    	for (int i=1;i<=m;i++) scanf("%d%d",&b[i],&p[i]),b[i]++;
    	S=b[1],T=b[2];
    	for (int i=1;i<=sz;i++) for (int j=1;j<=n;j++) add(id(i,j),j,0);//到了这栋楼,才可以下楼换狗
    	for (int i=1;i<=sz;i++)
    		for (int j=1;j<=n-i;j++){
    			int x=id(i,j),y=id(i,j+i);
    			add(x,y,1),add(y,x,1);//楼与楼对应层连边
    		}
    	for (int i=1;i<=m;i++){
    		if (p[i]<=sz) add(b[i],id(p[i],b[i]),0);//上楼,限制步长
    		else{
    			for (int j=1;b[i]+j*p[i]<=n;j++) add(b[i],b[i]+j*p[i],j);//暴力连边
    			for (int j=1;b[i]-j*p[i]>=1;j++) add(b[i],b[i]-j*p[i],j);
    		}
    	}
    	spfa();
    	return 0;
    }

    巴邻旁之桥

    三分套三分秒切此题

    虽然没有精度误差,但时间上因为是两个log,log还特别大,三分已被卡飞了


    我们需要更科学的方法

    首先家和单位在同一侧的,直接计算答案,你们要桥有什么用啊

    注意桥也有1的长度

    k==1时,建在所有家和单位的中位数处即可

    k==2时,可以发现对于每个人,走离家和单位的中点更近的桥不会更差(可能相同)

    那么把每个人按家和单位的中点排序后,一定存在一个分解点使得中点在左边的走左边的桥,中点在右边的走右边的桥

    那么左右就变成了两个K=1的情况

    分别找到中位数,修桥即可


    实现上用两个权值线段树

    动态维护两段的中位数即可

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    typedef long long ll;
    #define abs(a) (a>0?a:-(a))
    const int maxn=200010;
    using namespace std;
    struct data{
    	int l,r,idl,idr,v;
    	inline ll dis(ll bri){return abs(l-bri)+abs(r-bri);}
    }seq[maxn];
    int n,K,a[maxn],tot,cnt,b[maxn];char c1[2],c2[2];ll ans;
    bool cmp(data a,data b){return a.v<b.v;}
    
    struct Tseg{
    	#define ls (p<<1)
    	#define rs ((p<<1)|1)
    	#define mid ((l+r)>>1)
    	int val[maxn<<2];
    	void modify(int p,int l,int r,int x,int v){
    		if (l==r){val[p]+=v;return;}
    		//printf("%d %d %d %d %d
    ",p,l,r,x,v);
    		if (x<=mid) modify(ls,l,mid,x,v);
    		else modify(rs,mid+1,r,x,v);
    		val[p]=val[ls]+val[rs];
    	}
    	void modify(int x,int v){modify(1,1,tot,x,v);}
    	int query(int p,int l,int r,int rk){
    		if (l==r) return l;
    		if (rk<=val[ls]) return query(ls,l,mid,rk);
    		else return query(rs,mid+1,r,rk-val[ls]);
    	}
    	int query(int rk){return query(1,1,tot,rk);}
    	#undef mid
    	#undef ls
    	#undef rs
    }T[2];
    
    void work1(){
    	for (int i=1,l,r;i<=n;i++){
    		scanf("%s%d%s%d",c1,&l,c2,&r);
    		if (l>r) swap(l,r);
    		if (c1[0]==c2[0]) ans+=(r-l);
    		else a[++tot]=l,a[++tot]=r,ans++;
    		//printf("%d %d
    ",l,r);
    	}
    	sort(a+1,a+1+tot);
    	int mid=a[tot/2];
    	for (int i=1;i<=tot;i++) ans+=abs(a[i]-mid);
    	printf("%lld
    ",ans);
    }
    
    void prework(){
    	for (int i=1;i<=cnt;i++) b[++tot]=seq[i].l,b[++tot]=seq[i].r;
    	sort(b+1,b+1+tot);
    	for (int i=1;i<=cnt;i++){
    		seq[i].idl=lower_bound(b+1,b+1+tot,seq[i].l)-b;
    		seq[i].idr=lower_bound(b+1,b+1+tot,seq[i].r)-b;
    	}
    }
    
    ll getans(){
    	ll ans0=0,ans1=0,mins=1e9+7,mid0=0,mid1;
    	prework();
    	for (int i=1;i<=cnt;i++) T[1].modify(seq[i].idl,1),T[1].modify(seq[i].idr,1),seq[i].v=seq[i].l+seq[i].r;
    	sort(seq+1,seq+1+cnt,cmp),mid1=b[cnt+1];
    	for (int i=1;i<=cnt;i++) ans1+=seq[i].dis(mid1);mins=ans1;
    	//for (int i=1;i<=cnt;i++) printf("l=%d r=%d v=%d
    ",seq[i].l,seq[i].r,seq[i].v);
    	//printf("mins=%lld mid0=%lld mid1=%lld
    ",mins,mid0,mid1);
    	for (int i=1;i<=cnt;i++){
    		ans1-=seq[i].dis(mid1);
    		T[0].modify(seq[i].idl,+1),T[0].modify(seq[i].idr,+1),mid0=b[T[0].query(i)];
    		T[1].modify(seq[i].idl,-1),T[1].modify(seq[i].idr,-1),mid1=b[T[1].query(cnt-i+1)];
    		ans0+=seq[i].dis(mid0);
    		mins=min(mins,ans0+ans1);
    	}
    	//printf("%lld
    ",mins);
    	return mins;
    }
    
    void work2(){
    	for (int i=1,l,r;i<=n;i++){
    		scanf("%s%d%s%d",c1,&l,c2,&r);
    		if (l>r) swap(l,r);
    		if (c1[0]==c2[0]) ans+=(r-l);
    		else ans++,seq[++cnt]=(data){l,r};
    	}
    	printf("%lld
    ",ans+getans());
    }
    
    int main(){
    	scanf("%d%d",&K,&n);
    	if (K==1) work1();else work2();
    	return 0;
    }

    APIO2014:

    连珠线:

    首先我们可以发现,定根后,蓝线一定是fa[i],i,son[i]的形式

    那么我们枚举根,设f[i][0/1]分别表示i子树,i不是蓝线中点,是蓝线中点的最大权值

    那么f[i][0]=Σmax(f[j][0],f[j][1]+val[i,j]) j是i的儿子


    f[i][1]=f[i][0]+max(f[j][0]+val[i,j]-max(f[j][0],f[j][1]+val[i,j]))

    表示可以选择一个连向自己的,但是要减去之前统计的


    但是枚举根肯定不行

    考虑怎样换根

    我们发现换根时只有这两个点的f值变化,

    在第一遍DP时记录f[i][0/1]转移式后面那一堆max(f[j][0]+val[i,j]-max(f[j][0],f[j][1]+val[i,j]))的最大和次大

    那么我们把这两个点的值重新转移一遍

    设原根是x,现在的根是y

    现在y咸鱼翻身啦,它不是x的儿子了

    从x中去除y的贡献,之所以要记录次大,是因为y正好为x儿子中的最大时,x此时只能由次大来转移

    然后把x当做y的一个新儿子,转移一下即可

    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    const int maxn=200010,maxm=maxn<<1,inf=2e9+7;
    using namespace std;
    int n,pre[maxm],now[maxn],son[maxm],tot,val[maxm],f[maxn][2],maxs[maxn],sec[maxn],deg[maxn],ans;
    void add(int a,int b,int c){pre[++tot]=now[a],now[a]=tot,son[tot]=b,val[tot]=c,deg[b]++;}
    
    void read(int &x){
    	char ch;
    	for (ch=getchar();!isdigit(ch);ch=getchar());
    	for (x=0;isdigit(ch);ch=getchar()) x=x*10+ch-'0'; 
    }
    
    void dfs(int x,int fa){
    	if (deg[x]==1&&fa){
    		f[x][0]=0,f[x][1]=-inf;
    		return;
    	}
    	maxs[x]=sec[x]=-inf;
    	for (int y=now[x];y;y=pre[y])
    		if(son[y]!=fa){
    			dfs(son[y],x);
    			int tmp=max(f[son[y]][0],f[son[y]][1]+val[y]);
    			f[x][0]+=tmp,f[x][1]+=tmp;
    			sec[x]=max(sec[x],f[son[y]][0]+val[y]-tmp);
    			if (sec[x]>maxs[x]) swap(sec[x],maxs[x]);
    		}
    	f[x][1]+=maxs[x];
    }
    
    void moveto(int x,int fa){
    	ans=max(ans,f[x][0]);
    	for (int y=now[x];y;y=pre[y])
    		if (son[y]!=fa){
    			int tmp=max(f[son[y]][0],f[son[y]][1]+val[y]),f0=f[x][0],f1=f[x][1];
    			if (f[son[y]][0]+val[y]-tmp==maxs[x])
    				f0-=tmp,f1=f1-tmp-maxs[x]+sec[x];
    			else 
    				f0-=tmp,f1=f1-tmp;
    			int vx=max(f0,f1+val[y]);
    			f[son[y]][0]+=vx,f[son[y]][1]+=vx;
    			f[son[y]][1]-=maxs[son[y]];
    			int delta=f0+val[y]-vx;
    			sec[son[y]]=max(sec[son[y]],delta);
    			if (sec[son[y]]>maxs[son[y]]) swap(sec[son[y]],maxs[son[y]]);
    			f[son[y]][1]+=maxs[son[y]];
    			moveto(son[y],x);
    		}
    }
    
    int main(){
    	scanf("%d",&n);
    	for (int i=1,x,y,z;i<n;i++)
    		read(x),read(y),read(z),add(x,y,z),add(y,x,z);
    	dfs(1,0),moveto(1,0),printf("%d
    ",ans);
    	return 0;
    }

    回文串:

    见:http://blog.csdn.net/thy_asdf/article/details/47615439

    序列分割:

    化一化式子,就发现是比较裸的斜率优化了

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ll long long
    const int maxn=100010;
    using namespace std;
    int n,k,cnt,a[maxn],i,q[maxn],now=0,pre=1;
    ll f[maxn][2],sum[maxn];
    double slope(int i,int j){return !(sum[i]-sum[j])?1e9:1.0*((f[i][pre]-sum[i]*sum[i])-(f[j][pre]-sum[j]*sum[j]))/(double)(sum[i]-sum[j]);}
    
    void work(int x){
    	int head=1,tail=0;
    	for (int i=1;i<=n;i++){
    //		printf("%I64d %d
    ",sum[i],head);
    		while (head<tail&&slope(q[head],q[head+1])>-sum[i]) head++;
    		int j=q[head];/*printf("j%d
    ",q[head]);*/f[i][now]=f[j][pre]+(sum[i]-sum[j])*sum[j];
    //		printf("%I64d
    ",sum[i]-sum[j]);
    		while (head<tail&&slope(q[tail],q[tail-1])<slope(q[tail],i)) tail--;
    //		printf("%d
    ",tail);
    		q[++tail]=i;
    	}
    	now^=1,pre^=1;
    }
    
    int main(){
    	scanf("%d%d",&n,&k);
    	for (i=1;i<=n;i++) scanf("%d",&a[i]),sum[i]=sum[i-1]+a[i];
    	for (i=1;i<=k;i++) work(i);
    	printf("%lld
    ",f[n][pre]);
    	return 0;
    }



  • 相关阅读:
    03-字典
    02-列表
    01-字符串操作
    Django中的跨域问题
    Codeforces Round #617 (Div. 3) A
    Codeforces Round #717 (Div. 2) A
    如何在Vuespa中使用less
    excle导出
    ajaxFileUpload上传文件
    图片插入word
  • 原文地址:https://www.cnblogs.com/thythy/p/5493631.html
Copyright © 2011-2022 走看看