今天的题都比较巧妙。
T1:首先我们设sum[i]表示前i个位置有多少个精灵,设p[i]=sum[i]-i。接着我们不难发现对于最小的一个p[m],一定不会存在有精灵从m走到m+1。
证明:如果存在精灵从m走到m+1,那么一定存在一个位置x使得从x到m的精灵数大于从x到m的位置数,即sum[m]-sum[x-1]>m-(x-1),移项得sum[m]-m>sum[x-1]-(x-1),即p[m]>p[x-1]。这与p[m]最小矛盾,所以假设不成立,结论成立。
有了这一条性质之后我们就可以找一个p[m]最小的m,然后在m和m+1开始断开,化环为链,接着顺着扫一遍贪心就好了。贪心的过程就是每次尽量找一个最小的但大于当前位置的精灵,找不到就找一个最小的精灵,这个可以用线段树维护。时间复杂度O(nlogn)。
T2:首先我们发现一个最终的答案肯定是在原序列中以某个位置为开头,找一个以当前位置为起点的最长上升子序列和一个最长下降子序列,这两个子序列合起来就是答案。这个手推一下就明白了。
所以现在我们用树状数组求出以每一个位置为起点的最长上升子序列和最长下降子序列的长度和方案数,设为fa、fb、ga、gb。然后最终最长的长度就是max(fa[i]+fb[i]-1),方案数就是sum(ga[i]*gb[i]*2^(n-maxlen))(fa[i]+fb[i]-1==maxlen)。之所以要乘一个2^(n-maxlen)是因为剩下的数可以随便放。
树状数组求最长上升子序列及方案数:
首先要求最长长度。这个还是比较简单的,因为是上升所以我们每一次查询的是1~a[i]-1的最大值,这个是可以用树状数组维护的。如果是求最长下降子序列的话把a反过来就行了。
接着要求方案数。在维护树状数组时设f[i][1]表示当前区间的最大值,f[i][2]表示当前区间最大值的方案数。在查询时,要把所有达到最大值的区间的f[i][2]加上。修改时要把当前的g加入f[i][2]中。这样就可以了。
贴一下代码:
ll find1(ll v)
{
ll i=v,m=0;
while(i>=1)
{
if(f[i][1]>m)m=f[i][1];
i=i-(i&(-i));
}
return m;
}
ll find2(ll v,ll m)
{
ll i=v,s=0;
while(i>=1)
{
if(f[i][1]==m)s=(s+f[i][2])%mod;
i=i-(i&(-i));
}
return s;
}
ll change(ll v,ll m,ll s)
{
ll i=v;
while(i<=ma)
{
if(m>f[i][1]){f[i][1]=m;f[i][2]=s;}
else if(m==f[i][1])f[i][2]=(f[i][2]+s)%mod;
i=i+(i&(-i));
}
}
T3:题解待更新。