[TJOI2014] Alice and Bob
非常好的一道思维性题目,想了很久才想出来qwq(我好笨啊)
考虑a[]数组有什么用,首先可以yy出一些性质 (设num[i]为原来第i个位置的数是什么 , 因为题目说至少有一个排列可以满足a[],所以我们就假设num[]没有相同的元素):
1. 当 a[i] == a[j] 且 i<j 的时候,我们可以得出 num[i] > num[j] ,因为如果反过来的话 a[j] 就至少是 a[i]+1 了。
2. 对于任意一个 a[i] ,考虑所有 a[j] + 1 == a[i] 的 j,它们中至少有一个要满足 : num[j] < num[i];而很显然,因为上一个性质的传递性,所以只需要找到最大的 j 然后让num[j] < num[i] 就好了,也就是说每个 位置 至多 会和前面的一个位置 有必然的大小关系。
然后我们把<当作边,可以发现原图变成了一个森林。而现在我们的任务就是:求出原序列的一个拓扑排序,使得反向lis和最大。
这个好像还不是很容易啊,填一个数带来的影响太多了。
不过我们最初内心肯定都会有一个想法:贪心,尽量让靠后的位置匹配小的数。
但是我一开始心里有一个顾虑: 如果一个位置很靠后,但是因为它必须要小于一个很靠前的位置(或者说它的爸爸编号很小),从而被耽误导致答案很劣怎么办?
不过画图之后证明这种情况是不存在的!
可以发现森林的第i层就是由 所有 a[x] == i 的 x 组成的,而每个节点会向上一层最大的 编号小于自己的点 连边,所以这就保证了一种贪心的正确性:我们从虚根(0)开始,采取每次走编号最大的儿子的先序遍历策略。
这种贪心的正确性在于,我们在走一个点i之前经过的点,要么是i的祖先(钦定要比它小的),要么编号比i大(编号靠后的小答案更优)。
于是就做完了hhhhhhhh(虽然代码被我压得很短)
#include<bits/stdc++.h> #define ll long long #define pb push_back using namespace std; const int maxn=100005; vector<int> g[maxn]; int n,m,pre[maxn],A,num[maxn],now,f[maxn],M[maxn]; inline void update(int x,int y){ for(;x<=n;x+=x&-x) f[x]=max(f[x],y);} inline int query(int x){ int an=0; for(;x;x-=x&-x) an=max(an,f[x]); return an;} void dfs(int x){ if(x) num[x]=++now; for(int i=g[x].size()-1;i>=0;i--) dfs(g[x][i]);} int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&A),g[pre[A-1]].pb(i),pre[A]=i; dfs(0); ll ans=0; for(int i=n;i;i--) M[i]=query(num[i]-1)+1,update(num[i],M[i]),ans+=(ll)M[i]; printf("%lld\n",ans); return 0; }