2023 CCPC 湘潭邀请赛题解 更新至 8 题 (2023 Jiangsu Collegiate Programming Contest)
Preface
拖了好久,半个月前的vp,补完题之后一直没有写题解,题的一些细节几乎都快记不起来了,写的有点含糊。
最近打了重庆市赛和南昌,长春邀请赛,感觉要打成sb了。
每次写代码的时候都是正式比赛的时候,其余时间全在赶作业ddl。
认定是打完南昌太自信了,长春打了个稀巴烂,历史最差战绩,自己纯纯战犯。打比赛态度消极+只睡了5个小时脑子不清醒,赛时表现太差。下次比赛一定不能give up,还有赛前晚上一定不能v太晚,vp黑龙江省赛的题到凌晨2点,给我干成了省空间小王子,uint16都试出来了。
焯怎么这周还有考试。
我会在代码一些有必要的地方加上注释,签到题可能一般就不会写了.
以下是代码火车头:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <set>
#include <queue>
#include <map>
#include <unordered_map>
#include <iomanip>
#include <functional>
#include <random>
#include <bitset>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#include <ext/pb_ds/priority_queue.hpp>
#define endl '\n'
#define int long long
#define rep(i,a,b) for (int aa = a, bb = b, i = aa; i <= bb; i++)
#define rep2(i,a,b) for (int aa = a, bb = b, i = aa; i >= bb; i--)
namespace pbds = __gnu_pbds;
using namespace std;
template<class Key>
using pbds_set = pbds::tree<Key, pbds::null_type, std::less<Key>,
pbds::rb_tree_tag, pbds::tree_order_statistics_node_update>;
template<class T>
using pbds_queue = pbds::priority_queue<T>;
template<typename T>
using vec = vector<T>;
using PII = pair<int, int>;
using INT = __int128;
template<typename T>
void cc(const vector<T>& tem) {
for (const auto& x : tem) cout << x << ' ';
cout << endl;
}
template<typename... Args>
void cc(const Args &... a) {
size_t idx = 0;
((std::cout << a << (++idx == sizeof...(Args) ? "\n" : " ")), ...);
}
void cc() { cout << endl; }
void fileRead() {
#ifdef LOCALL
freopen("D:\\AADVISE\\cppvscode\\CODE\\in.txt", "r", stdin);
freopen("D:\\AADVISE\\cppvscode\\CODE\\out.txt", "w", stdout);
#endif
}
void kuaidu() { ios::sync_with_stdio(false), cin.tie(0), cout.tie(0); }
inline int max(int a, int b) {
if (a < b) return b;
return a;
}
inline double max(double a, double b) {
if (a < b) return b;
return a;
}
inline int min(int a, int b) {
if (a < b) return a;
return b;
}
inline double min(double a, double b) {
if (a < b) return a;
return b;
}
void cmax(int& a, const int& b) { if (b > a) a = b; }
void cmin(int& a, const int& b) { if (b < a) a = b; }
void cmin(double& a, const double& b) { if (b < a) a = b; }
void cmax(double& a, const double& b) { if (b > a) a = b; }
int gcd(int a, int b) {
if (!b) return a;
return gcd(b, a % b);
}
template<const int T>
int Kuai(int a, int b) {
int l = 1;
while (b) {
if (b % 2) l = l * a % T;
a = a * a % T, b /= 2;
}
return l;
}
int getb(int i, int j) { return i >> j & 1; }
Problem A. Today's Word
首先可以知道,当这个操作的次数比较多的时候,显然最后那一段长度为m的那一段,每一个字符都会进行next操作,而每26次就是一个循环,这个时候我们可以直接得出来答案了。
大概的计算一下,10的100次方对26取模是16,也就是说我们其实操作16次之后就会进入一个循环了。
所以这16次我们可以直接模拟做,但是有一个问题是,16次操作之后不一定就会大于m,所以我们需要16+26次,然后直接输出答案就好了。
//--------------------------------------------------------------------------------
const int N = 2e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
//const int mod = 998244353;
const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;
//--------------------------------------------------------------------------------
//struct or namespace:
//--------------------------------------------------------------------------------
bool fl = 0;
string dfs(const string& s) {
string ans;
if (fl == 1 or (s.size() / 4 >= m)) {
fl = 1;
rep(i, s.size() - m, s.size() - 1) {
if (s[i] < 'z')
ans.push_back((char)(s[i] + 1));
else ans.push_back('a');
}
return ans;
}
ans = s.substr(0, s.size() / 2) + s;
rep(i, 0 + s.size() / 2, s.size() - 1) {
if (s[i] < 'z')
ans.push_back((char)(s[i] + 1));
else ans.push_back('a');
}
return ans;
}
signed main() {
fileRead();
kuaidu();
T = 1;
// cin >> T;
while (T--) {
cin >> n >> m;
string s; cin >> s;
rep(i, 1, 26 + 16) {
s = dfs(s);
}
rep(i, s.size() - m, s.size() - 1) cout << s[i];
}
return 0;
}
/*
4
aaaa
aaaa
17
sofiawilumerrymee
sofiawillumarryme
32
takethesebrokenwingandlearntofly
takethesesunkeneyesandlearntosee
*/
Problem E. LCM Plus GCD
首先这个需要一个二进制枚举的容斥的前置知识,大概是这样的:
我们假设容斥的元素是2个A,B,那么一般会有A+B再去掉A和B的交集。
假设容斥的元素是3个,A,B,C,那么就是A+B+C-AB-AC-BC+ABC
如果我们的元素是n个,我们可以用(1<<n)-1,二进制的形式去描述,1代表当前这个元素参与,0代表不参与,然后这个+和-如何判断呢?其实我们会发现,如果参与的元素的个数是奇数,那么就是+,如果是偶数就是-。所以我们可以二进制枚举,然后通过判断有多少个1代表当前操作的贡献我们是加上还是减去。
那么这个题和容斥的关系在哪里呢?
首先我们假设lcm是gcd的y倍,gcd的值是a,那么a+ay=x。
我们可以枚举出来x的因数,a(y+1)=x,一个因数为a,另一个因数就是y+1。
那现在我们需要保证lcm是gcd的y倍,我们可以对y这个数字进行质因数分解,拆成\(2^{a1}*3^{a2}*5^{a3}*...\)的形式,然后我们需要保证,这些质因数分配到k个数字上去(也就是说我们要把a这个数字分配到k个数字上去,每一个数字分配的范围都是0到a之间),不能有相同的,然后这些a1,a2等必须起码有一个数字分配给这k个数字里面的其中一个分配的是0,(也就是a没有给这一个数字分配,这样才能够保证gcd还是a,否则gcd就会是a的倍数)。另外,还需要保证分配完了之后,a会被分配到起码有一个数字对应的值是a。(就是说有一个数字分配的值是a,要满足每一个a都是这样的),这一步就是为了满足lcm确实是y的倍数,否则分配后实际的lcm会比lcm小,因为a没有分配完。
然后容斥的内容大概是这样的:
我们的dfs函数是做一个容斥,但是有限制是gcd等于1.
我们可以假设一个完整全面的情况是gcd为1的倍数,而我们希望gcd为1,那么就需要用这个gcd为1的倍数去减去gcd是2,3,5等的倍数,但是由于会减去重复的,所以我们要再加上是6的倍数,10的倍数,之类的,但是又因为重复了,所以又会进行减去之类的操作,这样反反复复。
int dfs(vec<int>& tem) {
int len = tem.size();
int lim = (1ll << len) - 1;
int ans = 0;
rep(i, 0, lim) {
vec<int> tem2(tem);
bool ad = 1;
rep(j, 0, len - 1) {
if (getb(i, j)) {
ad = 1 - ad;
tem2[j] -= 1;
}
}
if (ad) ans += dfs2(tem2);
else ans -= dfs2(tem2);
ans %= mod, ans += mod, ans %= mod;
// cc(ans);
}
return ans;
}
例如在这段代码中,我们设置二进制里1代表是这个数的倍数,不考虑0的意义。
那么一开始全是0的时候,算出来的贡献是gcd是1的倍数的贡献,然后有一个位置是1的时候,就代表要减去gcd是这个数的倍数的贡献,如果有两个1,就代表要把gcd是这个数字的倍数的贡献加回来。
以上代码的内容的含义最后计算出来的结果就是gcd为1的时候的贡献的值,通过容斥将除了1以外的那些倍数都减去了。
而lcm是否等于y的判断在下面代码的dfs2的内容中,意思也是大同小异,只不过这里的内容是去减掉lcm是这个数的因数的贡献,例如加上lcm是这个数以及因数的贡献,然后减去lcm/2的这个数以及因数的贡献,减去lcm/3的这个数的以及因数的贡献,再加上...
具体的内容见代码:
//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
//const int mod = 998244353;
const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;
//--------------------------------------------------------------------------------
//struct or namespace:
class FENJIE {
int mul(int a, int b, int m) {
return static_cast<__int128>(a) * b % m;
}
int power(int a, int b, int m) {
int res = 1 % m;
for (; b; b >>= 1, a = mul(a, a, m))
if (b & 1) res = mul(res, a, m);
return res;
}
vector<int> ddd(vector<PII> tem) {
vector<int> p;
int l = 1;
function<void(int)> f = [&](int n) {
if (n == tem.size()) {
p.push_back(l);
return;
}
if (tem[n].second) {
tem[n].second -= 1;
l *= tem[n].first;
f(n);
l /= tem[n].first;
tem[n].second += 1;
}
f(n + 1);
};
f(0);
sort(p.begin(), p.end());
return p;
}
public:
//求出一个数所有的质因数
vector<int> factorize(int n) {
vector<int> p;
function<void(int)> f = [&](int n) {
if (n <= 10000) {
for (int i = 2; i * i <= n; ++i)
for (; n % i == 0; n /= i)
p.push_back(i);
if (n > 1) p.push_back(n);
return;
}
if (isprime(n)) {
p.push_back(n);
return;
}
auto g = [&](int x) { return (mul(x, x, n) + 1) % n; };
int x0 = 2;
while (true) {
int x = x0, y = x0, d = 1, power = 1, lam = 0, v = 1;
while (d == 1) {
y = g(y), ++lam, v = mul(v, abs(x - y), n);
if (lam % 127 == 0) { d = gcd(v, n), v = 1; }
if (power == lam) { x = y, power *= 2, lam = 0, d = gcd(v, n), v = 1; }
}
if (d != n) {
f(d), f(n / d);
return;
}
++x0;
}
};
f(n);
sort(p.begin(), p.end());
return p;
}
//判断一个数是不是质数
bool isprime(int n) {
if (n < 2) return false;
static constexpr int A[] = { 2, 3, 5, 7, 11, 13, 17, 19, 23 };
int s = __builtin_ctzll(n - 1);
int d = (n - 1) >> s;
for (auto a : A) {
if (a == n) return true;
int x = power(a, d, n);
if (x == 1 || x == n - 1) continue;
bool ok = false;
for (int i = 0; i < s - 1; ++i) {
x = mul(x, x, n);
if (x == n - 1) {
ok = true;
break;
}
}
if (!ok) return false;
}
return true;
}
//求出一个数的质因数,PII形式,代表质因数和指数
vector<PII> zhiyinshu(int n) {
vector<int> tem = factorize(n);
vector<PII> tmp;
tem.push_back(-1);
int las = 0, cnt = 0;
for (auto x : tem) {
if (x != las) {
if (las)tmp.push_back({ las, cnt });
cnt = 1, las = x;
}
else cnt++;
}
return tmp;
}
//求出一个数的所有的因数
vector<int> factor(int n) { return ddd(zhiyinshu(n)); }
} fj;
namespace nn {
const int N = 1e5 + 10;
vector<int> fact, infact;
void clear() {
fact.resize(N + 5);
infact.resize(N + 5);
fact[0] = infact[0] = 1;
for (int i = 1; i < N; i++) fact[i] = fact[i - 1] * i % mod;
infact[N - 1] = Kuai<mod>(fact[N - 1], mod - 2);
for (int i = N - 2; i >= 1; i--) {
infact[i] = infact[i + 1] * (i + 1) % mod;
}
}
int C(int n, int m) {
if (n < m or m < 0) return 0;
return fact[n] * infact[m] % mod * infact[n - m] % mod;
}
}
//--------------------------------------------------------------------------------
int k;
int dfs2(vec<int>& tem) {
int len = tem.size();
int lim = (1ll << len) - 1;
int ans = 0;
rep(i, 0, lim) {
bool ad = 1;
int val = 1;
rep(j, 0, len - 1) {
if (getb(i, j)) {
ad = 1 - ad;
val *= tem[j];
val %= mod;
}
else {
val *= tem[j] + 1;
val %= mod;
}
}
if (ad) ans += nn::C(val, k);
else ans -= nn::C(val, k);
// cc(ans);
ans %= mod, ans += mod, ans %= mod;
}
return ans;
}
int dfs(vec<int>& tem) {
int len = tem.size();
int lim = (1ll << len) - 1;
int ans = 0;
rep(i, 0, lim) {
vec<int> tem2(tem);
bool ad = 1;
rep(j, 0, len - 1) {
if (getb(i, j)) {
ad = 1 - ad;
tem2[j] -= 1;
}
}
if (ad) ans += dfs2(tem2);
else ans -= dfs2(tem2);
ans %= mod, ans += mod, ans %= mod;
// cc(ans);
}
return ans;
}
signed main() {
fileRead();
kuaidu();
nn::clear();
T = 1;
//cin >> T;
while (T--) {
cin >> n >> k;
vec<int> tem = fj.factor(n);
int ans = 0;
for (auto& a : tem) {
int y = n / a - 1;
if (y <= 1) continue;
vec<PII> zys1 = fj.zhiyinshu(y);
vec<int> zys;
for (auto& [a, b] : zys1) {
zys.push_back(b);
}
// cc(y);
// cc(zys);
ans += dfs(zys);
ans %= mod;
}
cc(ans);
}
return 0;
}
Problem F. Timaeus
一个简单dp,但是一开始想的不太对,因为题干要求两个人里面只能选择一个,所以如果用递推的那种形式,我们无法保证这个选择只会选择一个,所以用这个记忆化递归的这种形式。
但是式子写出来之后需要特判一下B是1的情况,否则状态转移会转移到自己的身上。
特判的情况队友张神直接给了式子,我直接傻乎乎腾上去。
//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
//const int mod = 998244353;
const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;
//--------------------------------------------------------------------------------
//struct or namespace:
double dp[M];
bitset<M> fl;
double p, q;
int A, B;
//--------------------------------------------------------------------------------
double dfs(int x) {
if (x < 0) return -INF;
if (fl[x]) return dp[x];
if (x == 0) return 0;
fl[x] = 1;
cmax(dp[x], p * (dfs(x - B) + 2) + (1 - p) * (dfs(x - B) + 1));
cmax(dp[x], q * (dfs(x - B + 1) + 1) + (1 - q) * (dfs(x - B) + 1));
return dp[x];
}
signed main() {
fileRead();
kuaidu();
T = 1;
//cin >> T;
while (T--) {
int p1, q1;
cin >> A >> B >> p1 >> q1;
p = p1 * 1.0 / 100;
q = q1 * 1.0 / 100;
if (B == 1) {
double q1 = (p * 2) + (1 - p);
double q2 = (1.0) / (1 - q);
double ans = max(q1, q2) * A;
cc(ans);
continue;
}
double ans = dfs(A);
cout << fixed << setprecision(10) << ans << endl;
// continue;
}
return 0;
}
Problem H. Neil's Machine
我也忘了具体细节了,貌似是个签到模拟题?
//--------------------------------------------------------------------------------
const int N = 2e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
//const int mod = 998244353;
const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;
//--------------------------------------------------------------------------------
//struct or namespace:
//--------------------------------------------------------------------------------
int A[N];
signed main() {
fileRead();
kuaidu();
T = 1;
// cin >> T;
while (T--) {
cin >> n;
string s, t;
cin >> s >> t;
rep(i, 0, n - 1) {
A[i] = s[i] - t[i] + 26;
A[i] %= 26;
}
int st = 0;
while (st < n and A[st] == 0) st++;
if (st >= n) {
cc(0);
continue;
}
set<int> S;
int las = 0;
int ans = 0;
rep(i, st, n - 1) {
int ed = i + 1;
while (A[ed] == A[ed - 1] and ed < n) ed++;
ed -= 1;
int tem = (A[i] + las) % 26;
// if (tem != 0) ans++;
ans++;
las += A[i];
i = ed;
}
cc(ans);
}
return 0;
}
/*
4
aaaa
aaaa
17
sofiawilumerrymee
sofiawillumarryme
32
takethesebrokenwingandlearntofly
takethesesunkeneyesandlearntosee
*/
Problem I. Elevator
纯纯签到,直接输出n-m+1
//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
//const int mod = 998244353;
const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;
//--------------------------------------------------------------------------------
//struct or namespace:
//--------------------------------------------------------------------------------
signed main() {
fileRead();
kuaidu();
T = 1;
cin >> T;
while (T--) {
cin >> n >> m;
if (n == m) {
cc(1);
continue;
}
cc(n - m + 1);
}
return 0;
}
Problem J. Similarity (Easy Version)
直接模拟他说的这个
//--------------------------------------------------------------------------------
const int N = 2e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
//const int mod = 998244353;
const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;
//--------------------------------------------------------------------------------
//struct or namespace:
//--------------------------------------------------------------------------------
string A[N];
int dfs(const string& s1, const string& s2) {
int ans = 0;
int l1 = s1.size(), l2 = s2.size();
rep(i, 0, l1 - 1) {
rep(j, 0, l2 - 1) {
int tem = 0;
rep(p, 0, 100) {
if ((i + p) >= l1 or (j + p) >= l2) break;
if (s1[i + p] == s2[j + p]) tem++;
else break;
}
cmax(ans, tem);
}
}
return ans;
}
signed main() {
fileRead();
kuaidu();
T = 1;
cin >> T;
while (T--) {
cin >> n;
rep(i, 1, n) {
cin >> A[i];
}
int ans = 0;
rep(i, 1, n) {
rep(j, i + 1, n) {
cmax(ans, dfs(A[i], A[j]));
}
}
cc(ans);
}
return 0;
}
/*
4
aaaa
aaaa
17
sofiawilumerrymee
sofiawillumarryme
32
takethesebrokenwingandlearntofly
takethesesunkeneyesandlearntosee
*/
Problem K. Similarity (Hard Version)
一个sb构造题,好久没做过构造了,感觉脑子已经秀逗了。
我们可以构造aaabaaabaaa这样的形式,每m个a,然后搭配一个b这样的形式。
然后下一个字符串就构造成aaacaaacaaa这种形式,然后到z的之后a可以变成b,然后还是接着上面的套娃。
//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
//const int mod = 998244353;
const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;
int k;
//--------------------------------------------------------------------------------
//struct or namespace:
//--------------------------------------------------------------------------------
string dfs(char a, char b) {
string s;
rep(i, 1, k) {
if (i % (m + 1) == 0) {
s.push_back(b);
}
else {
s.push_back(a);
}
}
return s;
}
signed main() {
fileRead();
kuaidu();
T = 1;
//cin >> T;
while (T--) {
cin >> n >> m >> k;
if (m >= k) {
cc("No");
continue;
}
if (m == 0) {
if (n <= 26) {
int tot = 0;
cc("Yes");
rep(i, 1, n) {
rep(j, 1, k) {
char x = 'a' + i - 1;
cout << x;
}
cc();
}
}
else {
cc("No");
}
}
else {
cc("Yes");
int cnt = 0;
rep(i, 0, 25) {
rep(j, i + 1, 25) {
cnt++;
cc(dfs('a' + i, 'a' + j));
if (cnt >= n) goto Z;
}
}
Z:;
}
}
return 0;
}
Problem L. Architect
直接做一个三维差分。要想起来一个有用的trick是查分之后所有的数字应该得是0才可以,否则就说明空间里有非0的存在,那么就一定不行。
//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
//const int mod = 998244353;
const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;
//--------------------------------------------------------------------------------
//struct or namespace:
struct node {
int x, y, z;
bool operator<(const node& q1) const {
if (q1.x != x) return x < q1.x;
if (q1.y != y) return y < q1.y;
return z < q1.z;
}
};
map<node, int> mp;
//--------------------------------------------------------------------------------
void dfs(int x1, int y1, int z1, int x2, int y2, int z2, int fl) {
mp[{x1, y1, z1}] += fl;
mp[{x2 + 1, y1, z1}] -= fl;
mp[{x1, y2 + 1, z1}] -= fl;
mp[{x1, y1, z2 + 1}] -= fl;
mp[{x2 + 1, y2 + 1, z1}] += fl;
mp[{x1, y2 + 1, z2 + 1}] += fl;
mp[{x2 + 1, y1, z2 + 1}] += fl;
mp[{x2 + 1, y2 + 1, z2 + 1}] -= fl;
}
int A[20][20][20];
signed main() {
fileRead();
kuaidu();
T = 1;
cin >> T;
while (T--) {
int w, h, l; cin >> w >> h >> l;
cin >> n;
mp.clear();
rep(i, 1, n) {
int x1, y1, z1, x2, y2, z2;
cin >> x1 >> y1 >> z1 >> x2 >> y2 >> z2;
dfs(x1, y1, z1, x2 - 1, y2 - 1, z2 - 1, 1);
}
dfs(0, 0, 0, w - 1, h - 1, l - 1, -1);
bool flag = 1;
for (auto [a, b] : mp) {
// cc(b);
if (b != 0) {
flag = 0;
break;
}
// A[a.x][a.y][a.z] = b;
}
if (flag) cc("Yes");
else cc("No");
}
return 0;
}
PostScript
能做的事情就是祈求郑州可以发挥好一些