Codeforces Round 889 (Div. 1)

发布时间 2023-07-31 22:06:16作者: 空気力学の詩

Preface

由于一轮集训最后一周题目难度变大加上要写专题补专题导致欠了很多的博客没写,接下来慢慢把它们补上吧

才不是因为天天溜会寝室看LPL呢,我发誓

顺序的话就倒着来好了,先从最后的这场收尾的CF补起好了

这场其实刚开始就被A1,A2卡的很难受,大概1h左右过了之后一直在刚B,其实核心的思路全部都想到了但就是没想到用bitset去优化那个东西(看到区间平移下意识地以为是平衡树)

不过惊喜的是这样都不掉分真是绷不住了,感觉靠刮痧真有机会刮上2100的说


A1. Dual (Easy Version)&&A2. Dual (Hard Version)

这题其实说穿了很简单,但就是需要一点灵光想到刚开始的那步

首先考虑如果所有数都\(\ge 0\),那么有一个显而易见的\(n-1\)步的做法,就是从左往右每次把前一个数加到后一个数上

同样的如果所有数都\(\le 0\),就可以从右往左把后一个数加到前一个数上去

但现在有一个问题就是序列中的数有正有负该怎么处理,以把所有数变成\(\ge 0\)的情况为例

我们肯定是找到最大的那个正数,然后通过将其翻倍来让它比绝对值最大的那个负数的绝对值大,然后我们在把所哟的负数加上它即可

但只是这样的话不足以通过A2,其实仔细想一下这个东西就是个trade-off,我们每次判断下是把所有数改成\(\ge 0\)还是\(\le 0\)更优即可

比赛的时候想到这个就感觉绝对是对的了,就没细算操作次数,最后样例一过直接一交就过了

#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>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef unsigned long long u64;
typedef pair <int,int> pi;
typedef vector <int> VI;
const int N=25,INF=1e9;
int t,n,a[N],mi,mx,cm,cp,cnt,x[100],y[100];
inline int calc(int x,int y)
{
	if (!x) return INF; int cnt=0; while (x<y) ++cnt,x*=2; return cnt;
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d",&n),cnt=mi=mx=cm=cp=0,i=1;i<=n;++i)
		{
			if (scanf("%d",&a[i]),a[i]>0) mx=max(mx,a[i]),++cp;
			if (a[i]<0) mi=max(mi,-a[i]),++cm;
		}
		if (!mi&&!mx) { puts("0"); continue; }
		if (calc(mx,mi)+cm<=calc(mi,mx)+cp)
		{
			int pos; for (i=1;i<=n;++i) if (a[i]==mx) { pos=i; break; }
			while (mx<mi) ++cnt,x[cnt]=y[cnt]=pos,mx*=2;
			for (i=1;i<=n;++i) if (a[i]<0) ++cnt,x[cnt]=i,y[cnt]=pos;
			for (i=2;i<=n;++i) ++cnt,x[cnt]=i,y[cnt]=i-1;
		} else
		{
			int pos; for (i=1;i<=n;++i) if (a[i]==-mi) { pos=i; break; }
			while (mi<mx) ++cnt,x[cnt]=y[cnt]=pos,mi*=2;
			for (i=1;i<=n;++i) if (a[i]>0) ++cnt,x[cnt]=i,y[cnt]=pos;
			for (i=n-1;i>=1;--i) ++cnt,x[cnt]=i,y[cnt]=i+1;
		}
		for (printf("%d\n",cnt),i=1;i<=cnt;++i) printf("%d %d\n",x[i],y[i]);
	}
	return 0;
}

B. Earn or Unlock

首先有一个很trivial的DP做法,设\(f_{i,j}\)表示处理了前\(i\)张牌,解锁了前\(j\)张牌时能获得的最大收益

则转移很显然,不过要注意合法的状态必须满足\(i\le j\)

  • 用第\(i\)张牌去解锁,\(f_{i,j}\to f_{i+1,j+a_i}\)
  • 获得第\(i\)张牌的价值,\(f_{i,j}\to f_{i+1,j}+a_i\)

然后观察DP的式子我们会发现其实已知\(i,j\)的情况下贡献是确定的,因为只要能确定能到达这个状态了那么操作的顺序啥的就无关紧要了

换句话说,假如最后解锁前\(x\)张牌的状态是合法的,那么它对应的贡献一定是\(a_1+a_2\cdots a_x-x+1\)

那么我们的DP其实只要考虑第一种情况即可,这种状态的平移直接用bitset就很好解决了(因为我们只要知道每个位置的可达性即可)

实现的时候可以设第\(n+1\sim 2n\)的牌都是\(0\),总复杂度\(O(\frac{n^2}{\omega})\)

#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>
#define int long long
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef unsigned long long u64;
typedef pair <int,int> pi;
typedef vector <int> VI;
const int N=200005;
int n,a[N],pfx[N],ans; bitset <N> f;
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%lld",&n),i=1;i<=n;++i) scanf("%lld",&a[i]),pfx[i]=pfx[i-1]+a[i];
	for (f.set(1),i=1;i<=n;++i)
	{
		f|=f<<a[i];
		if (f.test(i)) ans=max(ans,pfx[i]-i+1),f.reset(i);
	}
	for (i=n+1;i<=2*n;++i) if (f.test(i)) ans=max(ans,pfx[n]-i+1);
	return printf("%lld",ans),0;
}

C. Expected Destruction

比赛的时候看到期望就不敢做了,其实赛后一看感觉海星,就当学个思路吧

首先把问题转化为,在长为\(m+1\)的数轴上有若干个棋子,每次可以选一个棋子向后走一步,同一个位置最多存在一个棋子,当所有棋子到达\(m+1\)时结束

那么如果没有任何两个棋子在同个地方相遇的话答案就是\(\sum_{i=1}^n (m-a_i+1)\),现在我们考虑减去由于位置重合带来的影响

如果在位置\(x\)有两个棋子相遇,那么它们带来的贡献减少是显然的,就是\(m-x+1\),问题就是怎么求出概率

如果直接用后面的棋子追上前面的棋子后两个合并成一个的这种观点的话就很难想清楚,其实我们仔细想一下可以发现不妨假设后面的棋子在追上前面的棋子后就直接消失了

这样有一个好处就是所有的棋子间的相遇都只可能在相邻的棋子间进行,而根据期望的线性性,这样处理是没问题的

那么我们不妨单独考虑两个相邻的棋子,只考虑它们的移动时就是个经典套路,就是各自\(\frac{1}{2}\)的概率向前走

\(f_{x,y}\)表示两个棋子分别处于位置\(x,y\)时减去的期望贡献,转移非常trivial,只是要注意当\(y=m+1\)时要直接中止返回\(0\)

#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>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef unsigned long long u64;
typedef pair <int,int> pi;
typedef vector <int> VI;
const int N=505,mod=1e9+7,inv2=(mod+1)/2;
int n,m,a[N],ret,f[N][N];
inline int DP(CI x,CI y)
{
	if (x==y) return m-x+1; if (y==m+1) return 0;
	if (~f[x][y]) return f[x][y];
	return f[x][y]=(1LL*DP(x+1,y)*inv2%mod+1LL*DP(x,y+1)*inv2%mod)%mod;
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%d%d",&n,&m),i=1;i<=n;++i)
	scanf("%d",&a[i]),(ret+=m-a[i]+1)%=mod;
	for (memset(f,-1,sizeof(f)),i=1;i<n;++i) (ret+=mod-DP(a[i],a[i+1]))%=mod;
	return printf("%d",ret),0;
}

Postscript

补天咯