XJ_Day_29_dp优化_K - Cross the Wall
先附上上午码的,思路写在里面,我会在后面重新详细说明
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int MAX=5e4+3;
struct milk
{
LL h,w;
}a[MAX];
int st[MAX];
inline bool myru(milk x,milk y){if(x.h==y.h)return x.w>y.w;return x.h>y.h;}
LL rin()
{
LL s=0;
char c=getchar();
bool bj=0;
for(;(c>'9'||c<'0')&&c!='-';c=getchar());
if(c=='-')c=getchar(),bj=true;
for(;c>='0'&&c<='9';c=getchar())s=(s<<1)+(s<<3)+(c^'0');
if(bj)return -s;
return s;
}
int main()
{
//将人分成至多k组,每组的费用为max(hi)*max(wi),求最小的费用和
//首先贪心:
//1.对于任意1<=i,j<=n,如果满足h[i]>=h[j]&&w[i]>=w[j],则j是不需要考虑的
//2.那么剩下的人中,均满足,if(h[i]>h[j])w[i]<w[j];(上面的删的条件保证不会出现相等的情况)
//3.所以需要考虑的人中,最高的一定宽度最小,最宽的一定高度最小
//4.按高度从大到小排序,满足宽度递增
//5.显而易见,如果存在i<j<k(序号),k和j塞在同一组明显优于k和i塞在同一组,所以就按照这个排序之后的顺序,每组都是一段连续的区间
//6.那么对于每个i,要么自己新开一组,要么接在前一组
//7.问题在于怎么找到前一组的开头(即知道这组的洞的高度来更新答案),如果退化成O(N^2)是肯定不行的
//奇怪的思路1:
//枚举k个断点……,时间O(N^K),而且这题也并不是必须要k个,而是<=k,例子很好举,比方说,k=3;h1=100,w1=3;h2=99,w2=4;h3=98,w3=5;全放一组明显最优
//8.基本上f[N][k]就是极限
//先写一波假的复杂度的推导式吧
//f[i][j]表示当前到i,分了j块,i为第j个块的终点 的最小花费
//ans=max(f[N][j]),1<=j<=k;
//f[i][j]=min(f[i][j],f[x][j-1]+w[i]*h[x+1]),x<i;时间复杂度O(k*(N^2))
//分成若干组w[i]*h[x+1]!!!
//奇怪的思路2:
//if(h[i]*w[N]+h[last]*w[i-1]<h[last]*w[N])last=i;
//实现了一下,WA了,因为这个zz贪心会导致后面的分块数不够
// 10 4
// 1 100
// 2 99
// 3 98
// 4 97
// 5 96
// 6 95
// 7 94
// 8 93
// 9 92
// 10000 1
//对于这组数据,最后一个人肯定是单独作为一块,但是由于前面错误的贪心导致最后输出:970592
//奇怪的思路3:
//设st[i]表示第i个块的开始位置
//初始,st[1]=1;st[i]=N+1,i>1;
//若当前st[i]往前移能使答案更优,则移动
//初步估计时间复杂度为O(N*K)
//h[st[i-1]]*w[st[i]-1]+h[st[i]]*w[st[i+1]-1] 这个是当前这两段区间的和
//h[st[i-1]]*w[st[i]-2]+h[st[i]-1]*w[st[i+1]-1] 这个是st[i]向前移动一格后两端区间的和
//if(h[st[i-1]]*w[st[i]-2]+h[st[i]-1]*w[st[i+1]-1]<h[st[i-1]]*w[st[i]-1]+h[st[i]]*w[st[i+1]]-1)st[i]--;
//感觉是跑N次,每次看每个起始点能不能往前移
//写出来之后仍然是被那个数据Hack了
//因为这样附初值会导致st[2]跑到N就跑不上去了,后面的自然也上不去
//奇怪的思路4:
//续接奇怪的思路3,考虑外面枚举一层区间数,赋初值就根据这个赋
//WA了 ,艹
//反例:
// 10 4
// 1 100
// 2 99
// 3 98
// 4 97
// 5 96
// 9995 6
// 9996 5
// 9997 4
// 9998 3
// 10000 1
// Out put:1000000
//很好推翻的想法,至少我赋初值的方式不对,从后往前赋,加上正确但不一定完整的判定,WA掉很正常
//还是想想怎么优化DP式吧,别整花里胡哨的东西了
//9.h递减,w递增,这个应该是关键
int i,j;
int n,k;
for(;scanf("%d%d",&n,&k)>0;)
{
for(i=1;i<=n;i++)a[i].w=rin(),a[i].h=rin();
sort(a+1,a+n+1,myru);
int nam=0;
int max_w=0;
for(i=1;i<=n;i=j+1)
{
for(j=i;j<n&&a[j+1].h==a[i].h;j++);
if(a[i].w>max_w)max_w=a[i].w,a[++nam]=a[i];
}
n=nam;
// for(i=1;i<=nam;i++)printf("(%d,%d) ",a[i].h,a[i].w);
//奇怪的思路2代码
// int last=1,s=1;
// LL ans=0;
// for(i=2;i<=n;i++)
// {
// if(s==k)break;
// // printf("last:%d,ans:%lld -->",last,ans);
// if(a[i].h*a[n].w+a[last].h*a[i-1].w<a[last].h*a[n].w)ans+=a[last].h*a[i-1].w,last=i,s++;
// // printf("last:%d,ans:%lld\n",last,ans);
// }
// ans+=a[last].h*a[n].w;
// printf("%lld\n",ans);
//奇怪的思路3:
// st[1]=1;
// for(i=2;i<=k+1;i++)st[i]=n+1;
// for(j=1;j<=n;j++)
// for(i=2;i<=k;i++)
// {
// if(st[i-1]==st[i])break;
// if(a[st[i-1]].h*a[st[i]-2].w+a[st[i]-1].h*a[st[i+1]-1].w<a[st[i-1]].h*a[st[i]-1].w+a[st[i]].h*a[st[i+1]-1].w)st[i]--;
// }
// for(i=1;i<=k;i++)printf("%d ",st[i]);printf("\n");
// LL ans=0;
// for(i=1;i<=k;i++)ans+=a[st[i]].h*a[st[i+1]-1].w;
// printf("%lld\n",ans);
//奇怪的思路4:
// st[1]=1;
// LL ans=a[1].h*a[n].w;
// for(int now=2;now<=k;now++)
// {
// for(i=1;i<now;i++)st[i+1]=n-now+i+1;
// st[now+1]=n+1;
// for(j=1;j<=n;j++)
// for(i=2;i<=k;i++)
// {
// if(st[i]==st[i-1]+1)continue;
// if(a[st[i-1]].h*a[st[i]-2].w+a[st[i]-1].h*a[st[i+1]-1].w<a[st[i-1]].h*a[st[i]-1].w+a[st[i]].h*a[st[i+1]-1].w)st[i]--;
// }
// // for(i=1;i<=k;i++)printf("%d ",st[i]);printf("\n");
// LL sum=0;
// for(i=1;i<=now;i++)sum+=a[st[i]].h*a[st[i+1]-1].w;
// ans=min(ans,sum);
// }
// printf("%lld\n",ans);
}
return 0;
}
设第\(i\)个人的高度为\(h(i)\),宽度为\(w(i)\)
1.对于任意\(1\le i,j\le n,\)如果满足\(h(i)\ge h(j)\&\&w(i)\ge w(j)\),则\(j\)是不需要考虑的人
这个应该是很好理解的,更高更宽的人都能过,就不需要考虑更矮更窄的人
2.那么剩下的人中,均满足,\(if(h(i)>h(j))w(i)<w(j);\)(上面的删的条件保证不会出现相等的情况)
宽度(或高度)相等的若干个人,高度最高的会把其他的人都判成没用的
3.所以需要考虑的人中,最高的一定宽度最小,最宽的一定高度最小
4.按高度从大到小排序,满足宽度递增
这里就不用解释了吧,很好懂的,就是由2推导过来的结论
5.显而易见,如果存在\(i<j<k\)(序号),\(k\)和\(j\)塞在同一组明显优于\(k\)和\(i\)塞在同一组,所以就按照这个排序之后的顺序,每组都是一段连续的区间
这句话当时写的比较模糊,这里重新解释一下:
在前面排序以后的情况下,是这样的图形
对于当前人\(i\),考虑让他和前面的某个人\(j\)分成一组,打一个两个人都能通过的洞
高度为\(h(j)\),宽度为\(w(i)\)
那么这个操作之后\(i\)和\(j\)之间的所有点都可以直接通过这个洞(这个结论很显然,不想解释,前面的排序就是为了能够得到这个结论)
所以可以知道,每一组的人员都是一段连续的区间上的人,设左端点为\(l\),右端点为\(r\),这组的花费就是\(h(l)\ast w(r)\)
6.那么对于每个\(i\),要么自己新开一组,要么接在前一组
其实到这里状态转移方程就很明显了:
设\(f(i)(j)\)表示(当前到第\(i\)个人,已经分了\(j\)块,\(i\)为第\(j\)个块的终点)的最小花费
\(ans=max(f(n)(j)),1\le j\le k\)
\(f(i)(j)=min(f(i)(j),f(x)(j-1)+w(i)\ast h(x+1)),x<i\)
时间复杂度\(O(k\ast N^2)\)
这个方程需要优化,一看就知道是用斜率优化来做
设存在\(x_1<x_2\)
若有\(x_2\)决策更优
则:
这里的所有\(f()(j)\)都只跟\(f()(j-1)\)有关,可以消去一维
\(f(x_1)+h(x_1+1)\ast w(i)\ge f(x_2)+h(x_2+1)\ast w(i)\)
\(w(i)\ast (h(x_1+1)-h(x_2+1))\ge f(x_2)-f(x_1)\)
\(-w(i)\le \frac{f(x_2)-f(x_1)}{h(x_2+1)-h(x_1+1)}\)
设点\(P_x\)为\((h(x+1),f(x))\)
\(f(i)(j)=f(x)(j-1)+w(i)\ast h(x+1)\)
\(->b=y+(-k)\ast x\)
#include <bits/stdc++.h>
#define LL long long
using namespace std;
const int MAX=5e4+3;
const int MAK=1e2+3;
struct milk
{
LL h,w;
}a[MAX];
// int st[MAX];
int d[MAX];
LL f[2][MAX];
int head,tail;
bool now;
inline void push_d(int x)
{
//要维护斜率递减
for(;head<tail;)
{
if((f[now][x]-f[now][d[tail]])*(a[d[tail]+1].h-a[d[tail-1]+1].h)>=(f[now][d[tail]]-f[now][d[tail-1]])*(a[x+1].h-a[d[tail]+1].h))
tail--;
else break;
}
d[++tail]=x;
}
inline bool myru(milk x,milk y){if(x.h==y.h)return x.w>y.w;return x.h>y.h;}
LL rin()
{
LL s=0;
char c=getchar();
bool bj=0;
for(;(c>'9'||c<'0')&&c!='-';c=getchar());
if(c=='-')c=getchar(),bj=true;
for(;c>='0'&&c<='9';c=getchar())s=(s<<1)+(s<<3)+(c^'0');
if(bj)return -s;
return s;
}
int main()
{
int i,j;
int n,k;
for(;scanf("%d%d",&n,&k)>0;)
{
for(i=1;i<=n;i++)a[i].w=rin(),a[i].h=rin();
sort(a+1,a+n+1,myru);
int nam=0;
int max_w=0;
for(i=1;i<=n;i=j+1)
{
for(j=i;j<n&&a[j+1].h==a[i].h;j++);
if(a[i].w>max_w)max_w=a[i].w,a[++nam]=a[i];
}
n=nam;
LL ans;
for(i=1;i<=n;i++)f[1][i]=a[1].h*a[i].w;
ans=a[1].h*a[n].w;
now=false;
for(j=2;j<=k;j++)
{
// printf("\nk:%d\n\n",j);
now=!now;
head=1,tail=0;
for(i=1;i<j;i++)f[!now][i]=0x3f3f3f3f3f3f3f3f,push_d(i);
for(;i<=n;i++)
{
LL k_i=-a[i].w;
// for(int p=head;p<=tail;p++)printf("%d ",d[p]);printf(" -->\n");
for(;head<tail;)
{
// printf("%d: %lld?%lld\n",d[head],k_i*(a[d[head+1]+1].h-a[d[head]+1].h),f[now][d[head+1]]-f[now][d[head]]);
if(k_i*(a[d[head+1]+1].h-a[d[head]+1].h)<f[now][d[head+1]]-f[now][d[head]])break;
else head++;
}
// for(int p=head;p<=tail;p++)printf("%d ",d[p]);printf("\n");
f[!now][i]=f[now][d[head]]+a[i].w*a[d[head]+1].h;
push_d(i);
if(i==n)ans=min(ans,f[!now][i]);
// printf("f[%d]:%lld\n",i,f[!now][i]);
}
}
printf("%lld\n",ans);
return 0;
}