传送门
题目大意:给定 n 个点 (xi,yi) ,形成一条折线,满足 xi,yi>0 , xi<xi+1 , yi≠yi+1 对于两个点,如果它们高度相等,且中间所有点都严格低于这两个点,那么你可以在两点间连一条边。你需要连恰好 m 条边,满足每个点严格低于 k 条边,请最大化所有边的长度和。
神奇的树DP…
将可以连的边先列举出来,然后发现这些边不是包含(一条边覆盖另一条边),就是相互独立。
然后神奇滴联想到树,父亲与儿子之间是包含关系,与兄弟之间相互独立。(感觉有点像A+B Problem那道的玄学理论)QAQ
然而本蒟蒻还是做不来/(ㄒoㄒ)/~~
令 f[i][j][k] 表示以节点 i 为根的子树中连了 j 条边,点连的边的最大值为 k 。转移方程如下:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define MAXN 510
#define MAXM 40005
using namespace std;
const int inf=1e8;
int n, m, k, cnt, val[MAXN];
struct Node
{
int x, y;
bool operator < (const Node &a)const
{return x<a.x;}
}p[MAXN];
struct Line
{
int u, v, y;
Line(){}
Line(const int a,const int b,const int c){u=a, v=b, y=c;}
bool operator < (const Line &a)const
{return abs(u-v)<abs(a.u-a.v);}
}line[MAXN];
struct node
{
int v, next;
}edge[MAXM];
int adj[MAXN], pos;
inline void add(int u,int v)
{
edge[pos].v=v, edge[pos].next=adj[u];
adj[u]=pos;++pos;
}
void makeline()
{
for(int i=1, flag;i<=n;++i)
for(int j=i+1;j<=n;++j)
if(p[i].y==p[j].y)
{
flag=1;
for(int l=i+1;l<j&&flag;++l)if(p[l].y>=p[i].y)flag=0;
if(flag)
{
line[++cnt]=Line(p[i].x,p[j].x,p[i].y);
break;
}
}
}
void maketree()
{
//这棵树可能是一个森林,故虚拟一个根节点0
memset(adj,-1,sizeof adj), pos=0;
sort(line+1,line+cnt+1);
for(int i=1, flag;i<=cnt;++i)
{
flag=1;
for(int j=i+1;j<=cnt;++j)
if(line[i].y<line[j].y&&line[i].u>line[j].u&&line[i].v<line[j].v)
{
flag=0;
add(j,i);
break;
}
if(flag)add(0,i);
}
}
int f[MAXN][MAXN][11];
int d[MAXN], tmp[MAXN][MAXN];
int dfs(int u,int fa)
{
int size=1, v, c;
for(int p=adj[u];~p;p=edge[p].next)
if((v=edge[p].v)!=fa)
{
d[v]=dfs(v,u);
size+=d[v];
}
//这一部分考虑不选u
for(int j=0;j<k;++j)
{
for(int i=0;i<=m;++i)tmp[0][i]=-inf;
tmp[0][0]=0;
//这相当与一个背包,保存一下之前的值以防止错误更新
c=0;
for(int p=adj[u];~p;p=edge[p].next)
if((v=edge[p].v)!=fa)
{
++c;
for(int i=0;i<=m;++i)tmp[c][i]=tmp[c-1][i];
//由于保存的是前缀最大值,不需要再枚举变量d
//tmp[c][l]=max(f[son[u]][k][j]+f[u][l-k][j]);
for(int t=0;t<=d[v];t++)
for(int l=t;l<=min(size,m);l++)
tmp[c][l]=max(tmp[c][l],tmp[c-1][l-t]+f[v][t][j]);
}
for(int i=0;i<=m;i++)f[u][i][j]=max(f[u][i][j],tmp[c][i]);
}
//这一部分考虑选u
if(u)
{
for(int j=1, c;j<k;++j)
{
for(int i=0;i<=m;++i)tmp[0][i]=-inf;
tmp[0][1]=val[u];
c=0;
for(int p=adj[u];~p;p=edge[p].next)
if((v=edge[p].v)!=fa)
{
++c;
for(int i=0;i<=m;++i)tmp[c][i]=tmp[c-1][i];
//这一部分和上面很像 但不要忘了这是选了u的 要-1
for(int t=0;t<=d[v];t++)
for(int l=t+1;l<=min(size,m);l++)
tmp[c][l]=max(tmp[c][l],tmp[c-1][l-t]+f[v][t][j-1]);
}
for(int i=0;i<=m;++i)f[u][i][j]=max(f[u][i][j],tmp[c][i]);
}
}
for(int i=1;i<=m;i++)
f[u][i][0]=-inf;
f[u][0][0]=0;
//更新前缀最大值
for(int i=0;i<=m;i++)
for(int j=1;j<k;j++)
f[u][i][j]=max(f[u][i][j],f[u][i][j-1]);
return size;
}
int main()
{
int cas=0;
while(~scanf("%d%d%d",&n,&m,&k))
{
for(int i=1;i<=n;++i)scanf("%d%d",&p[i].x,&p[i].y);
cnt=0;
sort(p+1,p+n+1);
makeline();
maketree();
val[0]=-inf;
for(int i=1;i<=cnt;++i)val[i]=abs(line[i].u-line[i].v);
for(int i=0;i<=cnt;++i)
for(int j=0;j<=m;++j)
for(int l=0;l<=k;++l)
f[i][j][l]=-inf;
dfs(0,-1);
//已更新前缀最大值,直接访问f[0][m][k-1]即可
printf("Case %d: %d\n",++cas,(f[0][m][k-1]>0?f[0][m][k-1]:-1));
}
return 0;
}