一年没现场打Atcoder了,结果手速不够经典赛后过题,丢了个上大分的好机会
这场主要是B卡了挺久了,拖到快1h的时候才过,然后后面策略也有点问题,对着不擅长的Counting C题看了30min没啥想法
最后30min去看D发现是个很经典的DS拼凑题,结果写完之后比赛已经结束20min了,不过交上去也过了
感觉状态啥的还是没调整过来,今晚还有场Div1+2希望能别掉大分的说
刚开始感觉有点无从下手,但观察到核心性质后就很简单了
需要注意到,两个相邻的AA
或者两个相邻的BB
是无论如何都不能一起操作的
因此可以根据这个把序列分成若干段,每一段内部都是形如ABABABABA
这样的交错段
设这一段的长度为 l,手玩一下会发现不同的变化方案有 ⌊l+12⌋种,最后把每一段的方案数乘起来即可
首先考虑当 k=1 时怎么处理,不难发现交换次数的上界就是排列的逆序对数,则考虑是否存在一种方案使得交换次数总能达到该上界
考虑按照从小到大的顺序考虑每个数 x,设其在序列中初始所在的位置为 posx,则我们对于所有 j>posx∧pj<x的数均可以进行一次交换
具体地,把后面的比 x 小的数按照从大到小的顺序依次与位置 posx 上的数进行交换,不难发现这样的交换总能进行
由于我们是按照从小到大的顺序进行这个操作的,因此在处理后面更大的数时比它小的数的相对位置可能会发生变化,但对答案没有影响
因此就得到了一种达到上界的构造方案,推广到 k>1 的情形也是类似的,直接模拟地构造即可
刚开始看错题了以为是替换成所有选中位置的和,感觉不太可做;后面玩了会样例后才发现正确题意,然而还是不会做,鉴定为Counting战俘没什么好说的
首先这题做法一眼DP,但状态的设计比较巧妙,定义 fi,j 表示处理了前 i 个数,并且此时 ai=j 的方案数
根据操作的性质,当 j≠0 时我们可以直接枚举下一个选中的位置 k,转移为 fk,ak+j←fi,j,此时 k 位置上的数一定会发生变化,因此是一种不同的方案
但当 j=0 时情况就有点特殊了,此时直接枚举到的下一个位置 k 是不会变化的,因此不能记为一种方案
因此我们需要在 [i+1,k−1] 中找一个数 v≠0 作为中转点将其贡献转给 ak,即 fk,ak+v←fi,j
不过要注意中间的这段数需要去重,因为选它们中值相同的位置不会改变序列本身的形态,是同质的操作
总复杂度 O(n3×|ai|)
很典的一个题,属于是看一眼就知道怎么写了,但因为挺久没写代码了写的磨磨蹭蹭而且时间也不够,没能在比赛中写出来
首先不妨设 mx=maxl≤i≤rai,不难发现三段中至少有一段的贡献为 mx,因此根据 mx 所在的位置讨论一下:
- 当 mx 在中间一段时,此时简单分析后会发现最优策略就是 al,ar 单独成一段,剩下中间的全部成一段
- 当 mx 在最左边的一段时,此时中间一段最优一定是选一个数 ap,剩下最右边的一段的贡献为 maxp<j≤raj
- 当 mx 在最右边的一段时,此时中间一段最优一定是选一个数 aq,剩下最左边的一段的贡献为 maxl≤j<qaj
不难发现这样统计虽然没有考虑 mx 所在的具体位置的限制,但由于只会把贡献算大并且正确的答案一定会被统计到,因此是完备的
其中第一种情况很简单,而第三种情况只需要把原序列reverse
一下就可以规约为第二种情况,因此仅考虑第二种情况如何处理
考虑当右端点 r 固定时,选择 p 这个位置的贡献由两部分构成,一部分是 ap;另一部分为 maxp<j≤raj
不难发现后面那个式子的值是一段一段变化的,即我们只需要关心从位置 r 向左的一段极长的单调增子序列即可
这个只需要用单调栈维护下所有关键的分界点,每部分内部用ST表查询出前面部分式子的最小值即可算出整体的贡献
最后对于询问就用扫描线的思路从左到右处理右端点,然后在单调栈上二分找到左端点对应的位置
单独讨论掉最边上的一段贡献后,剩下的部分就是一个单点修改,区间求最小值了,直接用线段树维护即可
总复杂度 O(nlogn),代码其实很好写
#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=250005,INF=1e9;
int n,q,a[N],mn[N][20],mx[N][20],lg[N],ans[N],stk[N],top; vector <tri> Q1[N],Q2[N];
inline int query_min(CI l,CI r)
{
if (l==0) return INF;
int k=lg[r-l+1]; return min(mn[l][k],mn[r-(1<<k)+1][k]);
}
inline int query_max(CI l,CI r)
{
if (l==0) return -INF;
int k=lg[r-l+1]; return max(mx[l][k],mx[r-(1<<k)+1][k]);
}
class Segment_Tree
{
private:
int mn[N<<2];
public:
#define TN CI now=1,CI l=1,CI r=n
#define LS now<<1,l,mid
#define RS now<<1|1,mid+1,r
inline void modify(CI pos,CI mv,TN)
{
if (l==r) return (void)(mn[now]=mv); int mid=l+r>>1;
if (pos<=mid) modify(pos,mv,LS); else modify(pos,mv,RS);
mn[now]=min(mn[now<<1],mn[now<<1|1]);
}
inline int query(CI beg,CI end,TN)
{
if (beg>end) return INF;
if (beg<=l&&r<=end) return mn[now]; int mid=l+r>>1,ret=INF;
if (beg<=mid) ret=min(ret,query(beg,end,LS));
if (end>mid) ret=min(ret,query(beg,end,RS));
return ret;
}
#undef TN
#undef LS
#undef RS
}SEG;
int main()
{
RI i,j; for (scanf("%d%d",&n,&q),i=1;i<=n;++i)
scanf("%d",&a[i]),mn[i][0]=mx[i][0]=a[i];
for (lg[0]=-1,i=1;i<=n;++i) lg[i]=lg[i>>1]+1;
for (j=1;(1<<j)<=n;++j) for (i=1;i+(1<<j)-1<=n;++i)
mn[i][j]=min(mn[i][j-1],mn[i+(1<<j-1)][j-1]),
mx[i][j]=max(mx[i][j-1],mx[i+(1<<j-1)][j-1]);
for (i=1;i<=q;++i)
{
int l,r; scanf("%d%d",&l,&r);
int mx=query_max(l,r); ans[i]=mx+a[l]+a[r];
Q1[r].push_back({mx,l,i});
Q2[n-l+1].push_back({mx,n-r+1,i});
}
for (i=1;i<=n;++i)
{
while (top>0&&a[stk[top]]<=a[i]) --top;
stk[++top]=i; SEG.modify(top,query_min(stk[top-1],i-1)+a[i]);
for (auto [mx,l,id]:Q1[i])
{
int pos=lower_bound(stk+1,stk+top+1,l+1)-stk,ret;
if (l+1<=stk[pos]-1) ret=query_min(l+1,stk[pos]-1)+a[stk[pos]]; else ret=INF;
ret=min(ret,SEG.query(pos+1,top)); ans[id]=min(ans[id],mx+ret);
}
}
reverse(a+1,a+n+1); top=0;
for (i=1;i<=n;++i) mn[i][0]=mx[i][0]=a[i];
for (j=1;(1<<j)<=n;++j) for (i=1;i+(1<<j)-1<=n;++i)
mn[i][j]=min(mn[i][j-1],mn[i+(1<<j-1)][j-1]),
mx[i][j]=max(mx[i][j-1],mx[i+(1<<j-1)][j-1]);
for (i=1;i<=n;++i)
{
while (top>0&&a[stk[top]]<=a[i]) --top;
stk[++top]=i; SEG.modify(top,query_min(stk[top-1],i-1)+a[i]);
for (auto [mx,l,id]:Q2[i])
{
int pos=lower_bound(stk+1,stk+top+1,l+1)-stk,ret;
if (l+1<=stk[pos]-1) ret=query_min(l+1,stk[pos]-1)+a[stk[pos]]; else ret=INF;
ret=min(ret,SEG.query(pos+1,top)); ans[id]=min(ans[id],mx+ret);
}
}
for (i=1;i<=q;++i) printf("%d\n",ans[i]);
return 0;
}
最近总是倒反天罡想要挑战Counting,每次都落得大败而归,还是老老实实去写那些不用动脑的题吧
__EOF__
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
2019-06-30 Luogu P3265 [JLOI2015]装备购买