11月1日 2023CSP-S 复赛模测(“日记和×××”系列) - 模拟赛记录

Preface

这套题说实话挺水的,不仅仅是在数据上(实际得分比赛时估分高了 \(50+\) 分),而且正解也神奇得不像个正解(各种分类讨论卡子任务,感觉像是出题人水平不够一样)。

怪不得教练敢说没考到 \(300\) 分的都该多练。

总体来说,第一二四题的做题手感、做题策略都挺好的,只是第三题实在没有啥思路(背包没学好),这个时候就应该先把各种特殊性质打完的,不要老想着一开始就打正解或者半正解,有时候打一打特殊性质既可以有分保底,又可以加深题目理解

日记和最短路(shortway

(话说最短路的英语不应该是 shortest path 吗?)

题目中给了一个 DAG,然后要求用两种方式跑最短路。

看到 DAG,又要求最短路,第一时间想到先拓扑排序再做 DP,似乎只需要 \(O(N+M)\) 的时间复杂度。

然而这里有一个大问题:因为边权是一个字符串,而在组成最短路和比较路径大小的时候最坏时间复杂度是 \(O(\sum \lvert w_i \rvert)\),所以按照以上方法的实际时间复杂度应当是 \(O(N+M\sum \lvert w_i \rvert)\),虽然不可能卡得满,但是硬要算的话,期望得分应当只有 \(16\) 分才对。

然而,用这样的方法可以卡到 \(96\) 分,可见数据几乎完全没有卡字符串比较和合并的时间复杂度。

正解似乎是要使用拆点什么的,但是我不会,所以就只放上面说的 \(96\) 分代码了:

赛时超常发挥的神奇 $96$ 分代码
#include<queue>
#include<string>
#include<cstring>
#include<iostream>
using namespace std;

const int N=1e5+5,M=5e5+5;
int n,m;

struct Allan{
	int to,nxt;
	string val;
}edge[M];
int head[N],idx;
inline void add(int x,int y,string &z)
{
	edge[++idx]={y,head[x],z};
	head[x]=idx;
	return;
}

int indeg[N];
queue<int> q;
int top_order[N],top_idx;
void TopSort(int src)
{
	q.push(src);
	indeg[src]--;
	while(!q.empty())
	{
		int x=q.front(); q.pop();
		top_order[++top_idx]=x;
		for(int i=head[x];i;i=edge[i].nxt)
		{
			int y=edge[i].to;
			indeg[y]--;
			if(!indeg[y])
				q.push(y);
		}
	}
	return;
}

string sp1[N];
bool cmp1(const string &x,const string &y,const string &z)
{
	if(z=="-") return true;
	else if(x.length()+y.length()!=z.length())
		return x.length()+y.length()<z.length();
	else return x+y<z;
}
void Solve1()
{
	for(int p=1;p<=top_idx;p++)
	{
		int x=top_order[p];
		for(int i=head[x];i;i=edge[i].nxt)
		{
			int y=edge[i].to; string z=edge[i].val;
			if(cmp1(sp1[x],z,sp1[y])) //sp[x]+z<sp[y]
				sp1[y]=sp1[x]+z;
		}
		if(x!=n) sp1[x].clear();
	}
	return;
}

string sp2[N];
bool cmp2(const string &x,const string &y,const string &z)
{
	if(z=="-") return true;
	else return x+y<z;
}
void Solve2()
{
	for(int p=1;p<=top_idx;p++)
	{
		int x=top_order[p];
		for(int i=head[x];i;i=edge[i].nxt)
		{
			int y=edge[i].to; string z=edge[i].val;
			if(cmp2(sp2[x],z,sp2[y])) //sp[x]+z<sp[y]
				sp2[y]=sp2[x]+z;
		}
		if(x!=n) sp2[x].clear();
	}
	return;
}

int main()
{
	freopen("shortway.in","r",stdin);
	freopen("shortway.out","w",stdout);
	
	ios::sync_with_stdio(false);
	cin.tie(nullptr),cout.tie(nullptr);
	
	cin>>n>>m;	
	for(int i=1;i<=m;i++)
	{
		int x,y; string z;
		cin>>x>>y>>z;
		add(x,y,z);
		indeg[y]++;
	}
	
	TopSort(1);
	for(int i=1;i<=n;i++)
		sp1[i]=sp2[i]="-";
	sp1[1]=sp2[1]="";
	Solve1();
	Solve2();
	cout<<sp1[n]<<' '<<sp2[n]<<endl;
	
	return 0;
}
赛后补的 $100$ 分代码
#include<cstdio>
#include<queue>
#include<cstring>
using namespace std;

const int N=2e6+5;
int n,m;

struct ALLAN{
	struct Allan{
		int to,nxt;
		char val;
	}edge[N];
	int head[N],idx;
	void add(int x,int y,char z)
	{
		edge[++idx]={y,head[x],z};
		head[x]=idx;
	}
	int sp[N];
	queue<int> q;
	void find_sp(int src)
	{
		memset(sp,-1,sizeof(sp));
		sp[src]=0;  
		q.push(src);
		while(!q.empty())
		{
			int x=q.front(); q.pop();
			for(int i=head[x];i;i=edge[i].nxt)
			{
				int y=edge[i].to;
				if(sp[y]==-1) sp[y]=sp[x]+1,q.push(y);
			}
		}
	}
}g,rg;

int work[N],work_len;
int tmp[N],tmp_len;
char ans[N]; int ans_len;
void solve(bool op)
{
	work_len=1; work[work_len]=1;
	ans_len=tmp_len=0;
	while(work_len)
	{
		tmp_len=0;
		char now='|';
		bool reach_n=false;
		for(int p=1;p<=work_len;p++)
		{
			int x=work[p];
			for(int i=g.head[x];i;i=g.edge[i].nxt)
			{
				int y=g.edge[i].to;
				if(rg.sp[y]==-1) continue;
				if(op && g.sp[x]+1+rg.sp[y]>g.sp[n]) continue;
				if(g.edge[i].val<now)
				{
					reach_n=(y==n);
					tmp_len=1;
					tmp[tmp_len]=y;
					now=g.edge[i].val;
				}
				else if(g.edge[i].val==now)
				{
					if(y==n) reach_n=true;
					tmp[++tmp_len]=y;
				}
			}
		}
		ans[ans_len++]=now;
		if(reach_n) break;
		for(int i=1;i<=tmp_len;i++)
			work[i]=tmp[i];
		work_len=tmp_len;
	}
	ans[ans_len++]='\0';
	printf("%s ",ans);
	return;
}

char str[N];
int main()
{
	freopen("shortway.in","r",stdin);
	freopen("shortway.out","w",stdout);
	
	scanf("%d%d",&n,&m);
	int pos=n;
	for(int i=1;i<=m;i++)
	{
		int x,y; scanf("%d%d%s",&x,&y,str);
		int slen=strlen(str)-1;
		if(!slen) g.add(x,y,str[0]),rg.add(y,x,str[0]);
		else
		{
			pos++; g.add(x,pos,str[0]),rg.add(pos,x,str[0]);
			for(int j=1;j<slen;j++,pos++)
				g.add(pos,pos+1,str[j]),rg.add(pos+1,pos,str[j]);
			g.add(pos,y,str[slen]),rg.add(y,pos,str[slen]);
		}
	}
	g.find_sp(1),rg.find_sp(n);
	solve(1),solve(0);
	return 0;
}

日记和欧拉函数(euler

这道题倒是和赛时估分一分不差。

所有的欧拉函数值很明显可以预处理出来,因为不会写线筛求欧拉函数(现在会了),所以我把每个欧拉函数都直接算了出来,时间复杂度 \(O(R \sqrt{R})\)

然后题目中要求的求和可以很容易想到用前缀和的方式来处理,这样可以 \(O(1)\) 查询。只是没想到赛时为了卡一下常写的 if(res==1) return 1; 用来判断 \(\varphi^{(k)}1=1\) 居然一下子把 \(O(R^2)\) 卡到了 \(O(R \log R)\),真是意外收获。

这样总时间复杂度就是 \(O(R \sqrt{R} + T)\),拿了 \(52\) 分。

赛后去复习补充了线筛求欧拉函数的知识,时间复杂度成功降至 \(O(R \log R + T)\),得到 \(72\)

赛时乱打的 $52$ 分代码
#include<cstdio>
#include<cmath>
#include<algorithm>
#define LL long long
using namespace std;

const int T=1e5+5,R=1e6+5;
int t,b;
pair<int,int> q[T];

int Phi(int x) //O(sqrt(R))
{
	int res=x,sx=sqrt(x);
	for(int i=2;i<=sx;i++)
	{
		if(x%i==0)
		{
			while(x%i==0)
				x/=i;
			res=res/i*(i-1);
		}
	}
	if(x>1) res=res/x*(x-1);
	return res;
}
int phi[R];
void Init_phi(int maxr) //O(R*sqrt(R))
{
	for(int i=1;i<=maxr;i++)
		phi[i]=Phi(i);
	return;
}

int ExPhi(int x,int y) //O(R)
{
	int res=x;
	for(int i=1;i<=y;i++)
	{
		res=phi[res];
		if(res==1) return 1;
	}
	return res;
}
int exphi[R];
LL sum[R];
void Init_sum(int maxr) //O(R^2)
{
	int mx=0;
	for(int i=1;i<=maxr;i++)
	{
		mx=max(mx,phi[i]);
		exphi[i]=ExPhi(i,mx-b);
		sum[i]=sum[i-1]+exphi[i];
	}
	return;
}

int main()
{
	freopen("euler.in","r",stdin);
	freopen("euler.out","w",stdout);
	
	scanf("%d%d",&t,&b);
	int maxr=0;
	for(int i=1;i<=t;i++)
	{
		int l,r; scanf("%d%d",&l,&r);
		maxr=max(maxr,r);
		q[i]={l,r};
	}
	Init_phi(maxr);
	Init_sum(maxr);
	for(int i=1;i<=t;i++)
	{
		int l=q[i].first,r=q[i].second;
		printf("%lld\n",sum[r]-sum[l-1]);
	}
	return 0;
}
赛后乱打的 $72$ 分代码
#include<cstdio>
#include<cmath>
#include<bitset>
#include<algorithm>
#define LL long long
using namespace std;

const int T=1e5+5,R=1e6+5,PR=8e4+5;
int t,b;
pair<int,int> q[T];

int phi[R];
bitset<R> not_prime;
int prime[PR],prime_idx;
void Init_phi(int maxr)
{
	//Euler_sieve / Linear_sieve
	not_prime[1]=true;
	phi[1]=1;
	for(int i=2;i<=maxr;i++)
	{
		if(!not_prime[i])
		{
			prime[++prime_idx]=i;
			phi[i]=i-1;
		}
		for(int j=1;j<=prime_idx && 1ll*i*prime[j]<=maxr;j++)
		{
			not_prime[i*prime[j]]=true;
			if(i%prime[j]==0)
			{
				phi[i*prime[j]]=phi[i]*prime[j];
				break;
			}
			else phi[i*prime[j]]=phi[i]*phi[prime[j]];
		}
	}
	return;
}

int ExPhi(int x,int y) //O(R)
{
	int res=x;
	for(int i=1;i<=y;i++)
	{
		res=phi[res];
		if(res==1) return 1;
	}
	return res;
}
int exphi[R];
LL sum[R];
void Init_sum(int maxr) //O(R^2)
{
	int mx=0;
	for(int i=1;i<=maxr;i++)
	{
		mx=max(mx,phi[i]);
		exphi[i]=ExPhi(i,mx-b);
		sum[i]=sum[i-1]+exphi[i];
	}
	return;
}

int main()
{
	freopen("euler.in","r",stdin);
	freopen("euler.out","w",stdout);
	
	scanf("%d%d",&t,&b);
	int maxr=0;
	for(int i=1;i<=t;i++)
	{
		int l,r; scanf("%d%d",&l,&r);
		maxr=max(maxr,r);
		q[i]={l,r};
	}
	Init_phi(maxr);
	Init_sum(maxr);
	for(int i=1;i<=t;i++)
	{
		int l=q[i].first,r=q[i].second;
		printf("%lld\n",sum[r]-sum[l-1]);
	}
	return 0;
}

为什么说正解不像个正解呢?这个正解其实是在找规律,规律就是只有从 \(B\) 到后面某一截是需要单独计算的,之前的都以等差数列形式排布,而之后的全部都是 \(1\),然后就做出来了。

然而有一个测试点(测试数据 #5)一直在 \(900\) ms+,其它数据都只用几十毫秒,只有这个一枝独秀,鹤立鸡群,我加了记忆化卡常后保持 \(800\) ms 左右的稳定发挥,完全不符合理论时间复杂度,搞得我头疼。

玄之又玄的 $100$ 分代码
#include<cstdio>
#include<cmath>
#include<bitset>
#include<algorithm>
#define LL long long
using namespace std;

const int L=50;
int t,b;

int Phi(int x) //O(sqrt(R))
{
	int res=x,sx=sqrt(x);
	for(int i=2;i<=sx;i++)
	{
		if(x%i==0)
		{
			while(x%i==0)
				x/=i;
			res=res/i*(i-1);
		}
	}
	if(x>1) res=res/x*(x-1);
	return res;
}
int ExPhi(int x,int y) //O(R)
{
	int res=x;
	for(int i=1;i<=y;i++)
	{
		res=Phi(res);
		if(res==1) return 1;
	}
	return res;
}

int prime_less_b;
LL sum_bL;
bool is_prime(int x)
{
	for(int i=2;i*i<=x;i++)
		if(x%i==0) return false;
	return true;
}

LL arith(int x)
{
	return 1ll*(1+x)*x/2;
}
LL getsum(int x)
{
	if(x<=b) return arith(x);
	else if(x<=b+L)
	{
		LL res=arith(b);
		int mxphi=prime_less_b-1;
		for(int i=b+1;i<=x;i++)
		{
			mxphi=max(mxphi,Phi(i));
			res+=ExPhi(i,mxphi-b);
		}
		return res;
	}
	else
	{
		LL res=arith(b)+sum_bL;
		res+=x-(b+L);
		return res;
	}
}

int main()
{
	freopen("euler.in","r",stdin);
	freopen("euler.out","w",stdout);
	
	scanf("%d%d",&t,&b);
	for(int i=b;i>=2;i--)
		if(is_prime(i))
		{
			prime_less_b=i;
			break;
		}
	int tmxphi=prime_less_b-1;
	for(int i=b+1;i<=b+L;i++)
	{
		tmxphi=max(tmxphi,Phi(i));
		sum_bL+=ExPhi(i,tmxphi-b);
	}
	for(int i=1;i<=t;i++)
	{
		int l,r; scanf("%d%d",&l,&r);
		printf("%lld\n",getsum(r)-getsum(l-1));
	}
	return 0;
}
玄之又玄的 $100$ 分代码 · 记忆化卡常
#include<map>
#include<cmath>
#include<cstdio>
#include<algorithm>
#include<unordered_map>
#define LL long long
using namespace std;

const int L=50;
int t,b;

unordered_map<int,int> phi;
int Phi(int x) //O(sqrt(R))
{
	if(phi[x]) return phi[x];
	int savex=x,res=x,sx=sqrt(x);
	for(int i=2;i<=sx;i++)
	{
		if(x%i==0)
		{
			while(x%i==0)
				x/=i;
			res=res/i*(i-1);
		}
	}
	if(x>1) res=res/x*(x-1);
	return phi[savex]=res; //xÒѾ­¸Ä±ä¹ýÁË£¡ 
}
map<pair<int,int>,int> exphi;
int ExPhi(int x,int y) //O(R)
{
	if(exphi[{x,y}])
		return exphi[{x,y}];
	int res=x;
	for(int i=1;i<=y;i++)
	{
		res=Phi(res);
		if(res==1) return 1;
	}
	return exphi[{x,y}]=res;
}

int prime_less_b;
LL sum_bL;
bool is_prime(int x)
{
	for(int i=2;i*i<=x;i++)
		if(x%i==0) return false;
	return true;
}

LL arith(int x)
{
	return 1ll*(1+x)*x/2;
}
LL getsum(int x)
{
	if(x<=b) return arith(x);
	else if(x<=b+L)
	{
		LL res=arith(b);
		int mxphi=prime_less_b-1;
		for(int i=b+1;i<=x;i++)
		{
			mxphi=max(mxphi,Phi(i));
			res+=ExPhi(i,mxphi-b);
		}
		return res;
	}
	else
	{
		LL res=arith(b)+sum_bL;
		res+=x-(b+L);
		return res;
	}
}

int main()
{
	freopen("euler.in","r",stdin);
	freopen("euler.out","w",stdout);
	
	scanf("%d%d",&t,&b);
	for(int i=b;i>=2;i--)
		if(is_prime(i))
		{
			prime_less_b=i;
			break;
		}
	int tmxphi=prime_less_b-1;
	for(int i=b+1;i<=b+L;i++)
	{
		tmxphi=max(tmxphi,Phi(i));
		sum_bL+=ExPhi(i,tmxphi-b);
	}
	for(int i=1;i<=t;i++)
	{
		int l,r; scanf("%d%d",&l,&r);
		printf("%lld\n",getsum(r)-getsum(l-1));
	}
	return 0;
}

日记和二叉搜索树(tree

这道题赛时做的一脸懵逼,到头来只打了个 \(8\) 分暴力。

其实分析大样例可以很容易发现特殊性质 \(s(1)\) 的答案就是 \(0\),还能再赚 \(16\) 分,但是我当时被这题搞得有点懵,所以就没注意到。

而且特殊性质 \(s(2)\) 时最优排列就是这棵树的中序遍历序列,这个也想到了的,没去打真是不该,又少 \(8\) 分,唉。

(注:如无特殊说明,下面所说的“子树”都是指以当前节点的子节点为根的子树)

赛时思路和题解核心思想已经大差不差了,即对于每一个点,以它作为 LCA 的贡献就是权值比它小的子树大小和乘以权值比它大的子树大小和。而且赛时已经证出来了当这两部分大小最接近时贡献最大,只是没想出来如何让这两部分大小最接近,且它到底是什么编号无关紧要的这一点没有想到,所以没打出来。

赛后补充了 01 背包求这一部分的相关知识:具体来说,就是将每一棵子树的体积和价值都设为它的大小,然后在总体积不超过 \(\frac{sum-1}{2}\) 的前提下找最大价值,这个最大价值 \(t\) 乘以 \((sum-1-t)\)(减一是因为减去当前节点自己)就是当前节点的最大贡献。

因为每一次背包的时间复杂度都是 \(O(son_x \times size_x)\),其中 \(s_x\) 表示 \(x\) 的子节点数量,而 \(\sum son_x = n-1\),所以总时间复杂度为 \(O(N^2)\),且一般卡不满,故期望得分 \(48\) 分。

但是!(在加了特判链的情况下)它就是能跑到 \(96\) 分(还是在某些老年机上),包括一大堆 \(N \le 10^6\) 的点都卡过了(\(N\) 方过百万是吧),所以我说这个数据水呢~

赛后莫名其妙的 $96$ 分代码
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;

const int N=1e6+5,M=2e6+5;
int n;
long long ans;

struct Allan{
	int to,nxt;
}edge[M];
int head[N],idx;
inline void add(int x,int y)
{
	edge[++idx]={y,head[x]};
	head[x]=idx;
	return;
}

int sz[N];
vector<int> sonsz[N];
int sonf[N];
void DFS(int x,int fa=0)
{
	sz[x]=1;
	for(int i=head[x];i;i=edge[i].nxt)
	{
		int y=edge[i].to;
		if(y==fa) continue;
		DFS(y,x);
		sz[x]+=sz[y];
		sonsz[x].push_back(sz[y]);
	}
	
	int mxsz=(sz[x]-1)>>1;
	for(int i=1;i<=mxsz;i++)
		sonf[i]=0;
	for(int i=0;i<(int)sonsz[x].size();i++)
		for(int j=mxsz;j>=(int)sonsz[x][i];j--)
			sonf[j]=max(sonf[j],sonf[j-(int)sonsz[x][i]]+sonsz[x][i]);
	ans+=1ll*sonf[mxsz]*(sz[x]-1-sonf[mxsz]);
	return;
}

int deg[N];
int main()
{
	freopen("tree.in","r",stdin);
	freopen("tree.out","w",stdout);
	
	scanf("%d",&n);
	bool is_s1=true;
	for(int i=1;i<n;i++)
	{
		int x,y; scanf("%d%d",&x,&y);
		add(x,y),add(y,x);
		deg[x]++,deg[y]++;
		if(deg[x]>2||deg[y]>2) is_s1=false;
	}
	if(is_s1)
	{
		printf("0\n");
		return 0;
	}
	DFS(1);
	printf("%lld\n",ans);
	return 0;
}

日记和编辑器(edit

一眼看过去:数据结构。

然后发现前四种操作暴力都是 \(O(\sum \lvert s \rvert)\) 的,而第五种操作用 KMP 优化一下也是 \(O(\sum \lvert s \rvert)\) 的。

加上一共 \(n\) 次操作,而 \(\sum \lvert s \rvert \le 10n\),所以这样暴力的时间复杂度是 \(O(N^2)\),期望得分 \(40 \sim 60\) 分,实际得分 \(60\) 分。

(据说不用 KMP 的 \(O(N^3)\) 暴力也是 \(60\) 分,这不公平!)

正解似乎要用平衡树,但是我还是不会。

终于正常一点的 $60$ 分代码
#include<cstdio>
#include<cstring>
using namespace std;

const int N=1e5+5,LEN=1e6+5,PLEN=25;
int n;
char S[LEN]; int len;

void Insert(int x,char str[])
{
	int slen=strlen(str+1);
	for(int i=len+slen;i>x+slen;i--)
		S[i]=S[i-slen];
	for(int i=x+1;i<=x+slen;i++)
		S[i]=str[i-x];
	len+=slen;
	return;
}
void Delete(int l,int r)
{
	int slen=r-l+1;
	for(int i=r+1;i<=len;i++)
		S[i-slen]=S[i];
	len-=slen;
	return;
}
void Replace(int l,int r,char str[])
{
	Delete(l,r);
	Insert(l-1,str);
	return;
}
int Count(int l,int r,char ch)
{
	int res=0;
	for(int i=l;i<=r;i++)
		if(S[i]==ch) res++;
	return res;
}
char p[PLEN];
int plen,nxt[LEN];
void KMP_Init()
{
	plen=strlen(p+1);
	nxt[1]=0;
	for(int i=2,j=0;i<=plen;i++)
	{
		while(j && p[j+1]!=p[i]) j=nxt[j];
		if(p[j+1]==p[i]) j++;
		nxt[i]=j;
	}
	return;
}
int Search(int l,int r)
{
	int res=0;
	for(int i=l,j=0;i<=r;i++)
	{
		while(j && p[j+1]!=S[i]) j=nxt[j];
		if(p[j+1]==S[i]) j++;
		if(j==plen) res++,j=nxt[j];
	}
	return res;
}

char tmp[LEN];
int main()
{
	freopen("edit.in","r",stdin);
	freopen("edit.out","w",stdout);
	
	scanf("%d%s",&n,p+1);
	KMP_Init();
	for(int i=1;i<=n;i++)
	{
		char op[15]; scanf("%s",op);
		if(op[0]=='I') //Insert
		{
			int x; scanf("%d%s",&x,tmp+1);
			Insert(x,tmp);
		}
		if(op[0]=='D') //Delete
		{
			int l,r; scanf("%d%d",&l,&r);
			Delete(l,r);
		}
		if(op[0]=='R') //Replace
		{
			int l,r; scanf("%d%d%s",&l,&r,tmp+1);
			Replace(l,r,tmp);
		}
		if(op[0]=='C') //Count
		{
			int l,r; char c[5];
			scanf("%d%d%s",&l,&r,c);
			int ans=Count(l,r,c[0]);
			printf("%d\n",ans);
		}
		if(op[0]=='S') //Search
		{
			int l,r; scanf("%d%d",&l,&r);
			int ans=Search(l,r);
			printf("%d\n",ans);
		}
	}
	return 0;
}
posted @ 2024-11-01 17:12  Jerrycyx  阅读(27)  评论(0编辑  收藏  举报