thusc2017

巧克力

题目描述

“人生就像一盒巧克力,你永远不知道吃到的下一块是什么味道。”

明明收到了一大块巧克力,里面有若干小块,排成𝑛行𝑚列。每一小块都有自己特别的图案𝑐𝑖,𝑗,它们有的是海星,有的是贝壳,有的是海螺......其中还有一些因为挤压,已经分辨不出是什么图案了。明明给每一小块巧克力标上了一个美味值𝑎𝑖,𝑗 ( 0≤𝑎𝑖,𝑗≤106 ),这个值越大,表示这一小块巧克力越美味。

正当明明咽了咽口水,准备享用美味时,舟舟神奇地出现了。看到舟舟恳求的目光,明明决定从中选出一些小块与舟舟一同分享。

舟舟希望这些被选出的巧克力是连通的(两块巧克力连通当且仅当他们有公共边),而且这些巧克力要包含至少𝑘 ( 1≤𝑘≤5 )种。而那些被挤压过的巧克力则是不能被选中的。

明明想满足舟舟的愿望,但他又有点“抠”,想将美味尽可能多地留给自己。所以明明希望选出的巧克力块数能够尽可能地少。如果在选出的块数最少的前提下,美味值的中位数(我们定义𝑛个数的中位数为第\(\lfloor \frac{n+1}{2} \rfloor\)小的数)能够达到最小就更好了。

你能帮帮明明吗?

题解

中位数显然是可以二分答案的。

一开始以为选k个颜色是插头dp。后面才知道是个\(steiner\)树(如果你不会,就去学吧)。

然后我们有一个奇技淫巧,我们有k个集合,我们给每个颜色随机一个集合,然后做一遍\(steiner\)树。

至于dp时,我们可以把权值赋值成1000加减1,这样我们dp出来一定先满足选取的块最小的要求,然后在根据末位确定该中位数可不可行。

#include<bits/stdc++.h>
using namespace std;
typedef int sign;
typedef long long ll;
#define For(i,a,b) for(register sign i=(sign)(a);i<=(sign)(b);++i)
#define Fordown(i,a,b) for(register sign i=(sign)(a);i>=(sign)(b);--i)
const int N=250+5;
template<typename T>bool cmax(T &a,T b){return (a<b)?a=b,1:0;}
template<typename T>bool cmin(T &a,T b){return (a>b)?a=b,1:0;}
template<typename T>T read()
{
	T ans=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)&&ch!='-')ch=getchar();
	if(ch=='-')f=-1,ch=getchar();
	while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch-'0'),ch=getchar();
	return ans*f;
}
template<typename T>void write(T x,char y)
{
	if(x==0)
	{
		putchar('0'),putchar(y);
		return;
	}
	if(x<0)
	{
		putchar('-');
		x=-x;
	}
	static char wr[20];
	int top=0;
	for(;x;x/=10)wr[++top]=x%10+'0';
	while(top)putchar(wr[top--]);
	putchar(y);
}
void file()
{
#ifndef ONLINE_JUDGE
	freopen("chocolate.in","r",stdin);
	freopen("chocolate.out","w",stdout);
#endif
}
int n,m,k;
int c[N][N],a[N][N];
#define X(i) ((i-1)/m+1)
#define Y(i) ((i-1)%m+1)
#define XY(i,j) ((i-1)*m+j)
void input()
{
	n=read<int>(),m=read<int>(),k=read<int>();
	For(i,1,n)For(j,1,m)c[i][j]=read<int>();
	For(i,1,n)For(j,1,m)a[i][j]=read<int>();
}
const int inf=0x3f3f3f3f;
int Min;
int cl[N][N],id[N];
int dp[1<<5][N];
int val[N];
queue<int>q;
int vis[N];
int dx[4]={0,1,-1,0},dy[4]={1,0,0,-1};
void spfa(int s)
{
	int u,x,y,t1,t2,t3;
	while(!q.empty())
	{
		u=q.front();q.pop();
		x=X(u),y=Y(u);
		vis[u]=0;
		For(i,0,3)
		{
			t1=x+dx[i],t2=y+dy[i];
			if(t1<=n&&t2<=m&&t1>=1&&t2>=1&&c[t1][t2]!=-1)
			{
				t3=XY(t1,t2);
				if(cmin(dp[s][t3],dp[s][u]+val[t3])&&!vis[t3])	
				{
					vis[t3]=1;q.push(t3);
				}
			}
		}
	}
	For(i,1,n*m)for(int l=(s-1)&s;l;l=(l-1)&s)cmin(dp[l][i],dp[s][i]);
}
void steiner(int mid)
{
	memset(dp,inf,sizeof dp);
	For(i,1,n)For(j,1,m)if(c[i][j]!=-1)dp[1<<(id[c[i][j]]-1)][XY(i,j)]=val[XY(i,j)];
	int Max=(1<<k)-1;
	For(s,1,Max)
	{
		For(i,1,n)For(j,1,m)if(c[i][j]!=-1)
		{
			int ij=XY(i,j);
			for(int l=(s-1)&s;l;l=(l-1)&s)
				cmin(dp[s][ij],dp[l][ij]+dp[l^s][ij]-val[ij]);
		
			if(dp[s][ij]<inf)q.push(ij),vis[ij]=1;
		}
		spfa(s);
	}
	For(i,1,n*m)cmin(Min,dp[Max][i]);
}
int sum[N],top;
void work()
{
	top=0;
	For(i,1,n)For(j,1,m)if(c[i][j]!=-1)sum[++top]=a[i][j];
	sort(sum+1,sum+top+1);
	top=unique(sum+1,sum+top+1)-sum-1;
	int l=1,r=top,mid,ans1=-1,ans2=-1;
	while(l<=r)
	{
		mid=(l+r)>>1;
		Min=inf;
		For(i,1,n)For(j,1,m)if(c[i][j]!=-1)val[XY(i,j)]=1000+(a[i][j]>sum[mid]?1:-1);
		For(T,1,200)
		{
			For(i,1,n)For(j,1,m)cl[i][j]=rand()%k+1;	
			memset(id,0,sizeof id);
			For(i,1,n)For(j,1,m)if(c[i][j]!=-1&&!id[c[i][j]])id[c[i][j]]=cl[i][j];
			
			int p=0;
			For(i,1,n)For(j,1,m)if(c[i][j]!=-1)p|=(1<<(id[c[i][j]]-1));
			if(p==(1<<k)-1)
			{
			//	For(i,1,n)For(j,1,m)write(c[i][j]==-1?-1:id[c[i][j]],j==m?'\n':' ');
			//	cout<<endl;
				steiner(mid);
			}
		}
	//	cout<<Min<<endl;
		ans1=(Min+500)/1000;
		if(Min<=ans1*1000)ans2=sum[mid],r=mid-1;
		else l=mid+1;
	}
	write(ans1,' '),write(ans2,'\n');
}
int main()
{
	srand(19260817);
	file();
	int T=read<int>();
	while(T--)
	{
		input();
		work();
	}
	return 0;
}


题目背景

班级聚会的时候,班主任为了方便管理,规定吃饭的时候同一个寝室的同学必须坐在一起;但是吃完饭后,到了娱乐时间,喜欢不同游戏的同学会聚到一起;在这个过程中就涉及到了座位分配的问题。

题目描述

有 𝑛 张圆桌排成一排(从左到右依次编号为 0 到 𝑛−1 ),每张桌子有 𝑚 个座位(按照逆时针依次编号为 0 到 𝑚−1 ),在吃饭时每个座位上都有一个人;在吃完饭后的时候,每个人都需要选择一个新的座位(新座位可能和原来的座位是同一个),具体来说,第 𝑖 桌第 𝑗 个人的新座位只能在第 𝐿𝑖,𝑗 桌到第 𝑅𝑖,𝑗 桌中选,可以是这些桌中的任何一个座位。确定好新座位之后,大家开始移动,移动的体力消耗按照如下规则计算:

移动座位过程分为两步:

  1. 从起始桌移动到目标桌对应座位,这个过程中的体力消耗为两桌距离的两倍,即从第 𝑖 桌移动到第 𝑗 桌对应座位的体力消耗为 2×|𝑖−𝑗|;

2.从目标桌的对应座位绕着桌子移动到目标座位,由于桌子是圆的,所以客人会选择最近的方向移动,体力消耗为移动距离的一倍,即从编号为 𝑥 的座位移动的编号为 𝑦 的座位的体力消耗为 min(|𝑥−𝑦|,𝑚−|𝑥−𝑦|);

现在,给定每个客人的限制(即每个人的新座位所在的区间),需要你设计一个方案,使得所有客人消耗的体力和最小;本题中假设客人在移动的时候互不影响。

题解

看下很显然是个费用流,但是暴力连边边数众多,考虑线段树优化建边。

对每个位置开一棵线段树,线段树上是每张桌子。然后左边和右边的要分开建边。

线段树上在处理一下跨桌子的体力消耗即可,

具体的,拿往左边的线段树举例,我们现在消耗的费用可以到该区间的右端点,那往左子树走时就要在付出代价。

#include<bits/stdc++.h>
using namespace std;
typedef int sign;
typedef long long ll;
#define For(i,a,b) for(register sign i=(sign)(a);i<=(sign)(b);++i)
#define Fordown(i,a,b) for(register sign i=(sign)(a);i>=(sign)(b);--i)
const int N=1e6+5,M=2e7+5;
template<typename T>bool cmax(T &a,T b){return (a<b)?a=b,1:0;}
template<typename T>bool cmin(T &a,T b){return (a>b)?a=b,1:0;}
template<typename T>T read()
{
	T ans=0,f=1;
	char ch=getchar();
	while(!isdigit(ch)&&ch!='-')ch=getchar();
	if(ch=='-')f=-1,ch=getchar();
	while(isdigit(ch))ans=(ans<<3)+(ans<<1)+(ch-'0'),ch=getchar();
	return ans*f;
}
template<typename T>void write(T x,char y)
{
	if(x==0)
	{
		putchar('0'),putchar(y);
		return;
	}
	if(x<0)
	{
		putchar('-');
		x=-x;
	}
	static char wr[20];
	int top=0;
	for(;x;x/=10)wr[++top]=x%10+'0';
	while(top)putchar(wr[top--]);
	putchar(y);
}
void file()
{
#ifndef ONLINE_JUDGE
	freopen("seat.in","r",stdin);
	freopen("seat.out","w",stdout);
#endif
}
int n,m;
int L[305][15],R[305][15];
void input()
{
	n=read<int>(),m=read<int>();
	For(i,1,n)For(j,1,m)L[i][j]=read<int>()+1;
	For(i,1,n)For(j,1,m)R[i][j]=read<int>()+1;
}
int head[N],tt=1;
struct edge
{
	int v,flow,w,nex;
}e[M];
void add(int x,int y,int flow,int w)
{
//	cout<<x<<' '<<y<<' '<<w<<endl;
	e[++tt]=(edge){y,flow,w,head[x]},head[x]=tt;
	e[++tt]=(edge){x,0,-w,head[y]},head[y]=tt;
}
const int inf=0x3f3f3f3f;
int tot;
#define mid ((l+r)>>1)
int ls[N],rs[N];
struct segment_tree
{
	int rt,id[305];
	void build(int &h,int l,int r,int type)
	{
		if(l==r)h=id[l];
		else
		{
			h=++tot;
			build(ls[h],l,mid,type),build(rs[h],mid+1,r,type);
			add(h,ls[h],inf,type==1?(r-mid)*2:0);
			add(h,rs[h],inf,type==2?(mid+1-l)*2:0);
		}
	}
	void update(int h,int l,int r,int s,int t,int type,int id,int pos)
	{
		if(s<=l&&r<=t)
		{
			//cerr<<id<<' '<<h<<endl;
			add(id,h,1,type==1?(pos-r)*2:(l-pos)*2);
		}
		else
		{
			if(s<=mid)update(ls[h],l,mid,s,t,type,id,pos);
			if(mid<t)update(rs[h],mid+1,r,s,t,type,id,pos);
		}
	}
}tl[11],tr[11];
#define XY(i,j) ((i-1)*m+j)
int S,T;
int min_cost,vis[N];
int dis[N];
deque<int>q;
int spfa()
{
	//cerr<<1<<endl;
	For(i,1,tot)dis[i]=inf;
	dis[S]=0;
	q.push_back(S);
	int u,v;
	while(!q.empty())
	{
		u=q.front(),q.pop_front();
		vis[u]=0;
		//cerr<<u<<endl;
		for(register int i=head[u];i;i=e[i].nex)
		{
			v=e[i].v;
			//cerr<<u<<' '<<v<<' '<<dis[u]<<' '<<dis[v]<<endl;
			if(e[i].flow&&cmin(dis[v],dis[u]+e[i].w)&&!vis[v])
			{
				//cerr<<v<<endl;
				vis[v]=1;
				if(!q.empty()&&dis[v]<=dis[q.front()])q.push_front(v);
				else q.push_back(v);
			}
		}
	}
	return dis[T]^inf;
}
int cur[N];
int dfs(int u,int flow)
{
	if(u==T||!flow)return flow;
	int f,sum=0,v;
	vis[u]=1;
	for(register int &i=cur[u];i&&flow;i=e[i].nex)
	{
		v=e[i].v;
		if(dis[v]==dis[u]+e[i].w&&!vis[v]&&e[i].flow)
		{
			f=dfs(v,min(flow,e[i].flow));
			sum+=f;flow-=f;
			min_cost+=f*e[i].w;
			e[i].flow-=f,e[i^1].flow+=f;
		}
	}
	vis[u]=0;
	return sum;
}
int mcmf()
{
	int res=0;
	while(spfa())
	{
		//cerr<<2<<endl;
		For(i,1,tot)cur[i]=head[i];
		res+=dfs(S,inf);
	}
	//cout<<res<<endl;
	return res;
}
void work()
{
	tot=n*m;
	
	For(j,1,m)For(i,1,n)tl[j].id[i]=tr[j].id[i]=++tot;
	
	S=++tot,T=++tot;
		
	//cout<<S<<' '<<T<<endl;
	For(i,n*m+1,n*m*2)add(i,T,1,0);
		
	For(i,1,n)For(j,1,m)
	{
		add(tl[j].id[i],tr[j==1?m:j-1].id[i],inf,1);
		add(tl[j].id[i],tr[j==m?1:j+1].id[i],inf,1);
	}

	For(i,1,m)tl[i].build(tl[i].rt,1,n,1),tr[i].build(tr[i].rt,1,n,2);
	
	For(i,1,n*m)add(S,i,1,0);

	For(i,1,n)For(j,1,m)
	{
		if(R[i][j]<=i)tl[j].update(tl[j].rt,1,n,L[i][j],R[i][j],1,XY(i,j),i);
		else if(L[i][j]>i)tr[j].update(tr[j].rt,1,n,L[i][j],R[i][j],2,XY(i,j),i);
		else
		{
			tl[j].update(tl[j].rt,1,n,L[i][j],i,1,XY(i,j),i);
			tr[j].update(tr[j].rt,1,n,i+1,R[i][j],2,XY(i,j),i);
		}
	}
//	cerr<<mcmf()<<endl;
	if(mcmf()<n*m)puts("no solution");
	else write(min_cost,'\n');
}
int main()
{
	file();
	input();
	work();
	return 0;
}


posted @ 2018-12-26 22:03  dyx_diversion  阅读(446)  评论(0编辑  收藏  举报