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

阿巴阿巴好累

posted @ 2025-04-13 22:14  AdviseDY  阅读(52)  评论(0)    收藏  举报