NOIP2022模拟赛一 By RSJ 08.18

Preface

明天要考科三了,今天下午早早溜去练车了……

保佑RP++,不然挂了要等寒假回来再考了,那时候全忘光了又要重新练


A. 「NOIP2022模拟赛一 By RSJ」String Search

Pro

  • 给定一个\(01\)字符串,查找一个子串使得\(0\)在这个子串中出现了\(\frac{p}{q}\cdot k\)次,其中\(k\)是子串长度
  • \(n,p,q\le 2\times 10^5,p\le q\)

Sol

不难发现长度必须为\(q\)的倍数,同时再观察一下我们发现\(0\)的个数是连续变化的

那么也就意味着若长度为\(2k,3k\cdots\)的字串是符合题意的,那么就一定存在长度为\(k\)的字串符合题意

这里代码直接放比赛时的求稳CODE了

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,pfx[N],p,q; char s[N];
int main()
{
	for (scanf("%d",&t);t;--t)
	{
		RI i,j; bool flag=0; scanf("%d%d%d%s",&n,&p,&q,s+1),p=q-p;
		for (i=1;i<=n;++i) pfx[i]=pfx[i-1]+s[i]-'0';
		if (n>5000)
		{
			for (i=1;i<=n&&!flag;++i) if (i>=q&&pfx[i]-pfx[i-q]==p) flag=1,printf("YES\n%d %d\n",i-q+1,i);
		} else
		{
			for (j=q;j<=n&&!flag;j+=q) for (i=j;i<=n&&!flag;++i)
			if (1LL*(pfx[i]-pfx[i-j])*q==1LL*p*j) printf("YES\n%d %d\n",i-j+1,i),flag=1;
		}
		if (!flag) puts("NO");
	}
	return 0;
}

B. 「NOIP2022模拟赛一 By RSJ」Give You This Absolutely Amazing Doodle To Demonstrate

Pro

  • \(n\)个数,你需要将他们分为若干组,每组的落差值等于组内最大的数乘\(2\)减去最小的数
  • 求各组落差值之和不超过\(x\)的分组方案数
  • \(n\le 100,x\le 5000\)

Sol

考虑原问题的原型CF626F,会做那个这个就随便做了

考虑将问题转化为在数轴上的某些位置选出\(n\)个左括号和右括号(一个位置可以单独放一对括号),求所有括号匹配的长度和

然后发现这个贡献的计算很简单,就是右括号位置之和减去左括号位置之和即可

那么我们不难设计出一个DP,\(f_{i,j,k}\)表示考虑了前\(i\)个位置,剩余\(j\)个左括号未匹配的方案数,代价为\(k\)的方案数

不难发现这个状态是\(n^2\times \sum a_i\)的,虽然第三位最后我们只关心\([0,x]\)的值,但是转移过程中负数的情况是不能省略的

那么我们就要上技巧了,考虑差分,设\(d_i=a_i-a_{i-1}\),那么一对括号\((l,r)\)的贡献就是\(a_r-a_{l}=\sum_{i=l+1}^r d_i\)

我们发现这样有一个很好的性质,我们放入一个左括号时是不会产生贡献的,但每次处理完一个数是会多产生\(j\times d_{i+1}\)的贡献(\(j\)是未匹配的左括号的个数)

这样贡献只有加法没有减法了,因此复杂度降为\(O(n^2\times x)\)

对于这道题唯一的区别就是右括号位置贡献要多算一遍,略微修改转移的部分即可

#include<cstdio>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=105,X=5005,mod=998244353;
int n,x,f[N][N][X],a[N],d[N],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 %d",&n,&x),i=1;i<=n;++i) scanf("%d",&a[i]);
	for (sort(a+1,a+n+1),i=1;i<=n;++i) d[i]=a[i]-a[i-1];
	for (f[0][0][0]=1,i=0;i<n;++i) for (j=0;j<=i;++j)
	{
		int t=d[i+1]*j; for (k=0;k+t<=x;++k) if (f[i][j][k])
		{
			inc(f[i+1][j][k+t],1LL*(j+1)*f[i][j][k]%mod);
			inc(f[i+1][j+1][k+t],f[i][j][k]);
			if (j>0&&k+t+a[i+1]<=x) inc(f[i+1][j-1][k+t+a[i+1]],1LL*j*f[i][j][k]%mod);
		}
	}
	for (i=0;i<=x;++i) inc(ans,f[n][0][i]); return printf("%d",ans),0;
}

C. 「NOIP2022模拟赛一 By RSJ」Stock Trading

Pro

  • 你预测了一支股票未来\(n\)天价格,你初始没有票,但有花不完的钱
  • 每天最多将\(1\)支股票卖出或买入,但是任何时刻持有的股票数量要在\([l,r]\)之间
  • 求出最终利益的最大值
  • \(-10^6\le -n\le l\le 0\le r\le n\le 10^6\)

Sol

考试的时候只会\(O(n\times (r-l))\)的暴力DP和\(l=0,r=n\)带悔贪心,喜提60pts

后来陈指导告诉我这就是个Slope Trick的裸题,浅看了一下发现确实

暴力DP就是设\(f_{i,j}\)表示前\(i\)支股票,第\(i\)天的股票数为\(j\)的最大收益,我们观察转移方程:

\[f_{i,j}=\max(f_{i-1,j},f_{i-1,j-1}-a_i,f_{i-1,j+1}+a_i) \]

我们发现一个重要性质,相邻两位的差分值为\(a_i\),并且说实话我们转移的时候并不太关心\(j\)的具体值

因此我们直接用set维护相邻两位的差分值,转移的时候恭喜拆掉即可

复杂度\(O(n\log n)\)

#include<cstdio>
#include<set>
#include<cctype>
#define RI register int
#define CI const int& 
using namespace std;
class FileInputOutput
{
	private:
		static const int S=1<<21;
		#define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
		#define Tp template <typename T>
		char Fin[S],*A,*B;
	public:
		Tp inline void read(T& x)
		{
			x=0; char ch; bool flag=0; while (!isdigit(ch=tc())) if (ch=='-') flag=1;
			while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc())); if (flag) x=-x;
		}
		#undef tc
		#undef Tp
}F;
int n,l,r,L,R,x; multiset <int> S; long long ans;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	F.read(n); F.read(l); F.read(r); for (RI i=1;i<=n;++i)
	{
		F.read(x); S.insert(x); S.insert(x); ans-=x;
		if (--L<l) S.erase(S.begin()),++L;
		if (++R>r) ans+=*(--S.end()),S.erase(--S.end()),--R;
	}
	for (int x:S) ans+=x; return printf("%lld",ans),0;
}

D. 「NOIP2022模拟赛一 By RSJ」74TrAkToR's Div.2 Problem

Pro

  • 给定一个\(n\)个节点,\(m\)条边,无自环,无重边,保证联通的有向图
  • 改变一些边的方向后,可使得该有向图无环
  • 求所有不同的改变方案需要改变方向的边的数量之和
  • \(2\le n\le 18,m\le 200\)

Sol

首先注意到将一种改变方案全部取反后依然合法,且这样一对方案改变的边数之和为\(m\)

因此可以直接把每种方案的贡献视为\(\frac{m}{2}\),这样只要求方案数即可,并且把有向边化为了无向边

考虑利用拓扑序列来辅助DP,设\(f_S\)表示当前拓扑序列已经包含的点集为\(S\)的无环方案数

考虑枚举一个\(T\),从\(f_{S-T}\)中转移过来,要求\(T\)中的点两两之间不能有边,不然后于拓扑序列的性质就会成环

但是这样会因为拓扑序的顺序问题导致算重,因此我们还要子集容斥一下

复杂度\(O(3^n)\),据说好像有FWT+多项式求逆做法\(O(n^2\times 2^n)\),但说实话这个范围可能还没这个暴力DP来得快

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=1<<18,mod=998244353;
int n,m,lim,f[N],s[N],coef[N],x,y;
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
	for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void inc(int& x,CI y)
{
	if ((x+=y)>=mod) x-=mod;
}
int main()
{
	RI i,j; for (scanf("%d%d",&n,&m),i=1;i<=m;++i)
	scanf("%d%d",&x,&y),--x,--y,s[(1<<x)|(1<<y)]=1;
	for (lim=1<<n,j=0;j<lim;++j) for (i=0;i<n;++i) if (j&(1<<i)) s[j]|=s[j^(1<<i)];
	for (f[0]=i=1;i<lim;++i) for (coef[i]=coef[i&(i-1)]^1,j=i;j;j=(j-1)&i)
	if (!s[j]) inc(f[i],coef[j]?f[i^j]:mod-f[i^j]);
	return printf("%d",1LL*f[lim-1]*m%mod*quick_pow(2)%mod),0;
}

Postscript

PS:科三一把满分过了,驾照已经指日可待了……

posted @ 2022-08-18 13:35  空気力学の詩  阅读(60)  评论(0编辑  收藏  举报