AtCoder Regular Contest 180

Preface

一年没现场打Atcoder了,结果手速不够经典赛后过题,丢了个上大分的好机会

这场主要是B卡了挺久了,拖到快1h的时候才过,然后后面策略也有点问题,对着不擅长的Counting C题看了30min没啥想法

最后30min去看D发现是个很经典的DS拼凑题,结果写完之后比赛已经结束20min了,不过交上去也过了

感觉状态啥的还是没调整过来,今晚还有场Div1+2希望能别掉大分的说


A - ABA and BAB

刚开始感觉有点无从下手,但观察到核心性质后就很简单了

需要注意到,两个相邻的AA或者两个相邻的BB是无论如何都不能一起操作的

因此可以根据这个把序列分成若干段,每一段内部都是形如ABABABABA这样的交错段

设这一段的长度为 \(l\),手玩一下会发现不同的变化方案有 \(\lfloor \frac{l+1}{2} \rfloor\)种,最后把每一段的方案数乘起来即可

#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,mod=1e9+7;
int n; char s[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; scanf("%d%s",&n,s+1); int len=1,ans=1;
	for (i=2;i<=n;++i) if (s[i]!=s[i-1]) ++len; else ans=1LL*(len+1)/2*ans%mod,len=1;
	ans=1LL*(len+1)/2*ans%mod;
	return printf("%d",ans),0;
}

B - Improve Inversions

首先考虑当 \(k=1\) 时怎么处理,不难发现交换次数的上界就是排列的逆序对数,则考虑是否存在一种方案使得交换次数总能达到该上界

考虑按照从小到大的顺序考虑每个数 \(x\),设其在序列中初始所在的位置为 \(pos_x\),则我们对于所有 \(j>pos_x\and p_j<x\)的数均可以进行一次交换

具体地,把后面的比 \(x\) 小的数按照从大到小的顺序依次与位置 \(pos_x\) 上的数进行交换,不难发现这样的交换总能进行

由于我们是按照从小到大的顺序进行这个操作的,因此在处理后面更大的数时比它小的数的相对位置可能会发生变化,但对答案没有影响

因此就得到了一种达到上界的构造方案,推广到 \(k>1\) 的情形也是类似的,直接模拟地构造即可

#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=505;
int n,k,a[N],pos[N]; vector <pi> ans;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j; for (scanf("%d%d",&n,&k),i=1;i<=n;++i)
	scanf("%d",&a[i]),pos[a[i]]=i;
	for (i=1;i<=n;++i)
	{
		for (int l=pos[i];;)
		{
			int best=-1,num=-1;
			for (j=l+k;j<=n;++j)
			if (a[j]<a[l]&&a[j]>best) best=a[j],num=j;
			if (best!=-1) ans.push_back(pi(l,num)),swap(a[l],a[num]); else break;
		}
	}
	printf("%d\n",ans.size());
	for (auto [l,r]:ans) printf("%d %d\n",l,r);
	return 0;
}

C - Subsequence and Prefix Sum

刚开始看错题了以为是替换成所有选中位置的和,感觉不太可做;后面玩了会样例后才发现正确题意,然而还是不会做,鉴定为Counting战俘没什么好说的

首先这题做法一眼DP,但状态的设计比较巧妙,定义 \(f_{i,j}\) 表示处理了前 \(i\) 个数,并且此时 \(a_i=j\) 的方案数

根据操作的性质,当 \(j\ne 0\) 时我们可以直接枚举下一个选中的位置 \(k\),转移为 \(f_{k,a_k+j}\leftarrow f_{i,j}\),此时 \(k\) 位置上的数一定会发生变化,因此是一种不同的方案

但当 \(j=0\) 时情况就有点特殊了,此时直接枚举到的下一个位置 \(k\) 是不会变化的,因此不能记为一种方案

因此我们需要在 \([i+1,k-1]\) 中找一个数 \(v\ne 0\) 作为中转点将其贡献转给 \(a_k\),即 \(f_{k,a_k+v}\leftarrow f_{i,j}\)

不过要注意中间的这段数需要去重,因为选它们中值相同的位置不会改变序列本身的形态,是同质的操作

总复杂度 \(O(n^3\times |a_i|)\)

#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=105,M=100*20+5,mod=1e9+7;
int n,a[N],mn,mx,f[N][M],ans;
inline void inc(int& x,CI y)
{
	if ((x+=y)>=mod) x-=mod;
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j,k; for (scanf("%d",&n),i=1;i<=n;++i)
	scanf("%d",&a[i]),mn+=min(a[i],0),mx+=max(a[i],0);
	for (f[0][0-mn]=1,i=0;i<n;++i) for (j=mn;j<=mx;++j)
	{
		if (!f[i][j-mn]) continue;
		if (j==0)
		{
			set <int> s; s.insert(a[i+1]);
			for (k=i+2;k<=n;++k)
			{
				for (auto x:s) if (x) inc(f[k][a[k]+x-mn],f[i][j-mn]);
				s.insert(a[k]);
			}
		} else
		{
			for (k=i+1;k<=n;++k) inc(f[k][a[k]+j-mn],f[i][j-mn]);
		}
	}
	for (i=0;i<=n;++i) for (j=mn;j<=mx;++j) inc(ans,f[i][j-mn]);
	return printf("%d",ans),0;
}

D - Division into 3

很典的一个题,属于是看一眼就知道怎么写了,但因为挺久没写代码了写的磨磨蹭蹭而且时间也不够,没能在比赛中写出来

首先不妨设 \(mx=\max_\limits{l\le i\le r} a_i\),不难发现三段中至少有一段的贡献为 \(mx\),因此根据 \(mx\) 所在的位置讨论一下:

  • \(mx\) 在中间一段时,此时简单分析后会发现最优策略就是 \(a_l,a_r\) 单独成一段,剩下中间的全部成一段
  • \(mx\) 在最左边的一段时,此时中间一段最优一定是选一个数 \(a_p\),剩下最右边的一段的贡献为 \(\max_\limits{p<j\le r} a_j\)
  • \(mx\) 在最右边的一段时,此时中间一段最优一定是选一个数 \(a_q\),剩下最左边的一段的贡献为 \(\max_\limits{l\le j<q} a_j\)

不难发现这样统计虽然没有考虑 \(mx\) 所在的具体位置的限制,但由于只会把贡献算大并且正确的答案一定会被统计到,因此是完备的

其中第一种情况很简单,而第三种情况只需要把原序列reverse一下就可以规约为第二种情况,因此仅考虑第二种情况如何处理

考虑当右端点 \(r\) 固定时,选择 \(p\) 这个位置的贡献由两部分构成,一部分是 \(a_p\);另一部分为 \(\max_\limits{p<j\le r} a_j\)

不难发现后面那个式子的值是一段一段变化的,即我们只需要关心从位置 \(r\) 向左的一段极长的单调增子序列即可

这个只需要用单调栈维护下所有关键的分界点,每部分内部用ST表查询出前面部分式子的最小值即可算出整体的贡献

最后对于询问就用扫描线的思路从左到右处理右端点,然后在单调栈上二分找到左端点对应的位置

单独讨论掉最边上的一段贡献后,剩下的部分就是一个单点修改,区间求最小值了,直接用线段树维护即可

总复杂度 \(O(n\log n)\),代码其实很好写

#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()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	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;
}

Postscript

最近总是倒反天罡想要挑战Counting,每次都落得大败而归,还是老老实实去写那些不用动脑的题吧

posted @ 2024-06-30 14:20  空気力学の詩  阅读(146)  评论(0编辑  收藏  举报