2022 CCPC 江苏省赛题解 更新至 7 题
Preface
这场感觉有些牢了,难度不像是一般的省赛呢。
那个L题真的是卡我半天,还有英文题面差评。导致题意搞错了几次,纯纯英语白痴了。
这个一个人单挑的,真的不希望单挑了,虽然说会提升一些心理素质之类的,但是好累。
赛后补的题也不多,在网上搜到的题解也不多,H是个数位dp,一眼就不想补。索性直接开摆了。
我会在代码一些有必要的地方加上注释,签到题可能一般就不会写了.
以下是代码火车头:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <vector>
#include <set>
#include <queue>
#include <map>
#include <unordered_map>
#include <iomanip>
#define endl '\n'
#define int long long
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define rep2(i,a,b) for(int i=(a);i>=(b);i--)
using namespace std;
template<typename T>
void cc(const vector<T> &tem) {
for (const auto &x: tem) cout << x << ' ';
cout << endl;
}
template<typename T>
void cc(const T &a) { cout << a << endl; }
template<typename T1, typename T2>
void cc(const T1 &a, const T2 &b) { cout << a << ' ' << b << endl; }
template<typename T1, typename T2, typename T3>
void cc(const T1 &a, const T2 &b, const T3 &c) { cout << a << ' ' << b << ' ' << c << endl; }
void cc(const string &s) { cout << s << endl; }
void fileRead() {
#ifdef LOCALL
freopen("D:\\AADVISE\\Clioncode\\untitled2\\in.txt", "r", stdin);
freopen("D:\\AADVISE\\Clioncode\\untitled2\\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; }
using PII = pair<int, int>;
using i128 = __int128;
using vec_int = std::vector<int>;
using vec_char = std::vector<char>;
using vec_double = std::vector<double>;
using vec_int2 = std::vector<std::vector<int> >;
using que_int = std::queue<int>;
Problem A. PENTA KILL!
签到题,但是又觉得不太好签,还WA了一发。
先是用了个哈希,把每个人杀的人存了起来。然后直接暴力n^2枚举,查看区间里面是否有5个及以上的,而且没有重复的人出现的情况,有的话就有五杀,否则没有。
//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;
//--------------------------------------------------------------------------------
//global variable:
//struct or namespace:
//--------------------------------------------------------------------------------
map<string,int> mp;
int cnt = 0;
vec<string> A[N];
signed main() {
fileRead();
kuaidu();
T = 1;
//cin >> T;
while (T--) {
cin >> n;
rep(i, 1, n) {
string a, b;
cin >> a >> b;
if (mp.find(a) == mp.end()) {
cnt++;
mp[a] = cnt;
}
A[mp[a]].push_back(b);
}
bool fl = 0;
rep(i, 1, N-1) {
if (A[i].empty()) continue;
int cnt = 0;
int len = A[i].size();
rep(j, 0, len-1) {
rep(p, j+4, len-1) {
set<string> S;
rep(k, j, p) S.insert(A[i][k]);
if (S.size() >= 5 and S.size() == p - j + 1) fl = 1;
if (fl) break;
}
if (fl) break;
}
if (fl) break;
}
if (fl) cc("PENTA KILL!");
else cc("SAD:(");
}
return 0;
}
/*
*/
Problem B. Prime Ring Plus
赛后补的,但这个题确实很有意思呢。用网络流建图,然后根据边的流量来判断出和谁连接,很帅。
首先先考虑到,每一个数都是两边和自己奇偶性不一样的数字,也就是一个奇数跟一个偶数跟一个奇数等。
然后设出来源点s,汇点t。
源点向每一个奇数的点连接边,容量是2(对应的是每一个奇数两边有链接的数字),然后每一个偶数的点向汇点连接边,容量也是2(对应的是每一个偶数两边也有连接的数字)。
然后再暴力枚举一下哪些奇数加上偶数可以是一质数,有的话就把奇数点向偶数点连接一条边,容量是1。
这样跑一下最大流,流量如果是n,就代表整张图都跑满了,每一个点都有自己相连的两个点。我们再用dfs把他们标记起来,放到一个块里。最后输出他们就好了。
不过要注意的是,奇数点向偶数点连接的边要提前算出来,然后开好空间,否则有可能RE或者T或者M。
然后把define int long long关了。
//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;
//--------------------------------------------------------------------------------
//global variable:
bool fl[N];
//struct or namespace:
namespace ms {
namespace ni {
vector<int> fact, infact;
void cal_ni() {
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;
}
}
namespace pri {
const int N = 1e5 + 10;
vector<int> pri;
bool ispri[N], biao[N];
void cal_pri() {
for (int i = 2; i < N; i++) {
if (biao[i]) continue;
pri.push_back(i);
for (int j = i; j < N; j += i) biao[j] = 1;
}
for (auto &x: pri) ispri[x] = 1;
}
}
}
namespace z {
const int N = 1e5 + 10;
const int M = 1e7 + 10;
int cnt, n, m;
int st, ed;
int head[N], head1[N], dep[N];
struct Z {
int Next;
int to;
int val;
} D[M << 1];
bool bfs() {
queue<int> F;
rep(i, 0, n) dep[i] = 0;
dep[st] = 1;
head1[st] = head[st];
F.push(st);
while (!F.empty()) {
int x = F.front();
F.pop();
for (int i = head[x]; i != -1; i = D[i].Next) {
auto [qwe, y, val] = D[i];
if (dep[y] || val <= 0) continue;
dep[y] = dep[x] + 1;
head1[y] = head[y];
F.push(y);
if (y == ed) return 1;
}
}
return 0;
}
int dinic(int x, int cost) {
if (x == ed) return cost;
int flow = 0;
for (int i = head1[x]; i != -1; i = D[i].Next) {
auto [qwe, y, val] = D[i];
head1[x] = i;
if (dep[y] != dep[x] + 1 || val <= 0) continue;
int tem = dinic(y, min(cost - flow, val));
if (!tem) dep[y] = -1;
D[i].val -= tem;
D[i ^ 1].val += tem;
flow += tem;
if (flow >= cost) break;
}
return flow;
}
void clear(int n1, int m1) {
st = 0, ed = n1 + 5;
n = n1 + 7, m = m1 + 7;
cnt = 0;
rep(i, 0, n) head[i] = -1;
}
void add(int x, int y, int c) {
D[cnt] = {head[x], y, c};
head[x] = cnt++;
D[cnt] = {head[y], x, 0};
head[y] = cnt++;
}
int work() {
int ans = 0;
int flow;
while (bfs()) while (flow = dinic(st, INF)) ans += flow;
return ans;
}
}
namespace zz {
struct ED {
int y;
int val;
};
vector<ED> A[N];
int son[N], dep[N];
bool fl[N];
vec<int> co[N];
int cnt = 0;
void dfs(int x,int l) {
if (fl[x]) return;
co[l].push_back(x);
fl[x] = 1;
for (auto &[y,_]: A[x]) {
dfs(y, l);
}
}
void clear(const int &n) {
rep(i, 1, n) {
A[i].clear();
}
}
void add(const int &x, const int &y, int c = 1) {
A[x].push_back({y, c});
}
};
//--------------------------------------------------------------------------------
signed main() {
fileRead();
kuaidu();
T = 1;
//cin >> T;
ms::pri::cal_pri();
while (T--) {
cin >> n;
vec<PII> ed;
for (int i = 1; i <= n; i += 2) {
for (int j = 2; j <= n; j += 2) {
int val = i + j;
if (ms::pri::ispri[val]) {
ed.push_back({i, j});
}
}
}
z::clear(n + 2, ed.size() + n);
for (auto &[a,b]: ed) {
// cc(a, b);
z::add(a, b, 1);
}
for (int i = 1; i <= n; i += 2) z::add(z::st, i, 2);
for (int i = 2; i <= n; i += 2) z::add(i, z::ed, 2);
int flow = z::work();
// int flow = n;
if (flow != n) {
cc(-1);
continue;
}
// cc(11);
// DSU dsu;
int ed_len = ed.size();
zz::clear(n);
for (int i = 0; i < ed_len * 2; i += 2) {
if (z::D[i ^ 1].val) {
// cc(z::D[i ^ 1].to, z::D[i].to);
zz::add(z::D[i ^ 1].to, z::D[i].to);
zz::add(z::D[i].to, z::D[i ^ 1].to);
}
}
rep(i, 1, n) {
if (zz::fl[i]) continue;
zz::cnt++;
zz::dfs(i, zz::cnt);
}
cc(zz::cnt);
rep(i, 1, zz::cnt) {
cout << zz::co[i].size() << " ";
rep(j, 0, zz::co[i].size()-1) {
cout << zz::co[i][j];
if (j != zz::co[i].size() - 1) cout << " ";
}
cout << endl;
}
}
return 0;
}
/*
*/
Problem C. Jump and Treasure
一个很典的优先队列优化dp。说的高端,其实就是个滑动窗口的感觉。
线段树pass了,本来想直接copy模板的,但是算了下内存,还是算了吧。
提前预处理出来跳的倍数k所对应的答案,然后O1处理查询就好了。大于p的时候就一定是不行,否则就是可以。
dp的式子:
\(dp[i]= max(dp[j]) + a_i \ \ \ \ (i-j<=p)\)
一个优先队列,里面存着两个元素,一个是dp值,一个是对应的下标。然后转移dp的时候,如果下标不符合要求就弹出去。最后留下的这个队头,就是我们要的最大值了。
还有一个坑,就是一定要到n+1这个点!!!!因为这个wa了好几发。
//--------------------------------------------------------------------------------
const int N = 1e6 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;
//--------------------------------------------------------------------------------
//global variable:
int A[N], dp[N];
//struct or namespace:
//--------------------------------------------------------------------------------
int q, p;
void dfs(vec<PII> &tem,int k) {
// cc(k);
// cc(tem);
tem.push_back({n + 1, 0});
int siz = p;
priority_queue<PII> F;
F.push({0, 0});
rep(i, 0, tem.size() - 1) {
while (!F.empty()) {
if (tem[i].first - F.top().second > siz) {
F.pop();
}
else {
break;
}
}
int val = F.top().first + tem[i].second;
F.push({val, tem[i].first});
// cmax(dp[k], val);
if (i == tem.size() - 1) cmax(dp[k], val);
}
}
signed main() {
fileRead();
kuaidu();
T = 1;
//cin >> T;
while (T--) {
cin >> n >> q >> p;
rep(i, 1, n) cin >> A[i];
rep(i, 1, N-1) dp[i] = -INF;
rep(i, 1, p) {
vec<PII> tem;
for (int j = i; j <= n; j += i) tem.push_back({j, A[j]});
dfs(tem, i);
}
rep(i, 1, q) {
int x;
cin >> x;
if (x > p) cc("Noob");
else cc(dp[x]);
}
}
return 0;
}
/*
*/
Problem I. Cutting Suffix
脑筋急转弯,直接把一个集合里塞满一种类型的字符,另一个集合塞其他的,答案就是0.
所以判断一下字符的种类和1的关系就好了。
//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;
//--------------------------------------------------------------------------------
//global variable:
//struct or namespace:
//--------------------------------------------------------------------------------
signed main() {
fileRead();
kuaidu();
T = 1;
//cin >> T;
while (T--) {
string s;
cin >> s;
set<char> S;
for (auto &x: s) S.insert(x);
if (S.size() == 1) {
cc(s.size() - 1);
}
else {
cc(0);
}
}
return 0;
}
/*
*/
Problem J. Balanced Tree
直接打表规律找出来答案。
首先先特判了1e6以内的数据,这个可以直接算出来的。因为一个最简单的式子:
\(dp[x] = dp[x/2] * dp[(x-1)/2] * 2 (x是偶数) \\ dp[x]= dp[x/2]* dp[x/2] (x是奇数)\)
式子的来源就是考虑一个点当根节点,左右节点就是n-1个点去分配,左边是一半,右边则是另一半。然后考虑一下左右两边是否是一样的,也就是去考虑奇偶的关系了。
然后这个式子暴力算出来小数据。然后大数据先打表出来,发现很多都是0,但是有一些部分,在2的k次方的左右,不是0.大概区间是上下波动30,40.不知道具体的,直接开到1000了。
也就是说这个区间以外的,直接0.否则就暴力算出来答案。
是否在这个区间以内的,直接枚举0到64次方里距离n最近的那个点。
另外要注意的是,判断2的64次方和n的差值我用了int128。
//--------------------------------------------------------------------------------
const int N = 1e6 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
// const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;
//--------------------------------------------------------------------------------
//global variable:
ull dp[N];
//struct or namespace:
map<ull, ull> mp;
//--------------------------------------------------------------------------------
ull dfs(ull x) {
if (x < N) {
return dp[x];
}
if (mp.find(x) == mp.end()) {
if (x % 2 == 0) {
return mp[x] = dfs((x - 1) / 2) * dfs(x / 2) * 2;
}
else {
return mp[x] = dfs((x) / 2) * dfs(x / 2);
}
}
else {
return mp[x];
}
}
ull po[100];
signed main() {
fileRead();
kuaidu();
T = 1;
cin >> T;
// ull x = 1024 * 1024;
// cout << x << endl;
po[0] = 1;
rep(i, 1, 63) po[i] = po[i - 1] * 2;
INT mmax = (INT) po[63] * 2;
dp[0] = 1;
dp[1] = 1;
dp[2] = 2;
rep(i, 3, N-1) {
if (i % 2 == 1) {
dp[i] = dp[i / 2] * dp[i / 2];
}
else {
dp[i] = dp[(i - 1) / 2] * dp[i / 2] * 2;
}
}
while (T--) {
ull n;
cin >> n;
if (n < N) {
cc(dp[n]);
continue;
}
ull mmin = -1;
rep(i, 16, 63) {
ull abs;
if (n > po[i]) abs = n - po[i];
else abs = po[i] - n;
if ((mmin == -1) or (abs < mmin)) mmin = abs;
}
INT abs = (INT) mmax - n;
// cc(n, mmin);
if (mmin > 1000 && abs > 1000) {
// cc(n, mmin);
cc(0);
continue;
}
cc(dfs(n));
// rep(i, 1, 100000) {
// if (dp[i] != 0)
// cc(i, dp[i]);
// }
}
return 0;
}
/*
2039 72057594037927936
2040 4503599627370496
2041 70368744177664
2042 1099511627776
2043 4294967296
2044 67108864
2045 262144
2046 1024
2047 1
2048 2048
2049 1048576
2050 536870912
2051 68719476736
2052 35184372088832
2053 4503599627370496
2054 576460752303423488
4088 576460752303423488
4089 4503599627370496
4090 35184372088832
4091 68719476736
4092 536870912
4093 1048576
4094 2048
4095 1
4096 4096
4097 4194304
4098 4294967296
4099 1099511627776
4100 1125899906842624
4101 288230376151711744
8185 288230376151711744
8186 1125899906842624
8187 1099511627776
8188 4294967296
8189 4194304
8190 4096
8191 1
8192 8192
8193 16777216
8194 34359738368
8195 17592186044416
8196 36028797018963968
*/
Problem K. aaaaaaaaaaA heH heH nuN
一个我不喜欢的题,个人感觉一直都不习惯做这种题,有点ex。
我们先把原题的一长串nunhehheh搞成一个A来表示,那么我们首先发现,如果a的长度是k,其实就是提供了2的k次方-1的贡献。
然后这个a对前面的A都是有作用的。
就是说例如,AaAa,其中对于第一个A来说,后面其实有两个a,第二个A来说,后面有一个a,所以贡献就是2的2次方-1和2的1次方-1.
那么如何凑出来n呢?先把n的二进制写出来,有若干个1和0.
假设是1010110,那么对于每一个1在第i位,我们就知道存在一个A,后面有i个a(这个i存到vector里),而且还没完,因为i个a提供的是2^i-1,还差了一个1,我们先存到变量cnt里。
然后每一个1我们都这样处理之后,这个cnt,我们就当做新的n去处理,也就是用个递归去写。(其实不需要这样做,原题给的长度是1e6,差的这个1我们直接补的末尾去也是ok的,赛时没有想这么多)
然后我们最后得到的vector,可能长这样:1 1 1 2 2 ,
这种一样的数字怎么办呢?其实就是长AAAa这个样子,这就是3个1了。
所以我们最后倒着来处理这个字符串就好了。
但是还没完,这个A其实也是会相互利用的,nunhehhehnunhehheh,可以利用前面的一部分拼起来后面的一部分,导致答案计算的不对。
这里我们只需要把nunhehhe放在字符串的最开头,用h来代替我们的A就好了。(交了6发才过,其中忘了把A换成nunhehhe还WA了几发,勾八了)
//--------------------------------------------------------------------------------
const int N = 1e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;
//--------------------------------------------------------------------------------
//global variable:
//struct or namespace:
//--------------------------------------------------------------------------------
vec<int> tem;
void dfs(int x) {
if (x == 0) return;
if (x == 1) {
tem.push_back(1);
return;
}
int y = 0;
if (x % 2 == 1) y += 1;
rep(i, 1, 31) {
if (x >> i & 1) {
y += 1;
tem.push_back(i);
}
}
dfs(y);
}
string B = "ehhehnun";
// string B = "B";
signed main() {
fileRead();
kuaidu();
T = 1;
cin >> T;
while (T--) {
cin >> n;
tem.clear();
map<int,int> mp;
if (n == 0) {
cc("bd");
continue;
}
dfs(n);
// cc(tem);
// sort(tem.begin(), tem.end());
for (auto &x: tem) {
mp[x] += 1;
}
string ans = "";
int has = 0;
bool fl = 0;
for (auto [a,b]: mp) {
rep(i, has+1, a) ans += "a";
has = a;
rep(i, 1, b) ans += "h";
}
ans += B;
reverse(ans.begin(), ans.end());
cc(ans);
// cc(ans.size());
}
return 0;
}
/*
*/
Problem L. Collecting Diamonds
卡到最后的题,没做出来,哭
先说明,操作1是去掉AC的操作,操作2是去掉B的操作。
首先,我们只需要把AAABCCC这样的形式拿出来考虑,其他的形式不用管,不会造成影响。毕竟去掉了AC和去掉了B,最后剩下的部分对其他地方都不会有影响。
一开始以为每一个奇数的位置我们都会先做一遍操作1,但是其实是不对的。如果只有ABC的话,其实我们应该是去掉B,而不是去掉AC,因为他们的贡献都是1,但是去掉了B,可以改变后面的奇偶性,然后多出来一些答案。(例如原本是偶,但是我们前面去掉了B,那么他们就变成了奇,那么就可以自己去掉一个AC,也就是答案+1)
所以我们就大概明确一下我们的思路:
首先提取出来我们要的结构,用一个pair来表示,first是AC这样的半径的长度,second是pos(A的位置),判断奇偶用的。
开始for循环枚举,如果当前的点是奇,那么我们一般来说都会先让他AC的长度-1,ans++,但是要特判一下是否是1,如果是1的话,我们就laz++(laz是用来记录前面删除了几个B的,前面删除了多少个B,那么后面每一个点的操作按理来说都会+1(因为从偶变成了奇,那么就可以自己去掉一个AC)),不是1的话,我们就先减去一个AC,那么他们现在的奇偶性就是偶了。对于偶,我们处理他们的贡献就是半径的长度和laz取个min。
另外要提的一点是,对于特判1的地方,我们不是想laz++就++的,毕竟这个操作是需要前面有过删除B的操作,才可以这样做,否则自己的奇偶性改变不了,自己也做不到操作2了。
//--------------------------------------------------------------------------------
const int N = 4e5 + 10;
const int M = 1e6 + 10;
const int mod = 1e9 + 7;
const int INF = 1e16;
auto kuai = Kuai<mod>;
int n, m, T;
//--------------------------------------------------------------------------------
//global variable:
vec<PII> tem;
int you[N];
//struct or namespace:
//--------------------------------------------------------------------------------
signed main() {
fileRead();
kuaidu();
T = 1;
//cin >> T;
while (T--) {
string s;
cin >> s;
rep2(i, s.size()-1, 0) {
if (s[i] == 'B') you[i] = i;
else you[i] = you[i + 1];
}
int pos = you[0];
while (pos) {
int l = 0, r = 0;
rep2(i, pos-1, 0) {
if (s[i] == 'A') l++;
else break;
}
rep(i, pos+1, s.size()-1) {
if (s[i] == 'C') r++;
else break;
}
int val = min(l, r);
if (val != 0) {
tem.push_back({val, (pos - 1 + 1)});
}
pos = you[pos + 1];
}
// for (auto &[a,b]: tem) {
// cc(a, b);
// }
int ans = 0;
int laz = 0;
rep(i, 0, tem.size()-1) {
if (tem[i].second % 2 == 1) {
if (tem[i].first > 1) {
ans++;
tem[i].first -= 1;
}
else {
if (laz) {
laz++, ans++;
continue;
}
ans++;
continue;
}
}
laz++;
ans += min(laz, tem[i].first);
}
cc(ans);
}
return 0;
}
/*
ABCAAABCCC
AABCAAABCCC
ABC AAAAB AAAAAAAAABCCCCCCC ABC CCAC AAAAAABCCCCCC ABC BA
AAAAAABCCCCCCABCAAAAAABCCCCCCABC
*/
PostScript
阿巴阿巴好累