汉诺塔(思维、DP思想)

时间:2023-03-10 07:24:41
汉诺塔(思维、DP思想)

链接:https://ac.nowcoder.com/acm/contest/3007/C
来源:牛客网

题目描述

现在你有 N 块矩形木板,第 i 块木板的尺寸是 Xi*Yi,你想用这些木板来玩汉诺塔的游戏。
我们知道玩汉诺塔游戏需要把若干木板按照上小下大的顺序堆叠在一起,但因为木板是矩形,所以有一个问题:
第 i 块木板能放在第 j 块木板上方当且仅当 Xi<Xj 且 Yi<Yj,于是你很可能没法把所有的木板按照一定的次序叠放起来。
你想把这些木板分为尽可能少的组,使得每组内的木板都能按照一定的次序叠放。
你需要给出任意一种合理的分组方案。
提醒:“任意”意味着你的答案不必和标准输出完全一致,只要正确即可。

输入描述:

第一行,一个正整数 N
接下来 N 行,每行两个正整数表示 Xi 和 Yi
对于所有的数据,1≤N≤100,000,1≤Xi,Yi≤N,Xi 互不相等且 Yi 互不相等

输出描述:

输出文件包含两行,第一行一个正整数,表示最少组数
第二行 N 个正整数,依次表示你的方案中每块木板分在了哪一组
组的编号必须是从 1 开始的连续整数

输入


输出

  

最长上升子序列的 贪心+二分法:O(nlogn) 

分析:要让一个序列具有最长上升子序列,其实就是保证子序列中的每个元素尽可能小,降低门槛,让后面的元素尽可能多进入该子序列

实现:定义一个最长子序列数组Array,以及当前长度Len,从头到尾维护数组a

a. a[i]>Array[i] (当前元素大于子序列结尾元素),则a[i]进入子序列:Array[++len] = a[i]

b. a[i]<=Array[i],这时对Array进行维护,把Array中比a[i]大的第一个元素替换成a[i](这样可以降低后面元素进入子序列的门槛。

c. 为了降低算法复杂度,因为Array是升序序列,所以用lower_bound查找Array中第一个大于等于a[i]的元素

官方题解:

将木板按照Xi从小到大排序,将这时的Yi数列记为Zi数列,则问题变成将Zi划分为尽可能少的若干组上升子序列
根据Dilworth定理,最小组数等于Zi的最长下降子序列长度
要求最长下降子序列的长度,我们有一种经典的二分优化dp的方法,在这里不再详述。 借助这种做法我们能给出一种构造方法,在求出最小组数的同时得出方案。
将状态数组的每个位置变为栈,用入栈操作代替修改元素操作,即可在求出组数的同时,用这些栈来完成对数列的划分。

这题跟以前一道dp题导弹拦截问题很像。

求最长下降子序列长度+二分

 #include <stdio.h>
#include <string.h>
#include <iostream>
#include <string>
#include <math.h>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <sstream>
const int INF=0x3f3f3f3f;
typedef long long LL;
const double eps =1e-;
const int mod=1e9+;
const int maxn=1e5+;
using namespace std; struct node
{
int x,y;
int id;
bool operator <(const node &a) const//小到大
{
return x<a.x;
}
}P[maxn]; int fa[maxn],st[maxn];
int cnt; int main()
{
#ifdef DEBUG
freopen("sample.txt","r",stdin);
#endif int n;
scanf("%d",&n);
for(int i=;i<=n;i++)
{
scanf("%d %d",&P[i].x,&P[i].y);
P[i].id=i;
}
sort(P+,P++n);
int cnt=;
for(int i=;i<=n;i++)
{
int L=,R=cnt;
while(L<=R)//二分查找st中第一个比P[i].y小的位置
{
int mid=(L+R)>>;
if(st[mid]<P[i].y)
R=mid-;
else if(st[mid]>P[i].y) L=mid+;
}
st[L]=P[i].y;//替换该位置的值(贪心)
if(L>cnt) cnt=L;//比st中所有都小,添到尾部
fa[P[i].id]=L;//它替换了st中谁的值,他就跟谁在同一个集合
}
printf("%d\n",cnt);
for(int i=;i<=n;i++)
printf(i==n?"%d\n":"%d ",fa[i]); return ;
}

求最长上升子序列长度+upper_bound()

 #include <stdio.h>
#include <string.h>
#include <iostream>
#include <string>
#include <math.h>
#include <algorithm>
#include <vector>
#include <stack>
#include <queue>
#include <set>
#include <map>
#include <sstream>
const int INF=0x3f3f3f3f;
typedef long long LL;
const double eps =1e-;
const int mod=1e9+;
const int maxn=1e5+;
using namespace std; struct node
{
int x,y;
int id;
bool operator <(const node &a) const//大到小
{
return x>a.x;
}
}P[maxn]; int fa[maxn],st[maxn];
int cnt; int main()
{
#ifdef DEBUG
freopen("sample.txt","r",stdin);
#endif int n;
scanf("%d",&n);
for(int i=;i<=n;i++)
{
scanf("%d %d",&P[i].x,&P[i].y);
P[i].id=i;
}
sort(P+,P++n);
int cnt=;
for(int i=;i<=n;i++)
{
int pos;
if(P[i].y>st[cnt]) pos=++cnt;//比st中所有都大,添到尾部
else pos=upper_bound(st+,st++cnt,P[i].y)-(st+)+;//下标别忘加1
st[pos]=P[i].y;//替换该位置的值(贪心)
fa[P[i].id]=pos;//它替换了st中谁的值,他就跟谁在同一个集合
}
printf("%d\n",cnt);
for(int i=;i<=n;i++)
printf(i==n?"%d\n":"%d ",fa[i]); return ;
}

-