CSP-S2023题解

实则没有薄纱

分数线

一等分数线

115pts
先不看题,根据某些讲题人的亲身经历,我们分析一下如何拿到一等,甚至是河南省前十。
首先,T1的100pts都是容易的。
此时大约开赛40min,你就已经看完四道题,并且拿到100pts了。
T2 现在应该一看题就会 35pts,然后只写个 15min 就能写完,然后对着 35pts 优化就会 50pts,相当于你在开赛 70min 就会有 150pts,超过了某人一整场的分数。
此时你对着T3的大模拟陷入了沉思,果断投入T4的怀抱,容易发现,T4你瞅一眼就有50pts,如果你在认真看两眼,你就发现,T4的C性质和链差不多,假装你没发现,于是你开始写,我猜你大概40min就可以写完,于是,你大约在开赛110min的时候,就有了200pts,约等于当年的孔令钊。
接下来分为两种情况。
然后如果你是一个有梦想的人,你将开始写大模拟,某个讲评人当时没有读懂题,获得了5pts 的好成绩,现在这个讲评人写了 77min 就写完了,加上读题时间差不多 85min 就有了 T3 的 100pts。
于是,你开赛195min,获得了300pts的高分,薄纱赵沄充,达到河南省第五。
此时距离比赛结束只有45min了,如果你是一个保守的人,你将会开始写对拍和检查代码,于是最终你获得了300pts,HA第五的好成绩。
如果你并非保守,死磕 T2,如果你非常牛,这个讲题人从看题到 AC,只用了 40min,然后这个时候你就有了 350pts,约等于当年源神,然后如果你再写了 T4 的 C性质,你就能薄纱当年源神。
如果你没有梦想,于是你死磕T2,大概率有结果,于是你大概可以在开赛180min的时候A掉,获得 250pts 高分,薄纱当年赵旭航。
因为你没有梦想,所以你对着大模拟的性质A随便写了写,获得了15pts的高分,于是你拿到了265pts的高分,薄纱某一中学长。
如果你和Air一样强,看了看T2就秒了,那么你就有可能看着T4灵机一动,发现会了A性质就会了整道题。于是,你将在比赛中获得315pts的高分,薄纱当年赵沄充。
最坏情况下,你只过了T1,T2写了35pts,T3 15pts,T4 20pts。总分170pts,薄纱常梓良。
然后我们看看熟人,发现,刘瑾豪那年拿到了150pts,被你薄纱了。
好,那么我们接下来开始将如何真正薄纱各位学长,和孙培轩一样AK这场比赛。

[CSP-S 2023] 密码锁

容易发现,最多状态只有 \(10^5\) 种,所以直接对于每一个密码,处理出来这个密码对应的正确密码可能有哪些,然后给这些密码 \(cnt\)++ ,最后看哪些密码的 \(cnt\)\(n\) 即可。

#include<bits/stdc++.h>
#define int long long 
using namespace std;
int n;
const int N=1e5+100;
int cnt[N],vis[N],l[10];
int read() {int x;cin>>x;return x;}
void bfs(int x) {
	vis[x]=1;
	int xx=x;
	int cntt=0;
	for(int i=1;i<=5;i++) {
		int mask=xx;
		int k1=mask/l[i]%10;
		for(int j=1;j<=9;j++) {
			mask=xx;
			mask=mask-k1*l[i]+(j*l[i]+k1*l[i]+l[i-1])%l[i-1];
			if(vis[mask]) continue;
			vis[mask]=1;cnt[mask]++;
			cntt++;
		}
	}
	for(int i=1;i<=4;i++) {
		int mask=xx;
		int k1=mask/l[i]%10;
		int k2=mask/l[i+1]%10;
		for(int j=1;j<=9;j++) {
			mask=xx;
			mask=mask-k1*l[i]+(j*l[i]+k1*l[i])%l[i-1];
			mask=mask-k2*l[i+1]+(k2*l[i+1]+j*l[i+1])%l[i];
			if(vis[mask]) continue;
			vis[mask]=1;
			cnt[mask]++;
		}
	}
}
signed main() {
	n=read();
	l[0]=100000;l[1]=10000;l[2]=1000;l[3]=100;l[4]=10;l[5]=1;l[6]=1;
	for(int i=1;i<=n;i++) {
		int t=0;
		for(int j=1;j<=5;j++) t=t*10+read();
		bfs(t);
		memset(vis,0,sizeof(vis));
	}
	int ans=0;
	for(int i=0;i<=99999;i++) 
		if(cnt[i]==n) ans++;
	cout<<ans<<endl;
	return 0;
}

[CSP-S 2023] 消消乐

题意

给你个字符串,问你可删除的子串个数。

删除是指如果有相邻的相等的字符,删掉他们。

\(50\)

\(O(n ^ 2)\) 是好做的,枚举起点,维护个栈,然后往后扫即可。

然而我当年写这套题连这个都不会。

正解

我们考虑计算以每个字符为结尾的方案数,那么我们可以设计状态 \(f_i\) 表示以 \(i\) 结尾的方案数,那么我们有以下转移:

\[f_i = f_t + 1 \]

其中 \(t\) 是能使 \([t + 1, i]\) 合法的最大值。

那么我们只要能求出 \(t\) 就万事大吉了。

因为 \(t\) 最大,所以就有 \(s_{t + 1} = s_i\)

我们考虑如何才能使 \([t + 1, i]\) 合法,我们可以通过一直往前跳最短的合法串来做,这就是我们的 \(t\),代码如下。

// Problem: P9753 [CSP-S 2023] 消消乐
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P9753
// Memory Limit: 512 MB
// Time Limit: 1000 ms
// Author: Air2011
// 
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define Air
namespace io{inline int read(){int f=1,t=0;char ch=getchar();while(ch<'0'||ch>'9'){if(ch=='-')f=-f;ch=getchar();}while(ch>='0'&&ch<='9'){t=t*10+ch-'0';ch=getchar();}return t*f;}inline void write(int x){if(x<0){putchar('-');x=-x;}if(x>=10){write(x/10);}putchar(x%10+'0');}}
using namespace io;
int n;
const int N = 2e6 + 10;
string s;
int dp[N];
int t[N];
signed main() {
#ifndef Air
	freopen(".in","r",stdin);
	freopen(".out","w",stdout);
#endif
	n = read();
	cin >> s;
	s = ' ' + s;
	for(int i = 1; i <= n; i++){
		int npos = i;
		while(npos != 0){
			if(npos != 0){
				if(s[npos - 1] == s[i]){
					npos --;
					break;
				}
			}
			npos = t[npos - 1];
		}
		if(npos == 0) continue;
		t[i] = npos;
		dp[i] = dp[npos - 1] + 1;
	}
	int ans = 0;
	for(int i = 1; i <= n; i++){
		ans += dp[i];
	}
	cout << ans;
	return 0;
}

[CSP-S 2023] 结构体

题目背景

在 C++ 等高级语言中,除了 int 和 float 等基本类型外,通常还可以自定义结构体类型。在本题当中,你需要模拟一种类似 C++ 的高级语言的结构体定义方式,并计算出相应的内存占用等信息。

题目描述

在这种语言中,基本类型共有 \(4\) 种:byteshortintlong,分别占据 \(1\)\(2\)\(4\)\(8\) 字节的空间。

定义一个结构体类型时,需要给出类型名成员,其中每个成员需要按顺序给出类型名称。类型可以为基本类型,也可以为先前定义过的结构体类型。注意,定义结构体类型时不会定义具体元素,即不占用内存。

定义一个元素时,需要给出元素的类型名称。元素将按照以下规则占据内存:

  • 元素内的所有成员将按照定义时给出的顺序在内存中排布,对于类型为结构体的成员同理。
  • 为了保证内存访问的效率,元素的地址占用需要满足对齐规则,即任何类型的大小和该类型元素在内存中的起始地址均应对齐到该类型对齐要求的整数倍。具体而言:
    • 对于基本类型:对齐要求等于其占据空间大小,如 int 类型需要对齐到 \(4\) 字节,其余同理。
    • 对于结构体类型:对齐要求等于其成员的对齐要求的最大值,如一个含有 intshort 的结构体类型需要对齐到 \(4\) 字节。

以下是一个例子(以 C++ 语言的格式书写):

struct d {
    short a;
    int b;
    short c;
};
d e;

该代码定义了结构体类型 d 与元素 e。元素 e 包含三个成员 e.ae.be.c,分别占据第 \(0 \sim 1\)\(4 \sim 7\)\(8 \sim 9\) 字节的地址。由于类型 d 需要对齐到 \(4\) 字节,因此 e 占据了第 \(0 \sim 11\) 字节的地址,大小为 \(12\) 字节。

你需要处理 \(n\) 次操作,每次操作为以下四种之一:

  1. 定义一个结构体类型。具体而言,给定正整数 \(k\) 与字符串 \(s, t_1, n_1, \dots, t_k, n_k\),其中 \(k\) 表示该类型的成员数量,\(s\) 表示该类型的类型名,\(t_1, t_2, \dots, t_k\) 按顺序分别表示每个成员的类型,\(n_1, n_2, \dots, n_k\) 按顺序分别表示每个成员的名称。你需要输出该结构体类型的大小和对齐要求,用一个空格分隔。

  2. 定义一个元素,具体而言,给定字符串 \(t, n\) 分别表示该元素的类型与名称。所有被定义的元素将按顺序,从内存地址为 \(0\) 开始依次排开,并需要满足地址对齐规则。你需要输出新定义的元素的起始地址。

  3. 访问某个元素。具体而言,给定字符串 \(s\),表示所访问的元素。与 C++ 等语言相同,采用 . 来访问结构体类型的成员。如 a.b.c,表示 a 是一个已定义的元素,它是一个结构体类型,有一个名称为 b 的成员,它也是一个结构体类型,有一个名称为 c 的成员。你需要输出如上被访问的最内层元素的起始地址。

  4. 访问某个内存地址。具体而言,给定非负整数 \(addr\),表示所访问的地址,你需要判断是否存在一个基本类型的元素占据了该地址。若是,则按操作 3 中的访问元素格式输出该元素;否则输出 ERR

输入格式

\(1\) 行:一个正整数 \(n\),表示操作的数量。

接下来若干行,依次描述每个操作,每行第一个正整数 \(op\) 表示操作类型:

  • \(op = 1\),首先输入一个字符串 \(s\) 与一个正整数 \(k\),表示类型名与成员数量,接下来 \(k\) 行每行输入两个字符串 \(t_i, n_i\),依次表示每个成员的类型与名称。

  • \(op = 2\),输入两个字符串 \(t, n\),表示该元素的类型与名称。

  • \(op = 3\),输入一个字符串 \(s\),表示所访问的元素。

  • \(op = 4\),输入一个非负整数 \(addr\),表示所访问的地址。

输出格式

输出 \(n\) 行,依次表示每个操作的输出结果,输出要求如题目描述中所述。

【提示】

对于结构体类型的对齐要求和大小,形式化的定义方式如下:

  • 设该结构体内有 \(k\) 个成员,其大小分别为 \(s_1,...,s_k\),对齐要求分别为 \(a_1,...,a_k\);
  • 则该结构体的对齐要求为 \(a=\max\{a_1,...,a_k\}\)
  • 再设这些成员排布时的地址偏移量分别为 \(o_1,...,o_k\),则:
    • \(o_1 = 0\);
    • 对于 \(i=2,...,k\)\(o_i\) 为满足 \(o_{i-1}+s_{i-1}\le o_i\)\(a_i\) 整除 \(o_i\) 的最小值;
    • 则该结构体的大小 \(s\) 为满足 \(o_k+s_k\le s\)\(a\) 整除 \(s\) 的最小值;

对于定义元素时的内存排布,形式化的定义方式如下:

  • 设第 \(i\) 个被定义的元素大小为 \(s_i\),对齐要求为 \(a_i\),起始地址为 \(b_i\);
  • \(b_1 = 0\),对于 \(2\le i\)\(b_i\) 为满足 \(b_{i-1} + s_{i-1}\le b_i\)\(a_i\) 整除 \(b_i\) 的最小值。

做法显然,模拟即可。

[CSP-S 2023] 种树

题目描述

给定一颗树,对于树上的每个节点而言,从这个节点上的树被种下的当天开始,每天树都会长高 \(\max(b_i+x\times c_i,i)\) 米( \(x\) 是从开始给整颗树种树的那天开始的时间,而不是对于某一个节点上的树开始种下的时间。)

你每天可以给一个未种树且与某个已种树的节点之间有边的点种树,求使所有节点的树都长成对应高度 \(a_i\) 的最短时间。

20pts

直接状压,看每个节点种过树了没有,然后往外拓展就行。

性质A( \(c_i=0\) )

对于 \(c_i=0\) 的点,完成任务所需时间和种下时间无关,所以你可以直接预处理出来每个点完成所需时间,然后,你直接每次找到能更新的,所需时间最长的点,把它更新了。

性质B (链)

链显然直接按顺序做就行了。

100pts

考虑二分答案,直接二分某一个时间,看是不是能在这个时间内完成。

那就想如何判断。一个显然的事情是对于每个点,要是想要这个点上的树,你要是想要让它在某个时间之前完成任务,你就一定要在某个时刻之前给它种下,而这个时刻你是可以二分出来的。那么,问题就可以转化成能否在某一时刻之前种下这棵树。

那么,你考虑,对于每一个点,它被种下的条件显然是对于它的所有祖先节点都已经被种下了,所以你每个点最后种下的时刻应该是对子树里的点取 \(min\)

然后就和性质A很像了,你每次处理能够处理的点里面最后被种下时间最短的那个即可。

时间复杂度是 \(O(nlog^2)\)

需要注意的是,本题需要卡常。

#include<bits/stdc++.h>
#define min(a,b) (a>b?b:a)
using namespace std;
int n,tot=0;
const int N=2e5+10;
typedef long long ll;
ll a[N];
int b[N],c[N],lat[N];
bitset<N>vis;
// vector<int>e[N];
int dis[N],head[N],ver[N],nxt[N],e[N];
void add(int u,int v){ver[++tot]=v;nxt[tot]=head[u];head[u]=tot;}
struct node {
	int x,dat;
	friend bool operator <(node a,node b) {return a.dat>b.dat;}
};
namespace io {
	class In {
		public:
			template<typename T>
			inline In &operator>>(T &x) {
				x=0; bool f=0; char c=getchar();
				while(c<'0'||c>'9') f|=(c=='-'),c=getchar();
				while(c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
				if(c=='.') {
					c=getchar(); double dot=0.1;
					while(c>='0'&&c<='9') x+=(c-'0')*dot,dot*=0.1,c=getchar();
				} return (f?x=-x:x),*this;
			}
			inline In &operator>>(char &x) {while(isspace(x=getchar())); return *this;}
			inline In &operator>>(char *x) {
				char c=getchar(); while(isspace(c)) c=getchar(); while(!isspace(c)&&~c) *(x++)=c,c=getchar();
				return *x=0,*this;
			}
			inline In &operator>>(string &x) {
				char c=getchar(); x.clear();
				while(isspace(c)) c=getchar(); while(!isspace(c)&&~c) x.push_back(c),c=getchar();
				return *this;
			}
			inline In &operator>>(In &in) { return in;}
	};
	class Out {
		private:
			char buf[35]; short dot=6,top=0;
		public:
			template<typename T>
			inline Out &operator<<(T x) {
				if(x<0) putchar('-'),x=-x;
				do { buf[++top]=x%10,x/=10;} while(x);
				while(top) putchar(buf[top--]|'0'); return *this;
			}
			inline Out &operator<<(char c) {return putchar(c),*this;}
			inline Out &operator<<(string x) {for(auto c:x) putchar(c); return *this;}
			inline Out &operator<<(char *x) {while(*x) putchar(*(x++)); return *this;}
			inline Out &operator<<(const char *x) {while(*x) putchar(*(x++)); return *this;}
			inline Out &operator<<(double x) {snprintf(buf,sizeof(buf),"%.*lf",dot,x); return (*this)<<buf;}
			inline Out &operator<<(Out &out) {return out;}
			inline Out &setdot(const int n) {return dot=n,*this;}
	};
	In fin;
	Out fout;
	inline Out &setdot(const int n,Out& out=fout) {return fout.setdot(n),out;}
	inline In &getline(char *x,In& in=fin) {
		char c=getchar();
		while(!(c==' '||!isspace(c))) c=getchar(); while(c==' '||!isspace(c)) (*x++)=c,c=getchar();
		return *x=0,in;
	}
	inline In &getline(string &x,In& in=fin) {
		char c=getchar(); x.clear();
		while(!(c==' '||!isspace(c))) c=getchar(); while(c==' '||!isspace(c)) x.push_back(c),c=getchar();
		return in;
	}
}
using namespace io;
inline int read() {int x;fin>>x;return x;}
inline ll readll() {ll x;fin>>x;return x;}
int flag=0;
void dfs(int x,int fa) {
	for(int i=head[x];i;i=nxt[i]) {
		int y=ver[i];
		if(y==fa) continue;
		dfs(y,x);
		lat[x]=min(lat[x],lat[y]-1);
	}
}
bool check(int X) {
	int cnt=0;
	for(int i=1;i<=n;++i) {
		int l=0,r=n;
		while(l+1<r) {
			int mid=(l+r)>>1;
			if(check1(mid,X,a[i],b[i],c[i])) 
				l=mid;
			else r=mid-1;
		}
		if(check1(r,X,a[i],b[i],c[i])) lat[i]=r;
		else lat[i]=l;
	}
	flag=X;
	dfs(1,0);
	priority_queue<node>q;
	for(int i=1;i<=n;++i) vis[i]=0;
	q.push({1,lat[1]});
	vis[1]=1;
	int now=0;
	while(!q.empty()) {
		++now;
		node x=q.top();q.pop();
		if(now>x.dat) return 0;
		for(int i=head[x.x];i;i=nxt[i]) {
			int y=ver[i];
			if(vis[y]) continue;
			vis[y]=1;
			q.push({y,lat[y]});
		}
	}
	return 1;
}
inline void write(int x) {
	fout<<x<<"\n";
} 
signed main() {
	n=read();
	for(int i=1;i<=n;++i) {a[i]=readll();b[i]=read();c[i]=read();}
	for(int i=1;i<n;++i) {
		int u,v;
		u=read();v=read();
		add(u,v);
		add(v,u);
	}
	int l=n,r=1e9;
	while(l+1<r) {
		int mid=(l+r)>>1;
		if(check(mid)) r=mid;
		else l=mid+1;
	}
	if(check(l)) {write(l);}
	else write(r);
	return 0;
}
posted @ 2025-10-19 18:33  wjx_2010  阅读(69)  评论(5)    收藏  举报