NOIP2022模拟赛二 By ZJ 8.20

Preface

昨天睡得有点晚因此今天脑子不是很清醒……

T1本来一眼秒了的,结果自己数\(n=4\)的个数的时候输错了就以为自己错了

后来写了个假算法发现全场就我没过T1:(


A. 「NOIP2022模拟赛二 By ZJ A」数正方形

Pro

  • 在坐标系内放置\(n\)个边长相等(由你决定)的正方形(边必须与\(x,y\)轴平行)
  • 求一种正方形数目尽量多的方案,输出边长\(k\)以及左下角的坐标
  • \(n\le 1314\)

Sol

其实直接让\(k=n\)然后\((x_i,y_i)=i,i\)即可

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
int n;
int main()
{
	scanf("%d",&n); printf("%d\n",n);
	for (RI i=0;i<n;++i) printf("%d %d\n",i,i);
	return 0;
}

B. 「NOIP2022模拟赛二 By ZJ B」英语作文

Pro(稍作修改)

  • \(n\)个数字字符串\(s_i\),现在要选出一些将它们顺次连接在一起
  • 求最后字符串总长度不超过\(D\)时最大的数是多少
  • \(D\le2\times10^2,n\le10^4,|s_i|\le 32\)

Sol

如果没有那个位数限制,我们发现直接贪心,定义\(A\)排在\(B\)前面的条件为\(A+B>B+A\)\(+\)表示连接两个字符串),然后从前往后把所有的字符串连起来即可

那么现在多了一个长度限制怎么办,其实就是一个类似于\(0/1\)背包的东西,因为不管我们最后选了哪些串都一定是从前往后顺序连接起来的

直接做即可(话说为什么人家都是200+ms我直接2000+ms……)

#include<cstdio>
#include<iostream>
#include<string>
#include<algorithm>
#define RI int
#define CI const int&
using namespace std;
const int N=10005,M=205;
int n,m; string s,f[M],a[N],ans;
inline char trs(const char& ch)
{
	switch (ch)
	{
		case 'O': return '0';
		case 'D': return '0';
		case 'G': return '9';
		case 'B': return '8';
		case 'L': return '7';
		case 'q': return '6';
		case 'S': return '5';
		case 'h': return '4';
		case 'E': return '3';
		case 'Z': return '2';
		case 'I': return '1';
	};
}
inline bool cmp(const string& A,const string& B)
{
	return A+B>B+A;
}
inline bool check(const string& A,const string& B)
{
	int t1=0,t2=0; while (A[t1]=='0') ++t1; while (B[t2]=='0') ++t2;
	if (A.length()-t1!=B.length()-t2) return A.length()-t1>B.length()-t2;
	return A>B;
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i,j; std::ios::sync_with_stdio(false); cin>>m>>n;
	for (i=1;i<=n;++i) for (cin>>s,j=s.length()-1;~j;--j) a[i]+=trs(s[j]);
	for (sort(a+1,a+n+1,cmp),i=1;i<=n;++i) for (j=m;j>=a[i].length();--j)
	if (check(f[j-a[i].length()]+a[i],f[j])) f[j]=f[j-a[i].length()]+a[i];
	//for (i=1;i<=n;++i) cout<<i<<' '<<a[i]<<'\n';
	//for (i=0;i<=m;++i) cout<<i<<' '<<f[i]<<'\n';
	for (ans=f[0],i=1;i<=m;++i) if (check(f[i],ans)) ans=f[i];
	if (ans[0]!='0') return cout<<ans,0; cout<<"0.";
	for (ans=f[0],i=1;i<=m;++i) if (f[i]>ans) ans=f[i];
	for (i=1;i<ans.length();++i) cout<<ans[i]; return 0;
}

C. 「NOIP2022模拟赛二 By ZJ C」等比数列

Pro

  • 有一个长度为\(n\)的数列\(a=\{1,2,3,\cdots,n\}\)
  • 求出\(a\)有多少个长度至少为\(3\)的子序列是等比数列
  • 数据组数\(T\le 10^6,n\le 10^{14},\sum \sqrt n\le 10^7\),答案对\(998244353\)取模

Sol

我是nt,竟然一点都不会做的说

首先考虑对于一个长度为\(k+1\),首项为\(A\),公比为\(\frac{p}{q},\gcd(p,q)=1\)的等比数列是唯一确定的

此时其每一项的表达式为\(A\cdot \frac{p^i}{q^i}(i\in [0,k])\),不难发现如果要让所有数为整数只要末项\(A\cdot \frac{p^k}{q^k}\)为整数即可,即\(q^k|A\)

我们可以直接暴枚\(q\)\(k\)\(p\)的取值就是\(\phi(q)\)种,然后再乘上\(A\)的取值数目$\lfloor \frac{n}{q^k}\rfloor $即可

复杂度不知道怎么算,总之大概是\(\sqrt n\)级别的

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int S=10000005,mod=998244353;
int t,pri[S],phi[S],cnt,ans; bool vis[S]; long long n; __int128 pw;
inline void init(CI n)
{
	RI i,j; for (vis[1]=phi[1]=1,i=2;i<=n;++i)
	{
		if (!vis[i]) pri[++cnt]=i,phi[i]=i-1;
		for (j=1;j<=cnt&&i*pri[j]<=n;++j)
		if (vis[i*pri[j]]=1,i%pri[j]) phi[i*pri[j]]=phi[i]*(pri[j]-1);
		else { phi[i*pri[j]]=phi[i]*pri[j]; break; }
	}
}
inline void inc(int& x,CI y)
{
	if ((x+=y)>=mod) x-=mod;
}
int main()
{
	for (init(10000000),scanf("%d",&t);t;--t)
	{
		RI i,j; for (scanf("%lld",&n),ans=0,i=2;1LL*i*i<=n;++i)
		for (pw=1LL*i*i,j=2;pw<=n;++j,pw*=i) inc(ans,1LL*phi[i]*(n/pw)%mod);
		printf("%d\n",ans);
	}
}

D. 「NOIP2022模拟赛二 By ZJ D」沙漏求值

Pro

  • \(n\)个沙漏,每个沙漏有一个参数\(a_i\),它们按编号从上到下放置
  • 这个沙漏漏沙子的规律是这样的:如果到了\(i\)号漏斗时有\(s\)颗沙子,则会向下漏出\(x_i=s\bmod a_i\)颗沙子
  • 现在你可以在最上面的沙漏中放任意颗沙子,求\(\max\{\sum_{i=1}^n x_i\}\)
  • \(n\le 2\times 10^5,a_i\le 10^{18}\)

Sol

首先考虑暴力DP,设\(f_{i,j}\)表示前\(i\)个数,当前\(x_i=j\)时最大的\(\sum_{k=1}^i x_k\),这样可以做到\(O(n\cdot a_i)\)的复杂度

但是显然这个DP没法再优化了,因为状态本身数量级就爆了而且每种状态都有可能有贡献

那么考虑修改状态,不难发现\(x_j\le x_i,(j>i)\),同时若\(x_i\ne 0\),那么把初始值全减\(1\)一定可以让\(x_i\)\(1\)

考虑这样设计状态,令\(y=\sum_{j=1}^i (x_j-x_i)\),那么如果我们把初始值\(S\)变为\(S-x_i+k(k\in[0,x_i))\)一定有\(\sum_{j=1}^i x_j=k\cdot i+y\)

因此我们考虑这样设计状态,令\(f_{i,j}\)表示前\(i\)个数,当\(x_i=j\)时最大的\(y\),考虑转移

先假定每次枚举\(k\),那么就有\(f_{i+1,k\ \bmod\ a_{i+1}}\leftarrow f_{i,j}+(k-k\bmod a_{i+1})\times i\),但我们显然是不可能每次枚举\(k\)

先设\(j\ge a_{i+1}\),考虑\(k\bmod a_{i+1}\)的取值只有\(a_{i+1}\)种,不妨把\([0,j]\bmod a_{i+1}\)的取值写成一排,它们显然是一段带周期的\([0,a_{i+1})\)和一些零头

我们发现最优的转移点只有两个,一个是\(j\),一个就是最后一个周期的\(a_{i+1}-1\)

\(j<a_{i+1}\)的情况就更加简单了,直接保留即可

考虑这样DP的复杂度,显然我们可以保留数值来优化掉第一维,第二维用map维护即可

由于取模操作可以让一个数至少减半,因此每个\(j\)最多转移\(\log a_i\)次,总复杂度就是\(O(n\log n\log a_i)\)

#include<cstdio>
#include<map>
#define int __int128
#define RI register int
#define CI const int&
const int N=200005;
using namespace std;
int n,a[N],ans; map <int,int> f;
inline void print(int x)
{
	if (x>9) print(x/10); putchar(x%10+'0');
}
signed main()
{
	RI i; for (scanf("%lld",&n),i=1;i<=n;++i) scanf("%lld",&a[i]);
	for (f[a[1]-1]=0,i=2;i<=n;++i)
	{
		for (auto it=f.lower_bound(a[i]);it!=f.end();++it)
		{
			int x=it->first,y=it->second;
			f[x%a[i]]=max(f[x%a[i]],y+(i-1)*(x-x%a[i]));
			f[a[i]-1]=max(f[a[i]-1],y+(i-1)*(((x+1)/a[i]-1)*a[i]));
		}
		f.erase(f.lower_bound(a[i]),f.end());
	}
	for (auto it:f) ans=max(ans,it.second+n*it.first);
	return print(ans),0;
}

Postscript

接下来应该就不来了,好好准备迎接大学生活吧

posted @ 2022-08-20 15:14  空気力学の詩  阅读(80)  评论(0编辑  收藏  举报