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 ),在吃饭时每个座位上都有一个人;在吃完饭后的时候,每个人都需要选择一个新的座位(新座位可能和原来的座位是同一个),具体来说,第 𝑖 桌第 𝑗 个人的新座位只能在第 𝐿𝑖,𝑗 桌到第 𝑅𝑖,𝑗 桌中选,可以是这些桌中的任何一个座位。确定好新座位之后,大家开始移动,移动的体力消耗按照如下规则计算:
移动座位过程分为两步:
- 从起始桌移动到目标桌对应座位,这个过程中的体力消耗为两桌距离的两倍,即从第 𝑖 桌移动到第 𝑗 桌对应座位的体力消耗为 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;
}