P9572 Colorful Days♪
思路
Step0.骗分
显然,题目中的 \(c_1,c_2\) 就是为了送分,如果比赛中没有思路,倒是可以直接输出两个 \(0\) 先得到 \(2\) 分,聊胜于无。
Step1.暴力不出奇迹
显然第一个想到的是暴力,枚举 \(k\),容易观察得出,若一次增加 \(k\) 而 LCS 不变,则再增加 \(k\) 也无用。可凭借这个结论暴力验证,然后得出答案。
但是非常显然,这样时间复杂度及其高,定然会 TLE。
Step2.观察性质
我们发现,如果 \(k\) 足够大,那么所有同时存在于数组 \(S,T\) 的数字都可以贡献答案,所以最大的 \(LCS\) 就是同时存在数组 \(S,T\) 的个数。
Step3.从模拟入手
想要直接找到最小 \(k\) 不是一件简单的事情,不如逐步模拟。
因为我们在 Step2 中得出结论,只要是同时存在于数组 \(S,T\) 的,都需要计算,所以我们可以遍历数组 \(T\)。
假设当前遍历到的数是 \(x\)。
如果 \(x\) 不在数组 \(S\) 中,则可以直接跳过,因为无论 \(k\) 多大,也没有任何用处。
如果 \(x\) 在数组 \(S\) 中,那么就可以贡献答案,如果前一个可贡献答案的数在数组 \(S\) 中的位置之后有 \(x\) 的话,那么不需要增加 \(k\),如果没有的话,则需要增加一次 \(k\),用新增加的一段中的 \(x\) 满足要求。
这样就完美地解决了 \(k\) 的问题。
Step4.代码写法
可以用 vector 储存每个数字在数组 \(S\) 中的位置。
用一个变量,储存上一个可贡献答案的数在数组 \(S\) 中的位置。
我们就只需要遍历 vector,找到位置之后的满足条件的坐标,如果找不到,就增加 \(k\),并把变量赋值为该数字第一次出现的位置。
AC 代码
#include<bits/stdc++.h>
using namespace std;
int n,m,c1,c2,s[1000005],t[1000005],res,k=1,p,cnt;
vector<int>v[1000005];
int main()
{
scanf("%d%d%d%d",&n,&m,&c1,&c2);
for(int i=1;i<=n;++i) scanf("%d",&s[i]),v[s[i]].push_back(i);
for(int i=1;i<=m;++i) scanf("%d",&t[i]);
for(int i=1;i<=m;++i)
{
if(!v[t[i]].size()) continue;//如果在S中不存在就直接跳过
else
{
int flag=0;res++;//用flag标记是否找到位置
for(int j=0;j<v[t[i]].size();++j) if(v[t[i]][j]>p){flag=1,p=v[t[i]][j];break;}//找到第一个大于p的位置
if(!flag) k++,p=v[t[i]][0];//没找到就需要增加k了,记得赋值
}
}
printf("%d %d",res*c1,k*c2);//别忘了乘以c1,c2
return 0;
}
题外话
这个代码其实有个小问题,如果 LCS 为 \(0\),那么非负整数 \(k\) 的最小值应该是 \(0\)。只不过因为太细节,加了可能会影响公平性,所以没加。
改代码也很简单,只需要判断 \(res\) 是不是 \(0\) 就好。这里不改是看看到时候会不会有人直接复制交(滑稽)。
Step5.进一步的优化
原本交了,但是被巨佬同学说了,我这个复杂度过了是因为数据水,所以我有屁颠屁颠地跑回来修改了。
显然如果数据构造合适,在查找位置的时候会被卡,所以考虑在查找优化时间复杂度,第一时间就想到是二分。绝对不是看到标签里有二分。
这样的话就容易了,只需要再写个二分查找就好了。
仅贴出部分代码(二分查找函数和调用部分的循环代码)
int find(int i,int x)
{
int l=0,r=v[i].size()-1,mid,ans=-1;
while(l<=r)
{
mid=l+r>>1;
if(v[i][mid]>x) r=mid-1,ans=v[i][mid];
else l=mid+1;
}
return ans;
}
/////////////
for(int i=1;i<=m;++i)
{
if(!v[t[i]].size()) continue;
else
{
int l=find(t[i],p);res++;
if(l==-1) k++,p=v[t[i]][0];
else p=l;
}
}