Description
小W 回到了教室,可怜的他要收历史作业,但是0901 班这群不负责任的组长把作业收得乱七八糟,散得每个座位上都有作业本,小W 只好挨个去收。
0901 班的教室可以看作是一个n 行m 列的矩形,小W 在(0,0)这个格子(位于教室的左下角),教室的门在(n-1,m-1)这个格子。每次小W 可以向相邻的格子走一步,走到某个格子时,他会收完这个格子的历史作业。小W 是个很懒的人,他想走一条最短路到教室的门口,但是作业收得太少了又会引起公愤,所以他决定走一条作业收得最多的最短路线,你能帮帮他吗?
Input
第一行三个整数n,m,k。
接下来k 行,每行三个整数a,b,c,表示在(a,b)这个格子有c 本历史作业。
Output
一行一个整数,表示最多能收到的历史作业的本数。
Sample Input
2 2 2
0 0 1
1 1 1
Sample Output
2
Data Constraint
30%的数据,n,m<=30,k<=100。
70%的数据,n,m<=10^9,k<=10000。
100%的数据,1<=n,m<=10^9,1<=k<=100000,0<=c<=300,保证每对(a,b)至多出现一次。
分析:一条可行的方案,保证a[i]<=a[i+1],b[i]<=b[i+1],不需考虑都相等情况。我们把横坐标为第一关键字,纵坐标为第二关键字从小到大排序后,相当于求b的最长不下降子序列,不过每个点有一个权值。显然最长不下降子序列可以用nlogn解决,但是是权值为1的情况,我们可以把权为ci的点拆成ci个数字,这就保证了权值都为1,然后跑一个最长不下降子序列。因为ci<=300,n<=100000,单调队列开n*c可以承受。至于二分查找到一个位置插入,就把从这个位置开始,后面ci个全部覆盖掉即可,可以通过。
至于再优化,覆盖这个考虑用平衡树或其他数据结构解决。
代码:
#include <iostream>
#include <cstdio>
#include <cmath>
#define fo(i,a,b) for (int i=a;i<=b;i++)
#define N 100001
using namespace std;
int n,m,k,sum,ans,q;
int a[N],b[N],c[N];
int list[30000001];
void kp(int l,int r)
{
if (l>r) return;
int i=l; int j=r;
int key1=a[(l+r)/2];
int key2=b[(l+r)/2];
int t;
while (i<=j)
{
while ((a[i]<key1) || ((a[i]==key1) && (b[i]<key2))) i++;
while ((a[j]>key1) || ((a[j]==key1) && (b[j]>key2))) j--;
if (i<=j)
{
t=a[i]; a[i]=a[j]; a[j]=t;
t=b[i]; b[i]=b[j]; b[j]=t;
t=c[i]; c[i]=c[j]; c[j]=t;
i++;j--;
}
}
kp(i,r);
kp(l,j);
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
fo(i,1,k)
{
scanf("%d%d%d",&a[i],&b[i],&c[i]);
sum+=c[i];
}
kp(1,k);
int t=0;
fo(i,1,c[1])
{
list[++t]=b[1];
}
fo(i,2,k)
{
if (b[i]>=list[t])
{
fo(j,1,c[i])
{
list[++t]=b[i];
}
ans=max(ans,t);
}
else
{
int l=1; int r=t;
q=t;
while (l<=r)
{
int mid=(l+r)/2;
if (list[mid]>b[i])
{
q=min(q,mid);
r=mid-1;
}
else l=mid+1;
}
fo(j,1,c[i])
{
list[q++]=b[i];
}
t=max(t,q-1);
ans=max(ans,t);
}
}
printf("%d",ans);
return 0;
}