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 种操作:
- 禁用位置 x
- 启用位置 x
- 对区间 [l, r] 中启用的位置加 x
- 查询区间 [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();
}