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;
}

转载于:https://www.cnblogs.com/dengyixuan/p/10182128.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值