[luogu_U15116]珈百璃堕落的开始

https://www.zybuluo.com/ysner/note/1239458

题面

给定\(n\)个二元组\((x,y)\),问有多少种方案,使得选出其中几个后,\(\sum x=\sum y\)

  • \(nm\leq5*10^7,m\leq10^6\)

解析

在打比赛时又被傻逼DP切了
考虑到我们关注的是\(x-y\),我们可以维护一维状态\(x-y\)以代替\(x,y\)两维状态。
于是设\(f[i][j]\)表示在第\(1-i\)个数中,\(x-y=j\)的方案数。
\(x-y\)可以为负,记得开大空间。
转移显然。

但有几个要注意的地方。
如果暴力枚举\(j\),复杂度可以达到\(O(10^6n)\),数据没有保证\(n\)这样能过。
注意到\(10^6\)中有很多转移是从不合法状态转移到不合法状态。
既然开始状态为\(f[0][10^6]\)(防止状态出现负数),范围很小。我们可以维护一下当前被转移到了的区间范围,这样能大大降低时间复杂度。

还有,\(dp\)值的边界条件要求必须能把未转移到的状态和转移到的状态区分开来
所以初始所有\(dp\)值要设为\(-inf\)\(dp[0][10^6]\)除外。

// luogu-judger-enable-o2
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<vector>
#define re register
#define il inline
#define ll long long
#define max(a,b) ((a)>(b)?(a):(b))
#define min(a,b) ((a)<(b)?(a):(b))
#define fp(i,a,b) for(re int i=a;i<=b;i++)
#define fq(i,a,b) for(re int i=a;i>=b;i--)
using namespace std;
const int N=2e6+100,M=1e6;
int n,m,s[N],c[N],len,dp[2][N];
char a[N];
il ll gi()
{
   re ll x=0,t=1;
   re char ch=getchar();
   while(ch!='-'&&(ch<'0'||ch>'9')) ch=getchar();
   if(ch=='-') t=-1,ch=getchar();
   while(ch>='0'&&ch<='9') x=x*10+ch-48,ch=getchar();
   return x*t;
}
int main()
{
  n=gi();
  fp(i,1,n)
    {
      scanf("%s",a+1);len=strlen(a+1);
      fp(j,1,len) if(a[j]=='s') s[i]++;else if(a[j]=='c') c[i]++;
    }
  re int now=1,nxt=0,lasx=M,lasm=M,mn=M,mx=M;
  memset(dp,-63,sizeof(dp));dp[1][M]=0;
  fp(i,1,n)
  {
    swap(now,nxt);
    fp(j,lasm,lasx)
    {
      dp[now][j+s[i]-c[i]]=max(dp[now][j+s[i]-c[i]],dp[nxt][j]+s[i]);
      dp[now][j]=max(dp[now][j],dp[nxt][j]);
      dp[nxt][j]=0;
      mx=max(mx,j+s[i]-c[i]),mn=min(mn,j+s[i]-c[i]);
    }
    lasx=mx;lasm=mn;
  }
  printf("%d\n",dp[now][M]);
  return 0;
}
posted @ 2018-08-05 21:17  小蒟蒻ysn  阅读(297)  评论(0编辑  收藏  举报