The 15th Shandong CCPC Provincial Collegiate Programming Contest(补题
赛时4题,银尾
L. Stella
知识点:签到
思路:模拟即可
D. Distributed System
知识点:差分
思路:对于覆盖全部的,记录一个懒标记,对于部分的那些,会有两部分,\(b_i\) 到\(n-1\),和 \(0\) 到 \(b_i+n-1\),分别取模,需要处理两次,也可以不处理两次,开 \(2*n\) 的差分数组,处理一次,最后输出时 输出 \(a_i+a_{i+n}+sum\) 即可
Code
点击查看代码
void solve()
{
int q;
cin >> n >> q;
vi a(2 * n + 7);
int sum = 0;
while (q--)
{
cin >> x >> y;
sum += x / n;
x %= n;
a[y]++;
a[y + x]--;
}
for (int i = 1; i < 2 * n; i++)
{
a[i] += a[i - 1];
}
for (int i = 0; i <= n - 1; i++)
{
cout << a[i] + a[i + n] + sum << ' ';
}
cout << endl;
}
G. Assembly Line
知识点:贪心
思路:如果假设无任何限制条件,处理出每个工件处理完成的时间,但是由于每个工人每分钟只能处理一个工件,所以对于同一时间处理的工件,时间就要向后移,对时间数组排序,然后递推 \(a_i=max(a_{i-1}+1,a_i)\),最后一个工件处理完成的时间就是最终结果
H. Minimum Spanning Tree
知识点:最小生成树,贪心
思路:先在原来的大树上进行最小生成树操作,注意到,我们如果在相邻点之间加边,这样的边权是最小的,所以我们去除最小生成树上边权大于 \(1\) 的那 \(k\) 个边,自己在相邻而不连通的点之间加边即可
Code
点击查看代码
int fa[M];
void init()
{
for (int i = 1; i <= n; i++)
{
fa[i] = i;
}
}
int find(int x)
{
return fa[x] == x ? x : fa[x] = find(fa[x]);
}
bool same(int a, int b)
{
return find(a) == find(b);
}
void join(int u, int v)
{
u = find(u);
v = find(v);
if (u != v)
{
if (u > v)
{
fa[u] = v;
}
else
{
fa[v] = u;
}
}
}
struct node
{
int u, v, w, id;
};
struct node2
{
int u, v;
};
bool cmp(node a, node b)
{
return a.w < b.w;
}
void solve()
{
cin >> n >> m >> k;
init();
vector<node> a;
for (int i = 0; i < m; i++)
{
int u, v, w;
cin >> u >> v >> w;
a.push_back({u, v, w, i + 1});
}
sort(all(a), cmp);
vector<node> e2;
int ans = 0;
for (auto [u, v, w, id] : a)
{
if (!same(u, v))
{
join(u, v);
e2.pb({u, v, w, id});
ans += w;
}
}
sort(all(e2), cmp);
int op = 0;
for (auto [u, v, w, id] : e2)
{
cerr << u << ' ' << v << endl;
}
for (int i = e2.size() - 1; i >= 0; i--)
{ // 去除边
auto [u, v, w, id] = e2[i];
if (w <= 1)
{
break;
}
// if (abs(u - v) >= w)
// {
// }
if (op == k)
break;
op++;
ans -= w;
e2.pop_back();
if (op == k)
break;
}
for (auto [u, v, w, id] : e2)
{
cerr << u << ' ' << v << endl;
}
vector<node> b;
vi res; // 最后的边编号
init();
for (int i = 0; i < e2.size(); i++) // 剩下的边
{
auto [u, v, w, id] = e2[i];
res.push_back(id);
join(u, v);
}
vector<node2> e; // 加的边
int cnt = m;
for (int i = 2; i <= n; i++)
{
if (!same(i, i - 1))
{
join(i, i - 1);
cnt++;
res.pb(cnt);
e.pb({i, i - 1});
ans++;
}
}
cout << cnt - m << endl;
for (auto [u, v] : e)
{
cout << u << ' ' << v << endl;
}
cout << ans << endl;
for (auto i : res)
{
cout << i << ' ';
}
cout << endl;
}
A. Project Management
知识点:二分,贪心
思路:求最大选的人数,考虑二分,
先考虑排序规则,如果要在同等级中选,一定是先选包容性强的,如果要在不同等级中选,肯定是先选等级低的,因为如果选高等级的,对于低等级的限制不确定,所以选低等级的。
考虑 \(check\) 函数怎么写,二分人数,传进去人数,返回是否能达到(因为如果高人数可以满足,低人数一定可以满足,具有单调性)遍历所有等级,在一个等级中,如果发现当前选的高等级容纳量加上现在选的数量不满足二分的 \(mid\),这个就不能选,同时这个等级后面的所有人容纳性只会更低,所以都不能选,直接跳到下一个等级,一直进行这样的操作,判断最终人数是否满足答案,找出人数最大值,在知道最大值的情况下,带着这个数值再去找一遍就行了
Code
点击查看代码
struct node
{
int u;
int v;
int id;
};
vector<node> a;
bool cmp(node a, node b)
{
if (a.u == b.u)
return a.v > b.v;
return a.u < b.u;
}
int mx = 0;
bool check(int mid)
{
int now = 0;
for (int i = 1; i <= n;)
{
int j = i;
while (j <= n && a[j].u == a[i].u)
j++;
int sum = j - i;
int mx2 = min(sum, mid - now);
int cnt = 0;
for (int c = mx2; c >= 1; c--)
{
if (a[i + c - 1].v >= mid - now - c)
{
cnt = c;
break;
}
}
now += cnt;
if (now >= mid)
return 1;
i = j;
}
return 0;
}
void solve()
{
cin >> n;
a.resize(n + 1);
mx = 0;
for (int i = 1; i <= n; i++)
{
cin >> a[i].u >> a[i].v;
a[i].id = i;
mx = max(mx, a[i].u);
}
sort(a.begin() + 1, a.end(), cmp);
l = 1, r = n;
int res = 1;
while (l <= r)
{
int mid = l + ((r - l) >> 1);
if (check(mid))
{
res = max(mid, res);
l = mid + 1;
}
else
{
r = mid - 1;
}
}
cout << res << endl;
int now = 0;
vi ans;
for (int i = 1; i <= n;)
{
int j = i;
while (j <= n && a[j].u == a[i].u)
j++;
int sum = j - i;
int mx2 = min(sum, res - now);
int cnt = 0;
for (int c = mx2; c >= 1; c--)
{
if (a[i + c - 1].v >= res - now - c)
{
cnt = c;
break;
}
}
for (int k = 0; k < cnt; k++)
{
ans.push_back(a[i + k].id);
}
now += cnt;
if (now >= res)
break;
i = j;
}
for (int i = 0; i < res; i++)
{
cout << ans[i] << ' ';
}
cout << endl;
}
I. Square Puzzle
知识点:搜索,暴力
思路:一共有七种操作,移动每一行每一列以及旋转。进行 \(bfs\),把状态当作字符串存到桶里,同时记录到达这个状态的最小操作次数。
Code
点击查看代码
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define vi vector<int>
#define vb vector<bool>
#define endl '\n'
const int M = 2e5 + 7;
string s;
const int N = 1e6 + 5;
unordered_map<string, int> ans;
queue<string> q;
string youyi(string a, int op)
{
if (op == 1)
{
char t = a[0];
a[0] = a[2], a[2] = a[1], a[1] = t;
return a;
}
if (op == 2)
{
char t = a[3];
a[3] = a[5], a[5] = a[4], a[4] = t;
return a;
}
if (op == 3)
{
char t = a[6];
a[6] = a[8], a[8] = a[7], a[7] = t;
return a;
}
return a;
}
string xiayi(string a, int op)
{
if (op == 1)
{
char t = a[0];
a[0] = a[6], a[6] = a[3], a[3] = t;
return a;
}
if (op == 2)
{
char t = a[1];
a[1] = a[7], a[7] = a[4], a[4] = t;
return a;
}
if (op == 3)
{
char t = a[2];
a[2] = a[8], a[8] = a[5], a[5] = t;
return a;
}
return a;
}
string xuanzhuan(string a)
{
char t = a[0];
a[0] = a[6], a[6] = a[8], a[8] = a[2], a[2] = t;
t = a[1];
a[1] = a[3], a[3] = a[7], a[7] = a[5], a[5] = t;
return a;
}
void bfs()
{
q.push("123456789");
ans["123456789"] = 0;
while (q.size())
{
string g = q.front();
q.pop();
string t;
t = youyi(g, 1);
if (!ans.count(t))
q.push(t), ans[t] = ans[g] + 1;
t = youyi(g, 2);
if (!ans.count(t))
q.push(t), ans[t] = ans[g] + 1;
t = youyi(g, 3);
if (!ans.count(t))
q.push(t), ans[t] = ans[g] + 1;
t = xiayi(g, 1);
if (!ans.count(t))
q.push(t), ans[t] = ans[g] + 1;
t = xiayi(g, 2);
if (!ans.count(t))
q.push(t), ans[t] = ans[g] + 1;
t = xiayi(g, 3);
if (!ans.count(t))
q.push(t), ans[t] = ans[g] + 1;
t = xuanzhuan(g);
if (!ans.count(t))
q.push(t), ans[t] = ans[g] + 1;
}
}
void solve()
{
string s;
map<char, char> mp;
for (int i = 1; i <= 9; i++)
{
char ch;
cin >> ch;
mp[ch] = '0' + i;
}
for (int i = 1; i <= 9; i++)
{
char ch;
cin >> ch;
s += mp[ch];
}
if (s == "123456789")
cout << "0" << endl;
else if (ans[s] == 0)
cout << -1 << endl;
else
cout << ans[s] << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int qwq = 1;
bfs();
cin >> qwq;
while (qwq--)
{
solve();
}
return 0;
}
E. Greatest Common Divisor
知识点:贪心,数论,调和级数
思路:序列的最大公因数一定是序列和的因数
从大到小枚举序列和的因数,第一个满足条件的就是答案
如何证明当前因数是合法的?假设枚举的因数是 \(g\) 对于每一个数,都应该加到 \(g\)
的倍数,也就是加到离他最近的 \(g\) 的倍数,如果实现代价小于 \(k\),根据同余原理,剩下的操作次数一定是 \(g\) 的倍数,随即加到一个大数上即可,不会改变全序列的 \(gcd\),所以合法条件是 操作次数小于 \(k\).
如果对于每一个因数都去遍历代价数组,时间复杂度太高,需要预处理,所以我们分段来求解,如果最大值小于 \(g\),那么最优解是把所有数变成 \(g\),如果最大值大于 \(g\),那么把它按 \(g\) 的倍数分成若干段,开一个个数前缀和和一个数值前缀和进行预处理,这样分段求解的时间复杂度是调和级数,可以接受
Code
点击查看代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define endl '\n'
#define i64 long long
#define int long long
#define ui64 unsigned long long
#define vi vector<int>
#define vs vector<string>
#define vvi vector<vector<int>>
#define vb vector<bool>
#define pii pair<int, int>
#define all(a) a.begin(), a.end()
#define ull unsigned long long
#define pb push_back
const int M = 2e5 + 7;
const int mod = 998244353;
const int MOD = 1e9 + 7;
const int INF = 1e9;
const double pi = acos(-1.0);
int inf = -1e18;
// int dir[8][2] = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}, {1, 1}, {-1, -1}, {1, -1}, {-1, 1}};
int dir[4][2] = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};
i64 n, m, k = 0, x, y, l, r;
string s;
int gcd(int a, int b)
{
return b == 0 ? a : gcd(b, a % b);
}
int lcm(int a, int b)
{
return a * b / gcd(a, b);
}
int ksm(int a, int b, int p)
{
int ans = 1;
a %= p;
while (b)
{
if (b & 1)
{
ans = ans * a % p;
}
a = a * a % p;
b >>= 1;
}
return ans;
}
int gsc(int a, int b, int p)
{
a %= p;
int res = 0;
while (b > 0)
{
if (b & 1)
{
res = (res + a) % p;
}
a = (a + a) % p;
b >>= 1;
}
return res;
}
// 并查集
// int fa[M];
// void init()
// {
// for(int i=1;i<M;i++)
// {
// fa[i]=i;
// }
// }
// int find(int a)
// {
// return a==fa[a]?a:fa[a]=find(fa[a]);
// }
// bool same(int a,int b)
// {
// return find(a)==find(b);
// }
// void join(int a,int b)
// {
// a=find(a);
// b=find(b);
// if(a!=b)
// {
// fa[a]=b;
// }
// }
// 树状数组
// int tr[M];
// int lb(int i)
// {
// return i & (-i);
// }
// void add(int i, int x)
// {
// for (; i <= n; i += lb(i))
// {
// tr[i] += x;
// }
// }
// int sum(int i)
// {
// int ans = 0;
// for (; i > 0; i -= lb(i))
// {
// ans += tr[i];
// }
// return ans;
// }
// int que(int l, int r)
// {
// return sum(r) - sum(l - 1);
// }
// 欧拉筛
// int c = 0;
// int pri[M];
// int vis[M];
// void ols(int n)
// {
// for (ll i = 2; i <= n; i++)
// {
// if (!vis[i])
// {
// pri[++c] = i;
// }
// for (ll j = 1; (ll)(i * pri[j]) <= n; j++)
// {
// vis[i * pri[j]] = 1;
// if (i % pri[j] == 0)
// {
// break;
// }
// }
// }
// }
// tarjan
// vi adj[M];
// int dfn[M], tim = 0, low[M];
// int stk[M], vis[M], pos = 0;
// int c = 0, scc[M], siz[M];
// void tarjan(int x)
// {
// tim++;
// dfn[x] = tim;
// low[x] = tim;
// for (int i : adj[x])
// {
// if (!dfn[i])
// {
// tarjan(i);
// low[i] = min(low[i], low[x]);
// }
// else if (vis[i])
// {
// low[i] = min(low[i], dfn[x]);
// }
// }
// if (dfn[x] == low[x])
// {
// c++;
// int now;
// do
// {
// now = stk[pos];
// pos--;
// vis[now] = 0;
// scc[now] = c;
// siz[c]++;
// } while (now != x);
// }
// }
// 字典树
// int tr[3000005][63];
// int c = 1;
// int p[3000005];
// int path(char ch)
// {
// if (islower(ch))
// {
// return ch - 'a';
// }
// else if (isupper(ch))
// {
// return ch + 26 - 'A';
// }
// return ch + 52 - '0';
// }
// void insert(string word)
// {
// int cur = 1;
// p[cur]++;
// for (int i = 0; i < word.size(); i++)
// {
// int c = path(word[i]);
// if (tr[cur][c] == 0)
// {
// tr[cur][c] = ++c;
// }
// cur = tr[cur][c];
// p[cur]++;
// }
// }
// int cou(string word)
// {
// int cur = 1;
// for (int i = 0; i < word.size(); i++)
// {
// int c = path(word[i]);
// if (tr[cur][c] == 0)
// {
// return 0;
// }
// cur = tr[cur][c];
// }
// return p[cur];
// }
// void clear()
// {
// for (int i = 1; i <= c; i++)
// {
// memset(tr[i], 0, sizeof(tr[i]));
// p[i] = 0;
// }
// c = 1;
// }
void solve()
{
cin >> n >> k;
vi a(n);
int sum = 0;
int mx = 0;
for (int i = 0; i < n; i++)
{
cin >> a[i];
sum += a[i];
mx = max(mx, a[i]);
}
int sum0 = sum + k;
vi b;
for (long long i = 1; i * i <= sum0; i++)
{
if (sum0 % i == 0)
{
b.push_back(i);
if (i * i != sum0)
{
b.push_back(sum0 / i);
}
}
}
sort(b.rbegin(), b.rend());
vi c(mx + 1, 0);
for (int x : a)
{
c[x]++;
}
vi pc(mx + 1, 0);
vi pv(mx + 1, 0);
for (int i = 1; i <= mx; i++)
{
pc[i] = pc[i - 1] + c[i];
pv[i] = pv[i - 1] + i * c[i];
}
for (auto i : b)
{
if (i > mx)
{
if (n * i <= sum0)
{
cout << i << endl;
return;
}
}
else
{
int now = 0;
for (int j = 0; j <= mx; j += i)
{
l = j + 1;
r = min(mx, j + i - 1);
if (l <= r)
{
int t1 = pc[r] - pc[l - 1];
int t2 = pv[r] - pv[l - 1];
now += (j + i) * t1 - t2;
}
}
if (now <= k)
{
cout << i << endl;
return;
}
}
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int qwq = 1;
cin >> qwq;
/*
while (cin >> ws && cin.good())
{
solve();
}
*/
while (qwq--)
{
solve();
}
return 0;
}

浙公网安备 33010602011771号