【元旦的胡策】省选限时练(最大费用流+状压dp)

74 篇文章 0 订阅

T1:

SDOI2009 最优图像image

题解:

正解为最大费用流,但是需要一步转化,最优方案要求乘积最大,对每个概率取对数后,可转化为求和最大,便于求解
知道了这个我就华丽丽的。。。。T掉了
本题需要注意对算法进行优化。比如,由于费用为实数,且是1..99的对数值,因此可以对其乘以一个较大的整数,然后转为整数处理。
最后,采用反复求最短路+增广的算法会面临超时的风险,需要采用多次增广的方法进行优化。
像什么取对数,开根号之类容易挂精度的,可以先乘一个很大的数
系统自带的有log,log2,log10,分别是以e,2,10为底,其中以2位底的会比另外两个快三倍,如果只是用来比大小,最后用log2。
注意设置常数写法,不要写加法= =

代码:

#include <cmath>
#include <queue>
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define INF 1e9
const int N=1e5;
int c[N];
int tot=-1,nxt[N],point[N],v[N],remind[N],last[N],dis[N],pip[105][105],d[N];
bool vis[N];
void addline(int x,int y,int cap,int cc)
{
    ++tot; nxt[tot]=point[x]; point[x]=tot; v[tot]=y; remind[tot]=cap; c[tot]=cc;
    ++tot; nxt[tot]=point[y]; point[y]=tot; v[tot]=x; remind[tot]=0; c[tot]=-cc;
}
int dfs(int now,int t,int limit)
{
    vis[now]=1;
    if(now==t||!limit) return limit;
    int flow=0,f;
    for (int i=point[now];i!=-1;i=nxt[i])
      if (dis[v[i]]==dis[now]+c[i] && !vis[v[i]] && remind[i])//这里要这样写 
      {
        f=dfs(v[i],t,min(remind[i],limit));//这样写要快很多 
        flow+=f;
        limit-=f;
        remind[i]-=f;
        remind[i^1]+=f;
        if(!limit) return flow;
      }
    return flow;
}
bool spfa(int s,int t)//找最长路
{
    queue<int>q;
    memset(dis,128,sizeof(dis));
    memset(vis,0,sizeof(vis));
    q.push(s);
    dis[s]=0;
    while (!q.empty())
    {
        int x=q.front(); q.pop(); vis[x]=0;
        for (int i=point[x];i!=-1;i=nxt[i])
          if (dis[v[i]]<dis[x]+c[i] && remind[i])
          {
            last[v[i]]=i;
            dis[v[i]]=dis[x]+c[i];
            if (!vis[v[i]]) vis[v[i]]=1,q.push(v[i]);
          }
    }
    return dis[t]>0;
}
void zkw(int s,int t)//多路优化 
{
    while(spfa(s,t))
     do
     {
        memset(vis,0,sizeof(vis));
        dfs(s,t,INF);
     }
     while(vis[t]);
}
int main()
{
    freopen("image.in","r",stdin);
    freopen("image.out","w",stdout);
    memset(point,-1,sizeof(point));
    int n,m,x;
    scanf("%d%d",&n,&m);
    int s=0,t=n+m+1;
    for (int i=1;i<=n;i++)
      for (int j=1;j<=m;j++)
      {
        pip[i][j]=tot+1;
        scanf("%d",&x); x*=N; double s=log2(x); s*=N; long long d=s;
        //精度优化 
        addline(i,j+n,1,d);
      }
    for (int i=1;i<=n;i++) scanf("%d",&x),addline(s,i,x,0);

    for (int i=1;i<=m;i++) scanf("%d",&x),addline(i+n,t,x,0);

    zkw(s,t);

    for (int i=1;i<=n;i++)
    {
        for (int j=1;j<=m;j++)
          if (remind[pip[i][j]]) printf("0");else printf("1");
        printf("\n");
    }
}

T2:

SDOI2009 学校食堂Dining

题解:

一看到这个题就弃疗了,暴力的话可以写个线段树╮(╯▽╰)╭
我们看到有 0Bi7 也就是说一个人的容忍不会超过7个人,这个东西一看就很好压
那么f[i][S][k]表示到第i个人为止,前面的人都选过了(i不一定),ta和ta后面的7个人还没选过压缩的集合为S,上一个选k号人的最优值

S我们可以用二进制表示,k我们只需要记录一个对i的相对位置就行,数值从-8到7
(上一次选择的人只有可能在[i-8,i+7]这个区间内,有i-8的原因是我可以在选i之前选ta,一样在忍耐程度内)

当我们循环到i的时候

  • i选过了,没有出现在后面的S中,就将f [i, S, k]转移给f [i+1, S’, k-1],这里S’多了第i+8个人
  • i没有选过,出现在后面的S中,则枚举S中的一个符合要求的人j作为k之后所取的那个人即将状态f [i, S, k]转移给f [i, S-{j}, j],不要忘了加上花费

根据转移可以知道枚举时第一维必须从小到大枚举,第二维必须从大到小枚举
(从全都没选过开始)
计算贡献的一点点优化:(a or b)-(a and b)=a xor b

代码:

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define INF 1e9
const int sum=(1<<8)-1;
int f[1005][260][20],t[1005],b[1005];
int main()
{
    freopen("dining.in","r",stdin);
    freopen("dining.out","w",stdout);
    int T,n,hh;scanf("%d",&T);
    while (T--)
    {
        scanf("%d",&n);
        for (int i=1;i<=n;i++) scanf("%d%d",&t[i],&b[i]);

        memset(f,0x7f,sizeof(f));
        f[1][sum][-1+10]=0;
        for (int i=1;i<=n;i++)
          for (int j=sum;j>=0;j--)
            for (int k=-8+10;k<=7+10;k++)
              if (f[i][j][k]<INF)
              {
                if ((j&1)==0)//选过i,没有出现在后面的集合里 
                  f[i+1][(j>>1)+(1<<7)][k-1]=min(f[i+1][(j>>1)+(1<<7)][k-1],f[i][j][k]);
                else//没有选i的时候,我们可以取在ta后面合法的 
                {
                    int mn=INF;
                    for (int kk=0;kk<=7;kk++) 
                      if (j&(1<<kk)) //没选过,可以选上
                      {
                        if (i+kk>mn) break;
                        mn=min(mn,i+kk+b[i+kk]);
                        //注意,合法的不仅仅要对于这一位i合法,更是后面的每一个 

                        if (i+k-10==0) hh=0;else hh=t[i+kk]^t[i+k-10];
                        f[i][j-(1<<kk)][kk+10]=min(f[i][j-(1<<kk)][kk+10],f[i][j][k]+hh);
                      }
                }
              }

        int ans=INF;
        for (int i=-8;i<=7;i++) ans=min(ans,f[n+1][sum][i+10]);
        printf("%d\n",ans);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值