2014湘潭全国邀请赛I题 Intervals /POJ 3680 / 在限制次数下取有权区间使权最大/小问题(费用流)

先说POJ3680:给n个有权(权<10w)开区间(n<200),(区间最多数到10w)保证数轴上所有数最多被覆盖k次的情况下要求总权最大,输出最大权。

  思路:       限制的处理:s-->开始流量为k,要求总权最大,即费用最大,所以费用取负,最小费用最大流即可。对于输入区间[a,b]:w,添加边:a-->b,流量为1,费用为-w。

                  对于点i,i+1,添加边,费用为0,流量无穷。显然这种处理,限制了区间最多取k次,(流量控制),跑最大流能走添加的边尽量走,且越大越好(负数刚刚是最小费用),满足题意。但是TLE,因为w到10W,边10W,必然超时    ,所以点要处理,  所有点“压缩”,向前推进,只要存在的点,前一个向后一个连边即可,详见代码。


再看湘潭的这题: 问题相反:给n个有权(权<10w)开区间(n<2000),(区间最多数到10的9次),保证区间【1-,m】最少被覆盖k次的情况下要求总权最小,输出最小权。

 思路:(感激zz1215的建图提示)    限制的处理:显然在出口处流量必需达到k才算有解。对于输入区间[a,b]:w,添加边:a-->b,流量为1,费用为w,但是这样处理,点都是离散的,根本没有体现连续性,

不可能像上题那样建图(否则费用为0),所以:这样:点I向它前一个点连边,费用为0,流量为无穷,这样巧妙的解决了离散点问题。跑最小费用即可。显然,之前要处理点。


#include<iostream>
#include<queue>
#include<cstdio>
using namespace std;
const int inf=0x3f3f3f3f;
const int t=100000;
int n,k;
int e[300001][4];int head[100101];int nume=0;
void inline adde(int from,int to,int w,int c)
{
    e[nume][0]=to;e[nume][1]=head[from];head[from]=nume;
    e[nume][2]=w;e[nume++][3]=c;
    e[nume][0]=from;e[nume][1]=head[to];head[to]=nume;
    e[nume][2]=-w;e[nume++][3]=0;
}
int inq[111005];int d[110000];              //spfa
bool spfa(int & sumcost)               //每次求费用
{
    int pre[110005];
    int minf=inf;
    int prv[110005];
    for(int i=0;i<=t+1;i++)
      {
          inq[i]=0;d[i]=inf;
      }
    pre[0]=-1 ; prv[0]=-1;       //路径中,分别记录到点i的边,和i之前的点。(这题如果用矩阵建图要方便)
    queue<int>q;
    q.push(0);inq[0]=1;d[0]=0;
    while(!q.empty())
    {
        int cur=q.front();
        q.pop();
        inq[cur]=0;
         for(int i=head[cur];i!=-1;i=e[i][1])
         {
             int v=e[i][0];
             if(e[i][3]>0&&e[i][2]+d[cur]<d[v])
             {
                 d[v]=e[i][2]+d[cur];
                 prv[v]=cur;               //记录增广路
                 pre[v]=i;
                 if(!inq[v])
                 {
                     q.push(v);
                     inq[v]=1;
                 }
             }
         }
    }
     if(d[t+1]==inf)return 0;       //无法增广
     int cur=t+1;                   //目的点
     while(cur!=0)              //取路径上最小残流量边作为流量增广
    {
        minf=e[pre[cur]][3]<minf?e[pre[cur]][3]:minf;
        cur=prv[cur];
    }
    cur=t+1;
    while(cur!=0)                 //增广,改流量
    {
         e[pre[cur]][3]-=minf;
         e[pre[cur]^1][3]+=minf;
         cur=prv[cur];
    }
   sumcost+=d[t+1]*minf;         //费用为单位费用(该路径下每条边单位流量之和)*流量
   return 1;
}
void mincost(int & sumcost)
{
    while(spfa(sumcost)) ;     //无法增广为止
    return ;
}
int hash[100011];
void clear()
{
    nume=0;
    for(int i=0;i<=t+1;i++)
      {
          hash[i]=head[i]=-1;
      }
}
struct qujian
{
    int a,b,w;
};
int main()
{
   int T;
    scanf("%d",&T);
   for(int ii=1;ii<=T;ii++)
   {
       clear();
       cin>>n>>k;
       int a,b,w;
        vector<qujian>qq(n);
        vector<int>v;
       for(int i=0;i<n;i++)
       {
           scanf("%d%d%d",&a,&b,&w);
            hash[a]=hash[b]=1;            
            qq[i].a=a;qq[i].b=b;qq[i].w=w;
            adde(a,b,-w,1);
       }
       for(int i=0;i<100010;i++)     //处理“存在”的点
         if(hash[i]==1)
         {
             v.push_back(i);
         }
       for(int i=0;i<v.size()-1;i++)   //“存在”的点连边
       {
           adde(v[i],v[i+1],0,k);
       }
       adde(v[v.size()-1],t,0,k);              //超级源汇点的边
       adde(0,v[0],0,k);adde(t,t+1,0,k);
         int sumcost=0;
       mincost(sumcost);
       cout<<-sumcost<<endl;              //相反数
   }
   return 0;
}


湘潭:

#include<iostream>
#include<queue>
#include<algorithm>
using namespace std;
const int inf=0x3f3f3f3f;
int n,k,m;
int countv=0;
int f[4009];
void getf(int x)     // 把点1到10的9次(最多4000个点),压缩到4000以内,一一对应,以便建图。
{
    if(x>m){f[countv]=x;return ;}    //大于m的数没用,相当于m。
    countv++;
    f[countv]=x;
}
int getv(int x)         //获取对应点
{
    if(x>=m){return countv;}
    for(int i=1;i<=countv;i++)
    {
        if(f[i]==x)return i;
    }
}
int e[20001][4];int head[4005];int nume=0;
void inline adde(int from,int to,int w,int c)
{
    e[nume][0]=to;e[nume][1]=head[from];head[from]=nume;
    e[nume][2]=w;e[nume++][3]=c;
    e[nume][0]=from;e[nume][1]=head[to];head[to]=nume;
    e[nume][2]=-w;e[nume++][3]=0;
}
int inq[4005];int d[4005];              //spfa
bool spfa(int & sumcost,int &sumflow)               //每次求费用
{
    int pre[4005];
    int minf=inf;
    int prv[4005];
    for(int i=0;i<=countv+4;i++)
      {
          inq[i]=0; d[i]=inf;
      }
    pre[0]=-1 ; prv[0]=-1;       //路径中,分别记录到点i的边,和i之前的点。(这题如果用矩阵建图要方便)
    queue<int>q;
    q.push(0);inq[0]=1;d[0]=0;
    while(!q.empty())
    {
        int cur=q.front();
        q.pop();
        inq[cur]=0;
         for(int i=head[cur];i!=-1;i=e[i][1])
         {
             int v=e[i][0];
             if(e[i][3]>0&&e[i][2]+d[cur]<d[v])
             {
                 d[v]=e[i][2]+d[cur];
                 prv[v]=cur;               //记录增广路
                 pre[v]=i;
                 if(!inq[v])
                 {
                     q.push(v);
                     inq[v]=1;
                 }
             }
         }
    }
     if(d[countv+1]==inf)return 0;       //无法增广
     int cur=countv+1;                   //目的点
     while(cur!=0)              //取路径上最小残流量边作为流量增广
    {
        minf=e[pre[cur]][3]<minf?e[pre[cur]][3]:minf;
        cur=prv[cur];
    }
    cur=countv+1;
    while(cur!=0)                 //增广,改流量
    {
         e[pre[cur]][3]-=minf;
         e[pre[cur]^1][3]+=minf;
         cur=prv[cur];
    }
   sumcost+=d[countv+1]*minf;         //费用为单位费用(该路径下每条边单位流量之和)*流量
   sumflow+=minf;
   return 1;
}
void mincost(int & sumcost,int & sumflow)
{
    while(spfa(sumcost,sumflow)) ;     //无法增广为止
    return ;
}
void clear()
{
    nume=countv=0;
    for(int i=0;i<=4003;i++)
      {
          head[i]=-1;
          f[i]=0;
      }
}
struct qujian
{
    int a,b,w;
};
int main()
{
   int T;
   cin>>T;
   for(int ii=1;ii<=T;ii++)
   {
       clear();
       cin>>n>>k>>m;
       int a,b,w;
       vector<int>v;
       vector<qujian>qq(n);
       for(int i=0;i<n;i++)
       {
            cin>>a>>b>>w;
            v.push_back(a);
            v.push_back(b);
            qq[i].a=a;qq[i].b=b;qq[i].w=w;
       }
       sort(v.begin(),v.end());       //从小到大处理点
       for(int i=0;i<v.size();i++)
       {
           getf(v[i]);
       }
       for(int i=0;i<n;i++)
       {
            int t1=getv(qq[i].a);
            int t2=getv(qq[i].b);
            adde(t1,t2,qq[i].w,1);  //注意点:若使用adde(getv(a),getv(b),w,1)参数是从右往左开始赋值传递的!!!
       }
         for(int i=1;i<countv;i++)
       {
            adde(i+1,i,0,inf);  //注意点:若使用adde(getv(a),getv(b),w,1)参数是从右往左开始赋值传递的!!!
       }
       adde(0,1,0,inf);adde(countv,countv+1,0,k);
        int sumcost=0;  int sumflow=0;
       mincost(sumcost,sumflow);
       cout<<"Case "<<ii<<": ";
       if(sumflow!=k)       //到不了k,无解
       {
           cout<<-1<<endl;
       }
       else
       {
           cout<<sumcost<<endl;
       }
   }
   return 0;
}






  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值