题目链接:https://ac.nowcoder.com/acm/contest/1110/E
题意理解:
给出一组数字序列,求每次删掉一个数字之后序列的所有 f[i]^2的异或和,f[i]表示以i为结尾的LIS长度。
题解:
第一眼是直接LIS暴力来做,不能用经典的LIS做法来求,O(n^3)肯定会炸,然后想了想,可以用O(nlogn)二分贪心优化的LIS来求 f 数组,本来以为就这么简单,但是过不去,叉姐专门把带log的给卡了,因此只能用O(n^2)的方法来做了。
这个题需要深刻理解LIS的含义,只有知道dp数组的意义才能想到O(n^2)的做法。
删除第i个数,那么对之前的数 f 值是没有影响的,而在其之后的值有两种情况:假设当前为f[j],那么之后的可能为f[j]-1或者f[j];
1.当为f[j]的时候,表示j之前有一个位置f[x]=f[j]-1而且a[x]<a[j] ( x!=i)
2.当为f[j]-1的时候,表示j之前没有这种位置。
然后我们可以维护一个数组dp[i],用来表示在f值为i时的最小a[](即当前长度为 i 时最后的元素最小可以是多少)
之后我们删除 i 的时候判断一下是否对 j 的上升子序列有影响。尽可能贪心地维护最小值,保证f[j]仍为f[j]的情况多。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=5e3+7;
const int INF=0x3f3f3f3f;
int a[maxn];
int f[maxn];
int dp[maxn];
int b[maxn];
int main(){
int n;
while(~scanf("%d",&n)){
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
f[i]=1;
for(int j=1;j<i;j++)
if(a[j]<a[i])
f[i]=max(f[i],f[j]+1);
}
for(int i=1;i<=n;i++){
memset(dp,INF,sizeof dp);
dp[0]=0;
int res=0;
for(int j=1;j<=i-1;j++){
dp[f[j]]=min(dp[f[j]],a[j]);
res^=f[j]*f[j];
}
for(int j=i+1;j<=n;j++){
int temp=f[j];
if(dp[temp-1]<a[j]){
dp[temp]=a[j];
res^=temp*temp;
}
else{
dp[temp-1]=a[j];
res^=(temp-1)*(temp-1);
}
}
printf("%d ",res);
}
printf("\n");
}
return 0;
}