CSP-S 2023 题解

CSP-S 2023 题解

T1 密码锁

观察到锁的状态数量很少,可以考虑暴力搜索每一个状态判断合法性。令 k=10,时间复杂度 O(10k×k)

code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int kmax = 15;

struct V {
	int a[6];
} v[kmax];

int n;
int res;
int p[kmax];

bool C(int id) {
	int tot = 0;
	for(int i = 1; i <= 5; i++) {
		tot += (p[i] == v[id].a[i]);
	}
	if(tot == 5) return 0; // 完全相同
	if(tot == 4) return 1; // 只有一个不同
	if(tot < 3) return 0; // 超过两个不同
	for(int i = 1; i < 5; i++) { // 枚举相同的两位
		if(p[i] != v[id].a[i] && p[i + 1] != v[id].a[i + 1]) {
			int x = (p[i] + 1) % 10, y = (p[i + 1] + 1) % 10;
			for(; x != p[i]; x = (x + 1) % 10, y = (y + 1) % 10) { // 暴力枚举变化量
				if(x == v[id].a[i] && y == v[id].a[i + 1]) return 1;
			} 
		}
	}
	return 0;
}

bool Check() {
	for(int i = 1; i <= n; i++) {
		if(!C(i)) return 0; 
	}
	return 1;
}

void Dfs(int x) {
	if(x > 5) {
		res += Check();
		return;
	}
	for(int i = 0; i < 10; i++) {
		p[x] = i;
		Dfs(x + 1);
	}
}

int main() {
	// freopen("lock.in", "r", stdin);
	// freopen("lock.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n;
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= 5; j++) {
			cin >> v[i].a[j];
		}
	}
	Dfs(1); // 暴搜
	cout << res << '\n';
	return 0;
}

T2 消消乐

考虑dp。定义 pi 表示离 i 最近的满足 s(pi,i) 是一个合法串的位置,fi 表示以 i 这个位置结尾的合法串的数量,那么有转移 fi=fpi1+1,表示这个串要么单独成串,要么接在最近的合法串后面。答案就是 ans=fi,时间复杂度 O(n2)

考虑优化,重新定义 pi,j 表示对于前 i 个字符而言,离 i 最近的满足 spi,j=js(pi,j+1,i) 是一个合法串的位置,那么对于一个位置 i,它能匹配 s(pi1,si,i)。转移从 fpi1,si 转移过来。因为 s(pi1,si,i) 匹配成了一个合法串,并且将 s(pi1,si+1,i1) 中的所有合法子串合并成了一个合法串,那么接下来所有的匹配的位置 p 只能 <pi1,si,对每一种字符继承一下即可。记 m=26,时间复杂度 O(nm)

常数还是有点大,发现瓶颈在于要枚举每种字符。发现跳 p 的过程是一条链,用数组 lst 维护每条链的链头,每次只需要修改链头即可。时间复杂度 O(n)

code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using namespace std;

const int kmax = 2e6 + 3;
const int kmaxM = 26;

int n, a[kmax][kmaxM], f[kmax];
int lst[kmax];
string str;
long long res;

int main() {
  freopen("game.in", "r", stdin);
  freopen("game.out", "w", stdout);
  cin >> n >> str;
  str = ' ' + str;
  for(int i = 1; i <= n; i++) {
    lst[i] = i;
    int pre = a[lst[i - 1]][str[i] - 'a']; // 取出匹配的位置
    if(pre) {
        // cout << "***\n";
        lst[i] = lst[pre - 1]; // 修改链头
        f[i] = f[pre - 1] + 1; // 计算贡献
    }
    a[lst[i]][str[i] - 'a'] = i;
    res += f[i];
  }
  cout << res << '\n';
  return 0;
}

T3 结构体

大模拟,比较麻烦。对于每个结构体,需要存储它的变量数量、所有变量类型及名称、每个变量的对齐要求和地址偏移量,结构体的长度和该结构体的对齐要求。对于每个变量,存储它的起始位置、长度、对齐要求、类型、名字。为方便处理,要开两个 std::map 记录每个定义的结构体/变量的编号。初始时只有 byteshortintlong 四种基本类型。

定义一个结构体时,根据题面提示中给定的结构体地址分配方式计算当前结构体的长度、对齐要求,以及结构体中每一个变量的地址偏移量。

定义一个变量时,它的长度和对其方式与该变量类型的长度和对齐方式相同,只需要按照题意根据上一个变量的起始位置和长度来求出当前变量的起始位置。

访问一个变量的起始位置时,不断地将字符串中的变量名称拎出来,这样可以定位到每一个结构体内的哪一个变量及它的类型。注意第一次是全局变量名称,然后才是结构体中的变量名称,要分开处理。每一个结构体/变量的名称长度 10,可以暴力找变量名称。

查询每一个位置是由哪一个变量占据时,先二分求出它是在哪一个全局变量内,然后不断递归,每一层也同样二分出在哪一个变量内。如果在某一层内发现询问的位置上是空的,就可以返回无解了。注意在最底层,即到了基本类型的时候,虽然不用往下递归了,但依然要判断询问的位置是否被占据。如果有解,用一个栈维护变量的名称,每次返回的时候将变量的名称弹入栈中。

code

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <map>

using namespace std;

const int kmax = 105;

struct Strct {
	int num;
	string tpe[kmax];
	string nam[kmax];
	long long o[kmax], a[kmax];
	long long len, mx;
} strcc[kmax];

struct Chg {
	long long beg, len, mx;
	string tpe, nam;
} chage[kmax];

map<string, int> sct, chg;
int n, ct, ctt;
int tp;
string str, strr;
string resstr[kmax];

void Addstr() {
	cin >> str;
	sct[str] = ++ct;
	cin >> strcc[ct].num;
	for(int i = 1; i <= strcc[ct].num; i++) {
		cin >> strcc[ct].tpe[i] >> strcc[ct].nam[i];
		int x = sct[strcc[ct].tpe[i]];
		strcc[ct].mx = max(strcc[ct].mx, strcc[x].mx);
		if(i > 1) {
			long long lst = strcc[ct].o[i - 1] + strcc[sct[strcc[ct].tpe[i - 1]]].len;
			if(lst % strcc[x].mx == 0) {
				strcc[ct].o[i] = lst;
			} else {
				strcc[ct].o[i] = (lst / strcc[x].mx + 1) * strcc[x].mx;
			}
		}
	}
	long long lst = strcc[ct].o[strcc[ct].num] + strcc[sct[strcc[ct].tpe[strcc[ct].num]]].len;
	if(lst % strcc[ct].mx == 0) {
		strcc[ct].len = lst;
	} else {
		strcc[ct].len = (lst / strcc[ct].mx + 1) * strcc[ct].mx;
	}
	cout << strcc[ct].len << ' ' << strcc[ct].mx << '\n';
}

void Addchg() {
	cin >> str >> strr;
	chg[strr] = ++ctt;
	chage[ctt].tpe = str;
	chage[ctt].mx = strcc[sct[str]].mx;
	chage[ctt].len = strcc[sct[str]].len;
	chage[ctt].nam = strr;
	if(ctt > 1) {
		long long lst = chage[ctt - 1].beg + chage[ctt - 1].len;
		if(lst % chage[ctt].mx == 0) {
			chage[ctt].beg = lst;
		} else {
			chage[ctt].beg = (lst / chage[ctt].mx + 1) * chage[ctt].mx;
		}
	}
	cout << chage[ctt].beg << '\n';
}

void Findbeg() {
	cin >> str;
	long long res = 0;
	long long lst;
	for(int i = 0, j = 0; i < (int)str.size(); i = ++j) {
		for(; j < (int)str.size() && str[j] != '.'; j++) {
		}
//		cout << str.substr(i, j - i) << '\n';
		if(i == 0) {
			res = chage[chg[str.substr(i, j - i)]].beg;
			lst = sct[chage[chg[str.substr(i, j - i)]].tpe];
		} else {
			for(int k = 1; k <= strcc[lst].num; k++) {
				if(strcc[lst].nam[k] == str.substr(i, j - i)) {
					res += strcc[lst].o[k];
					lst = sct[strcc[lst].tpe[k]];
				}
			}
		}
	} 
	cout << res << '\n';
}

bool Dfs(long long pos, long long lst) {
	if(lst == -1) {
		int l = 1, r = ctt;
		for(int mid; l <= r;) {
			mid = (l + r) >> 1;
			if(chage[mid].beg <= pos) {
				l = mid + 1;
			} else {
				r = mid - 1;
			}
		}
		int res = l - 1;
		if(res == 0) {
			resstr[++tp] = "ERR";
			return 0;
		}
		if(chage[res].beg + chage[res].len < pos) {
			resstr[++tp] = "ERR";
			return 0;
		} 
		if(!Dfs(pos - chage[res].beg, sct[chage[res].tpe])) return 0;
		resstr[++tp] = chage[res].nam;
		return 1;
	}
//	cout << "lst = " << lst << ' ' << pos << '\n';
	if(!strcc[lst].num) {
		if(lst <= 4 && pos < strcc[lst].len) {
//			cout << "lst = " << lst << ' ' << strcc[lst].len << ' ' << strcc[lst].mx << '\n';
			return 1;
		} else {
			resstr[++tp] = "ERR";
			return 0;
		}
	}
	int l = 1, r = strcc[lst].num;
	for(int mid; l <= r;) {
		mid = (l + r) >> 1;
		if(strcc[lst].o[mid] <= pos) {
			l = mid + 1;
		} else {
			r = mid - 1;
		}
	}
	int res = l - 1;
	if(!res) {
		resstr[++tp] = "ERR";
		return 0;
	}
	if(strcc[lst].o[res] + strcc[sct[strcc[lst].tpe[res]]].len < pos) {
		resstr[++tp] = "ERR";
		return 0;
	}
	if(!Dfs(pos - strcc[lst].o[res], sct[strcc[lst].tpe[res]])) return 0;
//	cout << "str = " << strcc[lst].nam[res] << '\n';
	resstr[++tp] = strcc[lst].nam[res];
	return 1;
}

void Findpos() {
	long long pos;
	cin >> pos;
	tp = 0;
	Dfs(pos, -1);
	cout << resstr[tp--];
	for(; tp; tp--) {
		cout << '.' << resstr[tp];
	}
	cout << '\n';
}

int main() {
	freopen("struct.in", "r", stdin);
	freopen("struct.out", "w", stdout);
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	sct["byte"] = ++ct, strcc[ct].len = strcc[ct].mx = 1;
	sct["short"] = ++ct, strcc[ct].len = strcc[ct].mx = 2;
	sct["int"] = ++ct, strcc[ct].len = strcc[ct].mx = 4;
	sct["long"] = ++ct, strcc[ct].len = strcc[ct].mx = 8;
	cin >> n;
	for(int i = 1, op; i <= n; i++) {
		cin >> op;
		if(op == 1) {
			Addstr();
		} else if(op == 2) {
			Addchg();
		} else if(op == 3) {
			Findbeg();
		} else {
			Findpos();
		}
	} 
	return 0;
} 

T4 种树

答案显然满足单调性,可以先二分出答案。

假设二分出的答案为 k,那么我们要求每个节点的树要在 k 时刻后至少长到给定的高度,那么每个节点都有一个最晚访问时间,设 tx 表示 x 的最晚访问时间,那么 x 点的树不能晚于 tx 种植。所有点的最晚访问时间可以通过二分求出。

同时,t 数组还满足子树依赖关系,即可能有些点要满足子树的要求而提前最晚访问时间,具体的,有 tx=min(tx,minyson(x)ty1)

最后将 t 数组排序,从小到大依次选取,如果不能在限制之前取出则无解,否则有解。

时间复杂度 O(nlogVlogn),实现时要求常数要小。

code

#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cstring>

using namespace std;

const int kmax = 1e5 + 3;

struct Tree {
  long long a;
  int b, c;
  int d;
} tr[kmax];

struct E {
  int p, y;
} e[kmax << 1];

int n;
int h[kmax], ec;
int f[kmax];
int l[kmax];
bool b[kmax];

void Dfs(int x, int fa) {
  for(int i = h[x]; i; i = e[i].p) {
    int y = e[i].y;
    if(y == fa) continue;
    Dfs(y, x);
    l[x] = min(l[x], l[y] - 1);
  }
}

__int128 Calc(int id, __int128 lst) {
  if(tr[id].c >= 0) return lst * tr[id].b + lst * (lst + 1) / 2 * tr[id].c;
  __int128 rs = (1 - tr[id].b) / tr[id].c;
  if(lst <= rs) return lst * tr[id].b + lst * (lst + 1) / 2 * tr[id].c;
  return rs * tr[id].b + rs * (rs + 1) / 2 * tr[id].c + lst - rs;
}

int Pos(int id, long long k) {
  int l = 1, r = n;
  __int128 ps = Calc(id, k);
  for(int mid; l <= r;) {
    mid = (l + r) >> 1;
    if(ps - Calc(id, mid - 1) >= tr[id].a) {
      l = mid + 1;        
    } else {
      r = mid - 1;
    }
  }
  return l - 1;
}

bool Check(long long k) {
  for(int i = 1; i <= n; i++) {
    if(Calc(i, k) < tr[i].a) return 0;
    l[i] = Pos(i, k);
  }
  Dfs(1, 0);
  sort(l + 1, l + n + 1);
  for(int i = 1; i <= n; i++) {
    if(l[i] < i) return 0;
  }
  return 1;
}

void Addedge(int x, int y) {
  e[++ec] = {h[x], y};
  h[x] = ec;
}

int main() {
  freopen("tree.in", "r", stdin);
  freopen("tree.out", "w", stdout);
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  cin >> n;
  for(int i = 1; i <= n; i++) {
    cin >> tr[i].a >> tr[i].b >> tr[i].c;
  }
  for(int i = 1, x, y; i < n; i++) {
    cin >> x >> y;
    Addedge(x, y);
    Addedge(y, x);
  }
  long long l = n, r = 1e9;
  for(long long mid; l <= r;) {
    mid = (l + r) >> 1;
    if(Check(mid)) {
      r = mid - 1;
    } else {
      l = mid + 1;
    }
  }
  cout << r + 1 << '\n';
  return 0;
}
posted @   ereoth  阅读(499)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示