2022 CCPC湖北省赛

B potion

题意:

有一个容量仅在一半位置有刻度的量杯,有两类水,求最小接水步数使得杯子里面两类水的比例为 x : y,或者输出无解。

分析:

找规律可以发现 最终成立的话 x+y一定是2的次幂

一直不断递推可以发现 成立的条件是一定最初会回到1

比如 3 5

3是怎么来的 4-1=3 出现了1

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
int T;
ll pw[64];
ll gcd(ll aa,ll bb){
	if(bb)
	return gcd(bb,aa%bb);
	return aa;
}
void solve();
int main(){
	pw[0]=1;
	for(int i=1;i<=63;i++)
	pw[i]=pw[i-1]<<1;
	cin>>T;
	while(T--)solve();
     return 0;
}
void solve(){
	ll x,y,a,b;
	scanf("%lld%lld%lld%lld",&x,&y,&a,&b);
	ll gg=gcd(x,y);
	x/=gg,y/=gg;
	int ans=0;
	if(x>y)swap(x,y);
	int pd=0;
	for(int i=1;i<=63;i++){
		if(pw[i]-x==y){
			pd=i;
			break;
		}
	}
	ans=pd+1;
	if(!pd){
		cout<<"-1"<<endl;
		return;
	}
	pd--;
	while(x>0){
		for(int i=1;i<=pd;i++)
		if(pw[i]>=x){
			x=pw[i]-x;
			break;
		}
		if(x==1){
			cout<<ans<<endl;
			return;
		}
	} 
	cout<<"-1"<<endl;
}

F angel

题意:

一排有 n 个洞,有一只兔子在某个洞,每个时刻必定移动到相邻的洞中,需要构造长度最短的询问序列 qi,第 i 项表示询问在兔子第 i 次移动前是否在 qi 这个洞中,使得至少猜中一次。

分析:

猜结论还是挺好猜的 2(n-2)

构造序列的话 考虑从2开始一直不断往n-1赶 一开始在检查点右边的兔子只有往右不断的走

但是可能开始兔子就在检查点的左边 所以再逆向从n-1往2赶一次就好

贪心 一个看起来不难但是有点难度的题

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
int n;
void solve(int);
int main(){
	cin>>n;
	solve(n);
     return 0;
}
void solve(int x){
	if(x==1){
		cout<<1<<endl<<1<<endl;
		return;
	}
	if(x==2){
		cout<<2<<endl<<1<<" "<<1<<endl;
		return;
	}
	cout<<((n-2)<<1)<<endl;
	for(int i=2;i<=n-1;i++)
	cout<<i<<" "<<i<<" ";
}

K ptt

签到题 没啥说的直接模拟就好

#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) x&(-x)
#define ll long long
int n;
double a,b,c; 
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		scanf("%lf%lf",&a,&b);
		if(a>=10000000)c=2.0;
		else if(9800000<=a&&a<=9999999)c=1.0+(a-9800000)/200000;
		else c=(a-9500000)/300000;
		if(b+c>=0)
		printf("%.7lf\n",b+c);
	    else printf("0\n");
	}
     return 0;
}

L. Chtholly and the Broken Chronograph

题意:

给出一个数列 a1, a2, . . . , an,要求支持以下 4 种操作:

  1. 禁用位置 x
  2. 启用位置 x
  3. 对区间 [l, r] 中启用的位置加 x
  4. 查询区间 [l, r] 的和,不论每个位置状态如何

分析

对线段数数据结构还不太熟悉 本来就是一个很裸的线段树题目 还想了好久

本题只要多维护一个值 区间活跃状态的个数即可

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int MAXN = 1e5 + 5;
namespace SegmentTree {
	int n;
	int a[MAXN];
	int s[MAXN];  // 每个元素的状态
	struct Node {
		int l, r;
		int s;  // 元素的状态,s=0表示禁用,s=1表示启用
		int cnt;  // 区间s=1的元素个数
		ll sum;  // 区间和
		ll lazy;  // 加法懒标记
	}SegT[MAXN << 2];

	void push_up(int u) {
		SegT[u].sum = SegT[u << 1].sum + SegT[u << 1 | 1].sum;
		SegT[u].cnt = SegT[u << 1].cnt + SegT[u << 1 | 1].cnt;
	}

	void build(int u, int l, int r) {
		SegT[u].l = l, SegT[u].r = r;
		if (l == r) {
			SegT[u].sum = a[l];
			SegT[u].s = s[l];
			SegT[u].cnt = SegT[u].s;
			return;
		}

		int mid = l + r >> 1;
		build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r);
		push_up(u);
	}

	void push_down(int u) {
		SegT[u << 1].sum += SegT[u].lazy * SegT[u << 1].cnt;
		SegT[u << 1].lazy += SegT[u].lazy;
		SegT[u << 1 | 1].sum += SegT[u].lazy * SegT[u << 1 | 1].cnt;
		SegT[u << 1 | 1].lazy += SegT[u].lazy;
		SegT[u].lazy = 0;
	}

	void modify_state(int u, int x) {  // 反转a[x].s
		if (SegT[u].l == SegT[u].r) {  // 暴力修改叶子节点
			SegT[u].s ^= 1;
			SegT[u].cnt = SegT[u].s;
			return;
		}

		push_down(u);
		int mid = SegT[u].l + SegT[u].r >> 1;
		if (x <= mid) modify_state(u << 1, x);
		else modify_state(u << 1 | 1, x);
		push_up(u);
	}

	void modify_add(int u, int l, int r, int x) {
		if (l <= SegT[u].l && SegT[u].r <= r) {
			SegT[u].sum += (ll)SegT[u].cnt * x;
			SegT[u].lazy += x;
			return;
		}

		push_down(u);
		int mid = SegT[u].l + SegT[u].r >> 1;
		if (l <= mid) modify_add(u << 1, l, r, x);
		if (r > mid) modify_add(u << 1 | 1, l, r, x);
		push_up(u);
	}

	ll query(int u, int l, int r) {
		if (l <= SegT[u].l && SegT[u].r <= r) return SegT[u].sum;

		push_down(u);
		int mid = SegT[u].l + SegT[u].r >> 1;
		ll res = 0;
		if (l <= mid) res += query(u << 1, l, r);
		if (r > mid) res += query(u << 1 | 1, l, r);
		return res;
	}
};
using namespace SegmentTree;

void solve() {
	int q; cin >> n >> q;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= n; i++) cin >> s[i];

	build(1, 1, n);

	while (q--) {
		int op; cin >> op;
		if (op == 1 || op == 2) {
			int x; cin >> x;
			modify_state(1, x);
		}
		else if (op == 3) {
			int l, r, x; cin >> l >> r >> x;
			modify_add(1, l, r, x);
		}
		else {
			int l, r; cin >> l >> r;
			cout << query(1, l, r) << endl;
		}
	}
}

int main() {
	solve();
}

A. Nucleic Acid Test

题意:

给定 n 个点,m 条边的无向图,其中有 k 个核酸检测点。要求从任意一个核酸检测点出发,两次核酸之间不能超过 t 分钟,问若要遍历图上所有点,并在某个核酸点结束,你的速度最小是多少。
n ≤300, t ≤ 1e9,图不一定连通

分析:

既然只需要满足两次核酸之间不超过t分钟 对于核酸点 我们可以建立最小生成树

对于非核酸点 对答案的贡献就是到所有核酸点最短距离的两倍

用floyed和克鲁斯卡尔即可

const int MAXN = 305, MAXM = MAXN * MAXN;
int n, m, k;  // 节点数、边数、核酸点数
int t;  // 最大核酸间隔
int nucleic[MAXN];  // 核酸点编号
bool is_nucleic[MAXN];  // 记录每个节点是否是核酸点

namespace Floyd {
	int n;  // 节点数
	ll d[MAXN][MAXN];  // d[u][v]表示节点u与v间的最短路

	void init() {  // 初始化d[][]
		for (int i = 1; i <= n; i++)
			for (int j = 1; j <= n; j++) d[i][j] = i == j ? 0 : INFF;
	}

	void floyd() {
		for (int k = 1; k <= n; k++) {
			for (int i = 1; i <= n; i++) 
				for (int j = 1; j <= n; j++) d[i][j] = min(d[i][j], d[i][k] + d[k][j]);
		}
	}
}

namespace Kruskal {
	int n, m;  // 节点数、边数
	struct Edge {
		int u, v;
		ll w;

		bool operator<(const Edge& B) { return w < B.w; }
	}edges[MAXM];
	int fa[MAXN];  // 并查集的fa[]数组

	void init() {  // 初始化fa[]
		for (int i = 1; i <= n; i++) fa[i] = i;
	}

	int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); }

	ll kruskal() {  // 返回最小生成树的最长边,图不连通时返回INFF
		sort(edges, edges + m);  // 按边权升序排列

		ll res = 0;  // 最小生成树的最长边
		int cnt = 0;  // 当前连的边数
		for (int i = 0; i < m; i++) {
			auto [u, v, w] = edges[i];
			u = find(u), v = find(v);
			if (u != v) {
				fa[u] = v;
				res = max(res, w);
				cnt++;
			}
		}

		if (cnt < n - 1) return INFF;  // 图不连通
		else return res;
	}
}

void build() {  // 对核酸点建图
	Kruskal::m = 0;
	for (int i = 1; i <= k; i++) {
		for (int j = i + 1; j <= k; j++) 
			Kruskal::edges[Kruskal::m++] = { nucleic[i],nucleic[j],Floyd::d[nucleic[i]][nucleic[j]] };
	}
}

void solve() {
	cin >> n >> m >> k >> t;

	if (!t) {
		cout << -1;
		return;
	}

	Floyd::n = n;
	Floyd::init();

	while (m--) {
		int a, b, c; cin >> a >> b >> c;
		Floyd::d[a][b] = Floyd::d[b][a] = min(Floyd::d[a][b], (ll)c);
	}
	
	for (int i = 1; i <= k; i++) {
		cin >> nucleic[i];
		is_nucleic[nucleic[i]] = true;
	}

	Floyd::floyd();

	ll maxlength = 0;  // 每个节点到其最近的核酸点的最短距离的最大值
	for (int i = 1; i <= n; i++) {
		ll minlength = INFF;  // 每个节点到其最近的核酸点的最短距离
		if (is_nucleic[i]) {  // 核酸点
			for (int j = 1; j <= k; j++) {
				if (nucleic[j] == i) continue;

				minlength = min(minlength, Floyd::d[i][nucleic[j]]);
			}

			if (k == 1) minlength = 0;  // 特判只有一个核酸点的情况
		}
		else {  // 非核酸点
			for (int j = 1; j <= k; j++)  // 找到距该非核酸点最近的核酸点
				minlength = min(minlength, Floyd::d[i][nucleic[j]] << 1);
		}
		maxlength = max(maxlength, minlength);
	}

	Kruskal::n = n;
	Kruskal::init();  // 注意并查集要对n个节点都初始化
	Kruskal::n = k;  // 实际图中只有k个节点
	build();
	maxlength = max(maxlength, Kruskal::kruskal());

	if (maxlength == INFF) cout << -1;
	else cout << (maxlength + t - 1) / t;
}

int main() {
	solve();
}


J. Palindrome Reversion

题意:

给一个字符串 s,问能否翻转 s 的一个区间使 s 回文。

分析:

首先首尾相等的可以直接删去 剩下的字符串 t 一定是首尾不相同的

也就是说 此时一定得翻转t的前缀或者后缀才能达到目的

直接哈希枚举断点就好

const int MAXN = 1e6 + 5;

struct StringHash {
	int n;  // 字符串长度
	char str[MAXN];  // 下标从1开始
	const ll Base1 = 29, MOD1 = 1e9 + 7;
	const ll Base2 = 131, MOD2 = 1e9 + 9;
	ll ha1[MAXN], ha2[MAXN];  // 正着的哈希值
	ll rha1[MAXN], rha2[MAXN];  // 反着的哈希值
	ll pow1[MAXN], pow2[MAXN];  // Base1和Base2的乘方

	void init() {  // 预处理pow1[]、pow2[]
		pow1[0] = pow2[0] = 1;
		for (int i = 1; i <= n; i++) {
			pow1[i] = pow1[i - 1] * Base1 % MOD1;
			pow2[i] = pow2[i - 1] * Base2 % MOD2;
		}
	}

	void pre() {  // 预处理ha1[]、ha2[]
		for (int i = 1; i <= n; i++) {
			ha1[i] = (ha1[i - 1] * Base1 + str[i]) % MOD1;
			ha2[i] = (ha2[i - 1] * Base2 + str[i]) % MOD2;
			rha1[i] = (rha1[i - 1] * Base1 + str[n - i + 1]) % MOD1;
			rha2[i] = (rha2[i - 1] * Base2 + str[n - i + 1]) % MOD2;
		}
	}

	pll get_hash(int l, int r) {  // 求子串str[l...r]正着的哈希值
		ll res1 = ((ha1[r] - ha1[l - 1] * pow1[r - l + 1]) % MOD1 + MOD1) % MOD1;
		ll res2 = ((ha2[r] - ha2[l - 1] * pow2[r - l + 1]) % MOD2 + MOD2) % MOD2;
		return pll(res1, res2);
	}

	pll get_rhash(int l, int r) {  // 求子串str[l...r]反着的哈希值
		ll res1 = ((rha1[n - l + 1] - rha1[n - r] * pow1[r - l + 1]) % MOD1 + MOD1) % MOD1;
		ll res2 = ((rha2[n - l + 1] - rha2[n - r] * pow2[r - l + 1]) % MOD2 + MOD2) % MOD2;
		return pll(res1, res2);
	}

	bool IsPalindrome(int l, int r) {  // 判断子串str[l...r]是否是回文串
		return get_hash(l, r) == get_rhash(l, r);
	}

	pll add(pll a, pll b) {
		ll res1 = (a.first + b.first) % MOD1;
		ll res2 = (a.second + b.second) % MOD2;
		return pll(res1, res2);
	}

	pll mul(pll& a, ll k) {  // a *= Base的k次方
		ll res1 = a.first * pow1[k] % MOD1;
		ll res2 = a.second * pow2[k] % MOD2;
		return pll(res1, res2);
	}
}solver;

void solve() {
	cin >> solver.str + 1;

	solver.n = strlen(solver.str + 1);
	solver.init();
	solver.pre();

	int l = 1, r = solver.n;
	while (l < r && solver.str[l] == solver.str[r]) l++, r--;  // 去掉两边相同的字符

	if (l >= r) {  // 原串是回文串
		cout << "1 1" << endl;
		return;
	}

	for (int i = l; i <= r; i++) {  // 翻转前缀str[l...i]
		auto tmp1 = solver.get_rhash(l, i), tmp2 = solver.get_hash(i + 1, r);
		auto res1 = solver.add(solver.mul(tmp1, r - i), tmp2);
		auto tmp3 = solver.get_hash(l, i), tmp4 = solver.get_rhash(i + 1, r);
		auto res2 = solver.add(solver.mul(tmp4, i - l + 1), tmp3);
		if (res1 == res2) {
			cout << l << ' ' << i;
			return;
		}
	}

	for (int i = l; i <= r; i++) {  // 反转后缀str[i...r]
		auto tmp1 = solver.get_hash(l, i - 1), tmp2 = solver.get_rhash(i, r);
		auto res1 = solver.add(solver.mul(tmp1, r - i + 1), tmp2);
		auto tmp3 = solver.get_rhash(l, i - 1), tmp4 = solver.get_hash(i, r);
		auto res2 = solver.add(solver.mul(tmp4, i - l), tmp3);
		if (res1 == res2) {
			cout << i << ' ' << r;
			return;
		}
	}

	cout << "-1 -1";
}

int main() {
	solve();
}

posted @ 2022-10-15 10:29  wzx_believer  阅读(177)  评论(0编辑  收藏  举报