Codeforces 818G. Four Melodies 最大费用最大流

题意

给定一个长度为n的序列,你需要选出4个没有交集的子序列,使得它们都满足:相邻两项要么差的绝对值为1,要么模7余数相同
求最长的总长度
4≤n≤3000

分析

首先我们想想怎么建图,可以把四个子序列变为流量,一个点变成费用,就跑最大费用最大流,每个点只能流一次,我们想到拆两个点一个入一个出
入的连到出的流量为1,费用为1,代表用这个点,
出的连到所有到达入的点,流量为inf,费用为0
然后出点连到终点,起点连到入点

这样有N^2的建边,我们考虑优化

方法1:当你找不到任何优化的点的时候,可以考虑搞一个出错率极小的做法,有一个想法就是每个点仅向后面50个点连边,这样我判断一波好像极其难卡,可是好像我的费用流会T,我看有人这样就过了

方法2:正解,其实我们连模7相同的边和+1,-1边有一个小优化,就是只连到最近的一条,然后再从入点连向出点一条流量为inf,费用为0的边,正确性显然

这样边数就变成O(N)的了

代码

#include <bits/stdc++.h>
#define MP make_pair
using namespace std;
const int N = 50010;
const int inf = 1e9;
typedef pair<int,int> pii;
inline int read()
{
  int p=0; int f=1; char ch=getchar();
  while(ch<'0' || ch>'9'){if(ch=='-') f=-1; ch=getchar();}
  while(ch>='0' && ch<='9'){p=p*10+ch-'0'; ch=getchar();}
  return p*f;
}

struct node{int x,y,d,c,next;}edge[N<<4]; int len,first[N];
void ins(int x,int y,int c,int d)
{
  len++; edge[len].x=x; edge[len].y=y; edge[len].c=c; edge[len].d=d; edge[len].next=first[x]; first[x]=len;
  len++; edge[len].x=y; edge[len].y=x; edge[len].c=0; edge[len].d=-d; edge[len].next=first[y]; first[y]=len;
}

bool v[N]; int d[N]; queue<int>q; int pre[N],pos[N]; int s,t;
bool bfs()
{
  while(!q.empty()) q.pop(); q.push(s);
  memset(d,-63,sizeof(d)); d[s]=0;
  memset(v,0,sizeof(v)); v[s]=1;
  while(!q.empty())
  {
    int x=q.front();
    for(int k=first[x];k!=-1;k=edge[k].next)
    {
      int y=edge[k].y;
      if(d[y] < d[x] + edge[k].d && edge[k].c)
      {
        d[y] = d[x] + edge[k].d; pos[y] = x; pre[y]=k;
        if(!v[y]){v[y]=1; q.push(y);}
      }
    }
    q.pop(); v[x] = 0;
  }
  return d[t] >= 0;
}

int n,a[N];

int main()
{

  len=1; memset(first,-1,sizeof(first));
  n = read(); for(int i=1;i<=n;i++) a[i] = read();
  int ss=2*n+1; s = ss+1; t = s+1; ins(s,ss,4,0);
  for(int i=1;i<=n;i++)
  {
    ins(ss,i+n,1,0);
    ins(i+n,i,1,1);
    ins(i,t,1,0);
  }

  for(int i=1;i<=n;i++)
  {
    int cnt = 0;
    #define MAGIC 1
    for(int j=i+1;j<=n;j++)
      if((a[i] - a[j] == 1) && cnt < MAGIC){ins(i,j+n,inf,0); cnt++;}
    cnt=0;
    for(int j=i+1;j<=n;j++)
      if((a[i] - a[j] == -1) && cnt < MAGIC){ins(i,j+n,inf,0); cnt++;}
    cnt=0;
    for(int j=i+1;j<=n;j++)
      if((a[i]%7 == a[j]%7) && cnt < MAGIC){ins(i,j+n,inf,0); cnt++;}
    ins(i+n,i,inf,0);
  }

  int ans = 0;
  while(bfs())
  {
    for(int i=t;i!=s;i=pos[i]) ans+=edge[pre[i]].d,edge[pre[i]].c--,edge[pre[i]^1].c++; 
  }

  return printf("%d\n",ans),0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值