UESTC2022暑假一轮集训
一轮集训补题整理
代码是自己写的,部分题意和题解复制于网络
day1
Serge and Dining Room(线段树)
题意:
给出a 和 b 数组,a为各种食物的价格,b为一列排着队的小朋友拥有的钱,小朋友排队购买食物,每个人都买自己能买的起的最贵的食物,买不起就离开队伍。给出q次操作,操作1是修改食物的价格,操作2是修改小朋友的钱,每次操作后询问当小朋友买完之后,能买到的最贵的食物的价格是多少,没有食物了就输出-1.
题解:
首先,小朋友的顺序对最终答案没有任何影响,因为如果两个小朋友能买两个东西,这两个小朋友无论怎么换,都是能买的了的。
其次,对于一个价格为x的物品,如果有一个钱大于等于x的小朋友,就可以买走这个物品。如果我们把价格想象成一条数轴,那么物品就是1,小朋友就是-1,价格或者钱就是坐标轴的位置,这道题就转化成了求最大的后缀和大于等于1的下标。
最后,我们用线段树来维护这个值,注意用线段树维护最大后缀和时,在查询时要注意后面的区间的影响。
————————————————
版权声明:本文为CSDN博主「千摆渡w」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u014560290/article/details/111189980
//
// Created by vv123 on 2022/7/5.
//
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2e6 + 10;
int n, m, q, a[N], b[N];
#define mid (l+r>>1)
//#define len (r-l+1)
//#define llen (mid-l+1)
//#define rlen (r-mid)
#define ls (i<<1)
#define rs (i<<1|1)
#define ain (s<=l&&r<=t)
#define lin (s<=mid)
#define rin (t>=mid+1)
const int mxv = 1e6;
struct SegmentTree {
ll mx[N << 2], laz[N << 2];
void pushup(int i) {
mx[i] = max(mx[ls], mx[rs]);
}
void pushdown(int l, int r, int i) {
if (laz[i]) {
mx[ls] += laz[i]; mx[rs] += laz[i];
laz[ls] += laz[i]; laz[rs] += laz[i];
laz[i] = 0;
}
}
void build(int l, int r, int i) {}
void add(int s, int t, int l, int r, int i, int k) {
if (ain) {
mx[i] += k;
laz[i] += k;
return;
}
pushdown(l, r, i);
if (lin) add(s, t, l, mid, ls, k);
if (rin) add(s, t, mid + 1, r, rs, k);
pushup(i);
}
//查询>0的最大点
ll query(int l, int r, int i) {
if (mx[i] <= 0) return -1;
if (l == r) return l;
pushdown(l, r, i);
if (mx[rs] > 0) return query(mid + 1, r, rs);
else return query(l, mid, ls);
}
} tr;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i], tr.add(1, a[i], 1, mxv, 1, 1);
for (int i = 1; i <= m; i++) cin >> b[i], tr.add(1, b[i], 1, mxv, 1, -1);
cin >> q;
while (q--) {
int op, p, x;
cin >> op >> p >> x;
if (op == 1) {
tr.add(1, a[p], 1, mxv, 1, -1);
a[p] = x;
tr.add(1, a[p], 1, mxv, 1, 1);
} else {
tr.add(1, b[p], 1, mxv, 1, 1);
b[p] = x;
tr.add(1, b[p], 1, mxv, 1, -1);
}
cout << tr.query(1, mxv, 1) << "\n";
}
return 0;
}
GuziK hates Boxes(二分,贪心)
题意:
从左到右有 n 个 pile 每个 pile 里面有不定数量的 boxes 。
在最左边的 pile 的左边有m个学生要为老师来搬箱子,它们每个人有两种行动策略,向右移动一格,或者搬走一个当前 pile 下的箱子,每种策略都需要一秒钟,问你把所有箱子搬完所需的最短时间。
题解:
二分所需的时间,贪心增加学生的数量。
//
// Created by vv123 on 2022/7/4.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e5 + 10;
int n, m, a[N], lst;
long long sum = 0;
bool check(int t) {
int cnt = m, sum = 0;
for (int i = 1; i <= lst; i++) {
sum += a[i];
//出动一个领头羊,当我们走到i时,他的总时间期望为 i + 搬箱子所需的总时间sum
//如果我们多派一些学生出动,就可以减少sum
//如果sum + i > t,就需要加一个学生用来搬1~i之间的箱子
while (sum + i > t) {
cnt--;
sum -= t - i; //每个学生有t - i的时间搬箱子
if (cnt < 0) return false;
}
//cnt >= 1意味着他不需要动手
//cnt == 0意味着他搬了箱子,那么t-lst应不小于sum,最后sum <= 0
}
if (cnt == 0) return sum <= 0;
return true;
}
signed main() {
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> a[i], sum += a[i], lst = a[i] ? i : lst;
int l = 1, r = n + sum;
while (l < r) {
int mid = l + r >> 1;
if (check(mid)) r = mid; else l = mid + 1;
}
cout << l;
return 0;
}
day2
Rockdu loves Number Theory
题意
Rockdu has two positive integer sequences \(a_1,…,a_n\) and $ b_1,…,b_m$.
For each \(j=1,…,m\) find the greatest common divisor of \(a_1+b_j,…,a_n+b_j\)
题解
可以根据辗转相减,让求后面的数都减掉第一个数的GCD。代码略。
Stargazer loves Polynomial
题意
求\(\prod_{1\le i\le j\le n}|a_i-a_j|\bmod m\)
\(n\le2\cdot10^5,m\le1000\)
题解
当\(n>m\)时,结果为0,否则可以暴力求解。代码略。
Sugarii loves Tree Graph
题意:
给你一颗树,共有n个节点,让你在这棵树上找到k个工业节点,其余都为旅游节点,计算出每个工业节点走到1号节点经过的旅游节点数目sum,问你怎样选择这k个工业节点,使sum最大。
题解:
按照depth-cnt排序即可。cnt是以它为根的子树中工业节点的数量。
//
// Created by vv123 on 2022/7/5.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10;
vector<int> g[N];
int n, k;
long long ans = 0;
struct node {
int id, siz, dep;
}a[N];
bool ind[N];
bool cmp(node x, node y) {
return x.dep - x.siz > y.dep - y.siz;
}
void dfs(int u, int fa) {
a[u].dep = a[fa].dep + 1;
a[u].siz = 1;
for (auto v : g[u]) {
if (v == fa) continue;
dfs(v, u);
a[u].siz += a[v].siz;
}
}
void solve() {
cin >> n >> k;
for (int i = 1; i <= n - 1; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
for (int i = 1; i <= n; i++) a[i].id = i;
dfs(1, 0);
sort(a + 1, a + 1 + n, cmp);
int ans = 0;
for (int i = 1; i <= k; i++) {
ans += a[i].dep - a[i].siz;
}
cout << ans << "\n";
}
//dr:转化
//lb:优先队列
//讲题:dep-siz
signed main() {
int T = 1;
while (T--) {
solve();
}
return 0;
}
day3
Marco and GCD Sequence(构造)
题意:
有一个数组,将每一个区间的gcd放入一个集合里,给出集合,试还原原序列
题解:
1.原序列中的数字应该都是来自于这个集合的。
2.对于每一个区间来说,gcd不大于它们的最小值。
那么求出总体的gcd,并将每一个元素都除以这个gcd,如果现在序列中出现了1,就说明集合中的元素都能被一个属于该集合的元素整除,那么我们只要在每一个元素的前面插入一个公共gcd作为原序列就可以了。
如果操作后没有1,那就说明大家的公共gcd不在这个集合里,那这个集合就是无解的了。
————————————————
版权声明:本文为CSDN博主「sophilex」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/sophilex/article/details/125105270
//
// Created by vv123 on 2022/7/6.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e6 + 10;
const int mod = 1e9 + 7;
const int inf = 1e18;
int a[N], b[N], n;
int gcd(int x, int y) {
return y ? gcd(y, x % y) : x;
}
bool check() {
int x = a[1];
for (int i = 1; i <= n; i++)
x = gcd(x, a[i]);
return x == a[1];
}
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
if (!check()) cout << "-1\n";
else {
cout << 2 * n << "\n";
for (int i = 1; i <= 2 * n; i++) cout << (i % 2 ? a[1] : a[i / 2]) << " ";
cout << "\n";
}
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
Desk Disorder (图论)
题意:
有\(n\)个人,\(2n\)个座位。给出这\(n\)个人初始的座位,和他们想坐的座位。每个人要么坐在原来的位置不动,要么坐到想坐的座位上,但是不能有两个人坐在同一个座位上。问你合法的安排座位的方案数。
题解:
考虑把每个人看成边,把每个人想坐的位置连起来,显然我们会得到一个个链或基环树
如果是基环树,只能选择环动或者不动
链可以在每一个位置开动
//
// Created by vv123 on 2022/7/6.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
const int N = 1e6 + 10;
int pow(int a, int b) {
int res = 1;
for (; b; b >>= 1) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
}
return res;
}
int n, a[N], f[N], s[N], vis[N];
int find(int x) {
return x == f[x] ? f[x] : f[x] = find(f[x]);
}
signed main() {
int ans = 1;
cin >> n;
for (int i = 1; i <= n * 2; i++) f[i] = i, s[i] = 1;
for (int i = 1; i <= n; i++) {
int u, v;
cin >> u >> v;
vis[u] = 1;//要么是链,要么是基环树,答案只会出现在链的拓展点上
if (u == v) continue;//自环也要标记为vis,防止它成为有效拓展点
int fu = find(u), fv = find(v);
if (fu == fv) {
ans = ans * 2ll % mod;
}
f[fu] = fv;
s[fv] += s[fu];
}
for (int i = 1; i <= n * 2; i++) {
if (!vis[i]) ans = ans * s[i] % mod;
}
cout << ans << "\n";
return 0;
}
day4
String Game(二分)
题意:给你一段操作序列;按顺序依次删掉字符串1中相应位置的字符;问你最多能按顺序删掉多少个字符;使得s2是剩下的字符构成的字符串的子列
思路:二分即可。注意判断是否为子序列的check的写法。
//
// Created by vv123 on 2022/7/7.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 2e5 + 10;
int n, m, a[N], del[N], ne[N];
char ss[N], s[N], t[N];
/*
int kmp(int n) {
memset(ne, 0, sizeof(ne));
for (int i = 2, j = 0; i <= m; i++) {
while (j && p[j + 1] != p[i]) j = ne[j];
if (p[j + 1] == p[i]) j++;
ne[i] = j;
}
int ans = 0;
for (int i = 1, j = 0; i <= n; i++) {
while (j && p[j + 1] != s[i]) j = ne[j];
if (p[j + 1] == s[i]) j++;
if (j == m) {
ans++;
j = ne[j];
}
}
return ans;
}
*/
bool isSubsequence() { //T是否为S的子序列
int ns = strlen(s + 1), nt = strlen(t + 1);
if(!nt) return true;
if(nt && !ns) return false;
int ps = 1, pt = 1;
while (ps <= ns && pt <= nt) {
if (s[ps] == t[pt]) pt++;
ps++;
}
return pt > nt;
}
bool check(int x) {
memset(del, 0, sizeof(del));
for (int i = 1; i <= x; i++) del[a[i]] = 1;
int cnt = 0;
for (int i = 1; i <= n; i++)
if (!del[i]) s[++cnt] = ss[i];
s[cnt + 1] = '\0';
//cout << x << " " << s + 1 << "\n";
return isSubsequence();
}
void solve() {
cin >> ss + 1 >> t + 1;
n = strlen(ss + 1), m = strlen(t + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
int l = 0, r = n;
while (l < r) {
int mid = l + r + 1 >> 1;
if (check(mid)) l = mid;
else r = mid - 1;
}
cout << l << "\n";
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
Pair of Lines(几何,讨论)
题意:
给定n个点,输出是否这些点可以至多用两条直线就可以连接。
题解:
见注释。
//
// Created by vv123 on 2022/7/7.
//
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define x first
#define y second
typedef pair<int, int> pt;
const int N = 2e5 + 10;
inline pt operator-(const pt& a, const pt& b) {
return {a.x - b.x, a.y - b.y};
}
inline int cross(const pt& a, const pt& b) {
return a.x * b.y - b.x * a.y;
}
inline bool coline(const pt& p, const pt& a, const pt& b) {
pt pa = p - a, pb = p - b;
return cross(pa, pb) == 0;
}
int n;
pt p[N];
bool check1() {
//case1:假设p1和p2共线,则其他点要么在p1p2上,要么在pxpy上(px是前两个不在p1p2上的点)
vector<pt> others;
for (int i = 3; i <= n; i++) {
if (!coline(p[i], p[1], p[2])) others.push_back(p[i]);
}
if (others.size() < 3) return true;
for (int i = 2; i < others.size(); i++) {
if (!coline(others[i], others[0], others[1])) return false;
}
return true;
}
bool check2() {
//case2:p1和p2共线无法满足,按同样的方法测试p1p3是否共线。
swap(p[2], p[3]);
int r = check1();
swap(p[2], p[3]);
return r;
}
bool check3() {
//case3:p1和p3共线也无法满足,则只可能其他店要么在p2p3上,要么在p1pz上(pz是第一个不在p2p3上的点)
vector<pt> others;
others.push_back(p[1]);
for (int i = 4; i <= n; i++) {
if (!coline(p[i], p[2], p[3])) others.push_back(p[i]);
}
if (others.size() < 3) return true;
for (int i = 2; i < others.size(); i++) {
if (!coline(others[i], others[0], others[1])) return false;
}
return true;
}
void solve() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> p[i].x >> p[i].y;
if (n <= 3) { puts("YES"); return; }
//case1:假设p1和p2共线,则其他点要么在p1p2上,要么在pxpy上(px是前两个不在p1p2上的点)
//case2:p1和p2共线无法满足,按同样的方法测试p1p3是否共线。
//case3:p1和p3共线也无法满足,则只可能其他店要么在p2p3上,要么在p1pz上(pz是第一个不在p2p3上的点)
if (check1()) puts("YES");
else if (check2()) puts("YES");
else if (check3()) puts("YES");
else puts("NO");
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
Arpa’s overnight party and Mehrdad’s silent entering(二分图,构造)
题意:
有N对情侣,他们要围坐在一个2N大小的圆桌一块吃饭,有这样的要求,情侣两人必须有一个人得要吃好吃的食物,另一个人吃不好吃的,也就是一个人吃食物1,另一个人吃食物2,并且圆桌上任意连续3个人的食物必须不完全相同,也就是不能3个人都是吃1或2的,最后1和2N相邻(圆桌嘛)。
题解:
12、34、56这样连边,然后情侣连边。
可以发现这样一定是二分图,染色即可得到符合要求的方案。
//
// Created by vv123 on 2022/7/7.
//
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6 + 10;
vector<int> g[N];
int color[N], a[N], b[N];
void dfs(int u, int c) {
color[u] = c;
for (auto v : g[u]) {
if (!color[v]) dfs(v, 3 - c);
}
}
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
g[i * 2 - 1].push_back(i * 2);
g[i * 2].push_back(i * 2 - 1);
cin >> a[i] >> b[i];
g[a[i]].push_back(b[i]);
g[b[i]].push_back(a[i]);
}
for (int i = 1; i <= n * 2; i++) {
if (!color[i]) dfs(i, 1);
}
for (int i = 1; i <= n; i++)
cout << color[a[i]] << " " << color[b[i]] << "\n";
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
day5
0-1 MST(图论,bitset)
题意:
给出0-1完全图上\(m\)条边权为1的边,求最小生成树。
\(n\le10^5,m\le10^5\)
题解:
首先很自然的想法是求出边权为0的连通块个数+1。但是0权边的个数太多。
考虑用bitset维护没有走过的点。
//
// Created by vv123 on 2022/7/8.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 10;
unordered_map<int, bool> g[N];
bitset<N> unvis;
void dfs(int u) {
unvis[u] = 0;
for (int i = unvis._Find_first(); i < unvis.size(); i = unvis._Find_next(i)) {
if (!g[u][i]) dfs(i);
}
}
void solve() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
g[u][v] = g[v][u] = 1;
}
for (int i = 1; i <= n; i++) unvis[i] = 1;
int cnt = 0;
for (int i = 1; i <= n; i++) {
if (unvis[i]) dfs(i), cnt++;
}
cout << cnt - 1 << "\n";
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
day6
开摆
day7
Single-use Stones(思维题)
题意:
有一条河宽度为w,河上有一些石头,给出一组数(编号1~w-1),其中a[i]代表与河一岸距离为i的石头数量。每只青蛙的跳跃距离为l(l<w),故要踩着石头到河的彼岸,且被踩过的石头将消失,问这条河最多可以让多少只青蛙过。
题解:
求出所有长度为l的区间和的最小值即可。
Divide by Three(大模拟)
呕
Curiosity Has No Limits (构造)
题意:
给出两个数组a,b长度均为n-1
问能否构造出长度为n的t数组,满足
1、a[i] = t[i] | t[i+1]
2、b[i] = t[i] & t[i+1]
题解:
重要结论:a+b=a|b+a&b
暴力枚举即可
Spy Syndrome 2(字符串)
题意:
给你一堆单词。然后给出一个字符串,是由一些单词组成的,但是没有空格且单词被翻转了,求出解密后的字符串( It is guaranteed that at least one solution exists. If there are multiple solutions, you may output any of those. )。
题解:
从后往前暴力哈希。
//
// Created by vv123 on 2022/7/11.
//
#include <bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define seed 131
using namespace std;
const int N = 1e6 + 10;
char s[N], word[N];
ull f[N];
unordered_map<ull, string> mp;
void solve() {
int n, m;
cin >> n >> s + 1 >> m;
for (int i = 1; i <= m; i++) {
cin >> word;
ull val = 0;
for (int i = 0; word[i]; i++)
val = val * seed + tolower(word[i]);
mp[val] = word;
}
//f[i]:以i为结尾的单词的hash(满足1-i均被单词覆盖)
memset(f, -1, sizeof f);
f[0] = 0;
for (int i = 1; i <= n; i++) {
ull val = 0;
for (int j = i; j >= 1; j--) {
val = val * seed + s[j];
if (f[j - 1] != -1 && mp.find(val) != mp.end()) {
f[i] = val;
break;
}
}
}
vector<string> ans;
for (int i = n; i; i -= mp[f[i]].length()) ans.push_back(mp[f[i]]);
reverse(ans.begin(), ans.end());
for (auto u:ans) cout << u << " ";
cout << "\n";
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
day8
Engineer Artem(构造)
题意
给你一个矩阵,你可以对矩阵的每个元素选择是否加一,最后使得这个矩阵每个元素相邻不能有相同元素。
题解
考虑把原矩阵变成下面这样的矩阵即可
奇偶奇偶
偶奇偶奇
奇偶奇偶
......
Omkar and Determination (思维)
题意
给出由空地和障碍构成的矩阵,如果一个点可以往左往上走逃出边界,称它是可逃的。
现给出子矩阵的范围,问能否通过子矩阵的每个点的可逃性确定每个点是否为障碍。
子矩阵和原矩阵没关系。
题解
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (i > 1 && j > 1 && g[i][j - 1] == 'X' && g[i - 1][j] == 'X') block[j - 1] = 1;
}
}
for (int i = 1; i <= m; i++)
sum[i] = sum[i - 1] + block[i];
scanf("%lld", &q);
while (q--) {
int l, r;
scanf("%lld%lld", &l, &r);
sum[r - 1] - sum[l - 1] ? puts("NO") : puts("YES");
}
Mike and Form(容斥,莫比乌斯函数)
题意:
给一些数,每次操作将某个数激活或者禁用,输出当前所有激活的数中互质的数对的个数。
题解:
每次操作实际上是改变某个数作为因子出现的次数。
如果是质数,贡献为1,如果是不同质数的乘积,根据包含质数种类的奇偶性进行容斥。不考虑其他因子的贡献。
我们发现这个系数正好是莫比乌斯函数。
//
// Created by vv123 on 2022/7/12.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10;
int n, q, a[N], vis[N], prime[N], mu[N], inq[N];
void Shai(int n) {
vis[1] = mu[1] = 1;
int cnt = 0;
for (int i = 2; i <= n; i++) {
if (!vis[i]) {
prime[++cnt] = i;
mu[i] = -1;
}
for (int j = 1; j <= cnt && i * prime[j] <= n; j++) {
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) {//n'包含了n的所有质因子,与n的欧拉函数只差p1
mu[i * prime[j]] = 0; //p1的指数大于1,莫比乌斯函数为0
break;
} else {
mu[i * prime[j]] = mu[prime[j]] * mu[i];
}
}
}
}
int ans = 0;
int cnt[N];
void add(int x, int d) {
for (int i = 1; i * i <= x; i++) {
if (x % i == 0) {
ans -= cnt[i] * (cnt[i] - 1) / 2ll * mu[i];
cnt[i] += d;
ans += cnt[i] * (cnt[i] - 1) / 2ll * mu[i];
int j = x / i; if (j == i) continue;
ans -= cnt[j] * (cnt[j] - 1) / 2ll * mu[j];
cnt[j] += d;
ans += cnt[j] * (cnt[j] - 1) / 2ll * mu[j];
}
}
}
void solve() {
cin >> n >> q;
for (int i = 1; i <= n; i++)
cin >> a[i];
Shai(1e6);
while (q--) {
int t;
cin >> t;
if (!inq[t]) {
inq[t] = 1;
add(a[t], 1);
} else {
inq[t] = 0;
add(a[t], -1);
}
cout << ans << "\n";
}
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
Envy(图论,可撤销并查集,最小生成树)
题意:
给你一系列边和其权值,保证利用这些边能够成最小生成树。再给出一系列询问,每个询问中有一些边,问这些边能否在一颗最小生成树中同时出现。
题解:
考虑离线求解。 先将所有边按从小到大排序, 每次同时考虑一堆权值均为x的边, 根据kruskal算法, 所有 < x的边已经进行了并查集缩点, 再单独考虑所有涉及了这一次权值为x的询问, 把涉及到的边单独拿出来做并查集合并, 若出现了环则这条询问判断为不存在, 再把这次的修改还原, 考虑完所有的涉及到的询问后, 把该层权值为x的边端点并查集合并后, 继续考虑下一层权值的边。
————————————————
版权声明:本文为CSDN博主「三级头不怕通」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u011535421/article/details/78581548
//
// Created by vv123 on 2022/7/12.
//
#include <bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
const int N = 1e6 + 10;
int n, m, qwq, qcnt[N], qans[N], f[N], siz[N];
vector<pii> qw[N];
struct edge {
int u, v, w;
bool operator<(const edge& x) const {
return w < x.w;
}
} e[N], e0[N];
vector<pii> tmp;
int find(int x) {
return x == f[x] ? f[x] : find(f[x]);
}
void done(int x, int y) {
if (siz[x] > siz[y]) swap(x, y);
tmp.push_back({x, y});
f[x] = y;
siz[y] += siz[x];
}
void undone() {
pii u = tmp.back(); tmp.pop_back();
f[u.first] = u.first;
siz[u.second] -= siz[u.first];
}
bool add(vector<edge> &v, int op) { //op = 0/1 表示 不保留/保留
for (int i = 0; i < v.size(); i++) {
int fu = find(v[i].u), fv = find(v[i].v);
if (fu != fv) done(fu, fv);
else {
if (op == 0) {
for (int j = 0; j <= i - 1; j++) undone();//撤回v[0,i-1]的边
return false;
}
if (op == 1) continue;
}
}
if (op == 0)
for (int i = 0; i < v.size(); i++) undone();
return true;
}
inline void solve() {
cin >> n >> m;
memset(qans, 0, sizeof qans);
for (int i = 1; i <= n; i++) f[i] = i, siz[i] = 1;
for (int i = 1; i <= m; i++) {
cin >> e[i].u >> e[i].v >> e[i].w;
e0[i] = e[i];
}
cin >> qwq;
for (int i = 1; i <= qwq; i++) {
cin >> qcnt[i];
for (int j = 1; j <= qcnt[i]; j++) {
int x;
cin >> x;
qw[e[x].w].push_back({i, x});
//e[x].q.push_back(i);
}
}
sort(e + 1, e + 1 + m);
for (int l = 1, r; l <= m; l = r + 1) {
//1.确定边权相等的区间[l,r]
int w = e[l].w;
r = l;
while (r + 1 <= m && e[r + 1].w == w) r++;
//2.对这种边权,依次检查每个询问
//tle优化:直接对每种边权开个vector存询问
auto &q = qw[w];
sort(q.begin(), q.end());//使询问编号连续
for (int ll = 0, rr; ll < q.size(); ll = rr + 1) {
int qid = q[ll].first;
rr = ll;
while (rr + 1 < q.size() && q[rr + 1].first == qid) rr++;
if (qans[qid] == -1) continue;
vector<edge> v;
for (int i = ll; i <= rr; i++) v.push_back(e0[q[i].second]);
if (v.empty()) continue;
if (!add(v, 0)) qans[qid] = -1;
}
/*
for (int qid = 1; qid <= qwq; qid++) {
if (qans[qid] == -1) continue;
v.clear();
for (int i = l; i <= r; i++) {
if (find(e[i].q.begin(), e[i].q.end(), qid) != e[i].q.end()) {
v.push_back(e[i]);
}
}
if (v.empty()) continue;
if (!add(v, 0)) qans[qid] = -1;
}
*/
//3.最后固定连通块状态
vector<edge> v;
v.clear();
for (int i = l; i <= r; i++) v.push_back(e[i]);
add(v, 1);
}
for (int i = 1; i <= qwq; i++)
qans[i] == -1 ? cout << "NO\n" : cout << "YES\n";
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(0);
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
day9
Coprime Subsequences (容斥,莫比乌斯函数)
题意:
给\(n\)个数,问\(gcd=1\)的非空子集有多少个
题解:
集合总共有\(2^n-1\)个,那\(gcd=1\)的集合\(=(2 ^n-1)-|gcd=2|-|gcd=3|-|gcd=5|+|gcd=6|……\)的容斥。
与Mike and Form类似,考虑枚举每个因子的出现次数,利用莫比乌斯函数计算贡献即可。
//
// Created by vv123 on 2022/7/13.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
const int N = 1e5 + 10;
int pow2[N];
int mu[N], vis[N], prime[N], cnt;
void Shai(int n = N - 1) {
vis[1] = mu[1] = 1;
for (int i = 2; i <= n; i++) {
if (!vis[i]) {
prime[++cnt] = i;
mu[i] = -1;
}
for (int j = 1; j <= cnt && i * prime[j] <= n; j++) {
//printf("i=%d prime[j]=%d\n", i, prime[j]);
vis[i * prime[j]] = 1;
if (i % prime[j] == 0) {
mu[i * prime[j]] = 0; //p1的指数大于1,莫比乌斯函数为0
break;
} else {
mu[i * prime[j]] = mu[prime[j]] * mu[i];
}
}
}
}
int num[N];
signed main() {
Shai();
int n, t;
cin >> n;
pow2[0] = 1;
for (int i = 1; i <= n; i++) pow2[i] = pow2[i - 1] * 2 % mod;
for (int i = 1; i <= n; i++) {
cin >> t;
for (int j = 1; j * j <= t; j++) {
if (t % j == 0) {
num[j]++;
if (j * j != t) num[t / j]++;
}
}
}
int ans = (pow2[n] - 1 + mod) % mod;
for (int p = 2; p <= N - 1; p++) {
if (!num[p]) continue;
//cout << p << " " << num[p] << " " << mu[p] << " " << mu[p] * (pow2[num[p]] - 1) << "\n";
ans = (ans + (mu[p] * (pow2[num[p]] - 1 + mod) % mod + mod) % mod) % mod;
}
cout << ans << "\n";
return 0;
}
Strange Device(交互,思维)
题意:
有一个两两值互不相同的数组,但是你不知道这个数组的值,有一个机器,你询问 k 个下标,他会返回这数组中这 k 个下标对应的数 按升序排序后的第 m 个的下标和值。你现在不知道 m,你可以通过问 不多于 n 个问题,每个问题的形式为 :输出 \(?\ x_1 \ x_2\ x_3\ … \ x_k\),机器会返回这些下标在数组中对应的数中第m小数的下标和值。求 m。
————————————————
版权声明:本文为CSDN博主「ACM败犬」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41997978/article/details/103769455
题解:见注释
//
// Created by vv123 on 2022/7/13.
//
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
int n, k, pos, ret;
void ask(int x) {
cout << "?";
for (int i = 1; i < x; i++) cout << " " << i;
for (int i = x + 1; i <= k + 1; i++) cout << " " << i;
cout << endl;
}
int main() {
cin >> n >> k;
//well, lets consider [1,k+1]
//we try to delete from 1 to k + 1
//when the deleted number > the mth in [1,k + 1], the ret are all a[m]
//when the deleted number <= the mth in [1,k + 1], the ret are all a[m+1]
//so the answer is the count of bigger ret
map<int, int> cnt;
for (int i = 1; i <= k + 1; i++) {
ask(i);
cin >> pos >> ret;
cnt[ret]++;
}
cout << "! " << cnt.rbegin()->second << endl;
return 0;
}
day10
爆零场。。。
0-1 Tree (树,计数)
题意:
给定一棵边权为0/1的树,求满足以下条件的路径的数量:经过权值为1的边后,不能再经过权值为0的边。\(n\le2\cdot10^5\)
题解:
考虑权值为0的边和权值为1的边构成的两颗子树,在上边dfs为每一个点染色,求出这个点所在连通块的大小。
枚举每一个点作为0和1的分界点,则贡献为(cnt0 - 1) * (cnt1 - 1)。
//
// Created by vv123 on 2022/7/14.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e6 + 10;
//那是不是只需要dfs两遍
//求出每个点所在的0树和1树连通块大小
//枚举每一个点作为0和1的分界点,则贡献为(cnt0 - 1) * (cnt1 - 1)
int n, c0, c1, siz0[N], color0[N], siz1[N], color1[N];
vector<int> g0[N], g1[N];
void dfs0(int u, int fa) {
color0[u] = c0; siz0[c0]++;
for (auto v:g0[u]) {
if (v == fa) continue;
dfs0(v, u);
}
}
void dfs1(int u, int fa) {
color1[u] = c1; siz1[c1]++;
for (auto v:g1[u]) {
if (v == fa) continue;
dfs1(v, u);
}
}
inline void solve() {
cin >> n;
for (int i = 1; i <= n - 1; i++) {
int u, v, w;
cin >> u >> v >> w;
if (w == 0) g0[u].push_back(v), g0[v].push_back(u);
else g1[u].push_back(v), g1[v].push_back(u);
}
int ans = 0;
for (int i = 1; i <= n; i++) {
if (!color0[i]) {
c0++;
dfs0(i, -1);
ans += siz0[c0] * (siz0[c0] - 1);
}
if (!color1[i]) {
c1++;
dfs1(i, -1);
ans += siz1[c1] * (siz1[c1] - 1);
}
}
//for (int i = 1; i <= n; i++) cout << color0[i] << " "; cout << "\n";
//for (int i = 1; i <= c0; i++) cout << siz0[i] << " "; cout << "\n";
//for (int i = 1; i <= n; i++) cout << color1[i] << " "; cout << "\n";
//for (int i = 1; i <= c1; i++) cout << siz1[i] << " "; cout << "\n";
for (int i = 1; i <= n; i++) ans += (siz0[color0[i]] - 1) * (siz1[color1[i]] - 1);
cout << ans << "\n";
}
signed main() {
//ios::sync_with_stdio(0);
//cin.tie(0);
int T = 1;
while (T--) {
solve();
}
return 0;
}
Sewing Graph (构造)
懒得搬了,简单来说,一个二维图形的构造,你通常考虑一下一条链的情况就能想到怎么做,然后把二维的点按坐标排序一下就做好了。
Swapping Places (基于优先队列的拓扑排序)
题意:
给你一个单词序列,若干对朋友,两个单词相邻且互为朋友时可以交换。问交换后得到字典序最小的序列。
解析:
不能交换的两个单词,其组成的相对位置不会改变,在拓扑排序的结果中存在先后关系,而可以交换的单词在同一层中,可以贪心选择字典序最小的。
//
// Created by vv123 on 2022/7/14.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e3 + 10, M = 1e6 + 10;
string s[N];
unordered_map<string, int> rk;
bool changeable[N][N];
int S, m, n, last[N];
struct Node {
int id, rk;
bool operator<(const Node &x) const {
return rk > x.rk;
}
} node[M];
vector<int> g[M];
int in[M], ans[M];
inline void toposort() {
priority_queue<Node> q;//小根堆优先队列拓扑排序
for (int i = 1; i <= n; i++) {
if (!in[i]) q.push(node[i]);
}
int cnt = 0;
while (!q.empty()) {
Node u = q.top(); q.pop();
//printf("%d:\n", u.id);
ans[++cnt] = u.rk;
for (auto v:g[u.id]) {
//printf("->%d\n", v);
if (!--in[v]) q.push(node[v]);
}
}
}
inline void solve() {
cin >> S >> m >> n;
for (int i = 1; i <= S; i++)
cin >> s[i];
sort(s + 1, s + 1 + S);
for (int i = 1; i <= S; i++)
rk[s[i]] = i;
for (int i = 1; i <= m; i++) {
string a, b;
cin >> a >> b;
changeable[rk[a]][rk[b]] = changeable[rk[b]][rk[a]] = 1;
}
for (int i = 1; i <= n; i++) {
string str;
cin >> str;
node[i] = {i, rk[str]};
for (int j = 1; j <= S; j++) {
if (!last[j] || changeable[j][rk[str]]) continue;
g[last[j]].push_back(i); in[i]++;
//printf("%s -> %s\n", s[j].c_str(), s[rk[str]].c_str());
}
last[rk[str]] = i;
}
toposort();
for (int i = 1; i <= n; i++)
cout << s[ans[i]] << " ";
cout << "\n";
}
signed main() {
//ios::sync_with_stdio(0);
//cin.tie(0);
int T = 1;
while (T--) {
solve();
}
return 0;
}
day11
Prince and Princess(博弈)
题意
有a个只说真话的人(其中一位是公主),b个只说假话的人,c个回答可以为真可以为假的人。向这些人进行询问,可以进行三种询问,分别为询问回答者的身份,其他任意一个人的身份或谁是公主
解题思路
感觉这题模拟即可,只是读懂这个题目的trick很难。其实发现问什么问题不重要,只要a>b+c就一定YES,问的人数就是2*(b+c)+1。但这还不够,仔细观察下面这句话:
They will never refuse to answer the questions, but may not tell the truth. People, including Princess Tofu herself, who support this marriage will present the facts.
意思是,公主也在支持者的范围内,可能会问到公主。也就是支持者至少有一个人,即公主。所以就是当a为1且a>b+c时,什么都不要问,请他们立刻原地结婚
PS:仔细多读几遍题目并不是坏事,可能经常有遗漏的细节
————————————————
版权声明:本文为CSDN博主「Happig丶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_44691917/article/details/103519472
Game of Pairs(交互,构造,数学与图论)
题意:
给定一个n,你可以选择先手或后手,尝试赢得游戏:
1.先手把1~2n分成n个对
2.后手从每个对中选一个数
如果后手所选数的和是2n的倍数,则后手胜。如果不存在这种选法,则先手胜。
题解:
当n为偶数时,容易发现(i, i + n)这种构造,不管怎么选,模n相加都是(n-1)*n/2,这是n/2的奇数倍,模n不为0,自然不是2n的倍数。
当n为奇数时,结论时不管怎么分配后手都可以选到符合要求的方案,详见注释。
//
// Created by vv123 on 2022/7/15.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6 + 10;
int n, vis[N], chosen[N];
vector<int> pos[N], pir[N], ans;
void dfs(int u, int task) {
vis[u] = 1;
if (task == -1) {
int a = pir[u][0], b = pir[u][1];
if (a % n == b % n) {
chosen[a] = 1;
ans.push_back(a);
return;
}
else {
chosen[a] = 1;
ans.push_back(a);
for (int i = 0; i <= 1; i++) {
if (pos[b % n][i] != u)
dfs(pos[b % n][i], b % n);
}
return;
}
} else {
int a = pir[u][0], b = pir[u][1];
if (a % n != task) swap(a, b);
chosen[a] = 1;
ans.push_back(a);
for (int i = 0; i <= 1; i++) {
int v = pos[b % n][i];
if (v != u && !vis[v])
dfs(v, b % n);
}
}
}
inline int solve() {
cin >> n;
int x, y;
if (n % 2 == 0) {
cout << "First" << endl;
for (int i = 1; i <= n; i++) cout << i << " ";
for (int i = 1; i <= n; i++) cout << i << " ";
cout << endl;
cin >> x;
return 0;
} else {
//n为偶数时,就像上面那样构造,没问题
//n为奇数时,考虑选取一组(0,1,2...n-1) (mod n)
//显然它们的和 (n-1)/2 * n + kn是n的倍数
//所以这些数mod 2n为0或n
//又因为所有数的和=n * 2n - 2n, mod 2n = n
//所以上面选出的数mod 2n为0,就直接选,否则就选剩下的,也有mod 2n = (n - n) = 0
//为什么一定可以选出一组(0,1,2...n-1) (mod n)呢?
//我们把每个pair视为一个点,同余的两个数较小的向较大的连边
//除开mod n相同在同一pair里自娱自乐的,其他的每个点都是入度1出度1。显然是一堆环。
//每个点只对连向的点有约束,所以随便从哪一个点出发一定能把所有点选完
//所以可以dfs一波。。。。。。
cout << "Second" << endl;
for (int i = 1; i <= n * 2; i++) {
int t;
cin >> t;
pir[t].push_back(i);
pos[i % n].push_back(t);
}
for (int i = 1; i <= n; i++) {
if (!vis[i]) dfs(i, -1);
}
int sum = 0;
for (int i = 0; i < n; i++) {
sum = (sum + ans[i]) % (n * 2);
}
if (sum == 0) {
for (int i = 1; i <= 2 * n; i++) if (chosen[i]) cout << i << " ";
} else {
for (int i = 1; i <= 2 * n; i++) if (!chosen[i]) cout << i << " ";
}
cout << "\n";
cin >> x;
return 0;
}
}
signed main() {
//ios::sync_with_stdio(0);
//cin.tie(0);
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
day12
摆
day13
摆
day14
一不小心rk1了。
基本上有可能做对的赛时都过了。
不过换题之前的那个dp专题原题我没补...
题意:在值域有限的情况下,区间询问最小非子序列的长度。
题解:
我们能很明确的知道我们需要求的是一个长度最小的非子序列(简称S),而构造这个序列的办法是:从该区间的第一个字符开始划分“完整段”(指包含‘a’到(char)(‘a’+m-1)中每个字符的最小子串);依次将每个“完整段”的最后一个字符纳入S中;当剩下的字符不能再构成一个“完整段”时,选取一不在剩下字符中出现的字符加入S中。可得,该S为长度最小的非子序列之一(大家可以想想为什么)。
所以,该问题可以转化成求区间中有多少个“完整段”,可事先用st表初始化存一下(不然会T)
day15
Reverse the String(LCP)
题意
给定一个字符串,求可以将字符串中的任意区间的一个连续子串翻转一次的前提下,求翻转后字典序最小的字符串是什么
题解
将原串的所有字符排序,翻转肯定是从排序后的字符串与原字符串不同的位置开始的
那么可以遍历翻转的结束位置,比较所有翻转后字符串的大小
假如当前字典序最小的是 s ,那么和翻转后的字符串 t 比较时,可以先找到两个字符串的 最长公共前缀(lcp),前面都是相同的,只需比较不同的第一个位置的大小即可
此时,寻找两个字符串的lcp可以使用二分+哈希进行求解
————————————————
版权声明:本文为CSDN博主「Hexrt」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_45754027/article/details/123338212
day16
Maximum Subsequence(折半搜索)
题意:
从\(n\)个整数中选出一个子集,使它们的和\(\bmod m\)最大。
\(1\le n\le 35, 1\le m\le 10^9\)
题解:
\(2^{35}\)大概是\(7\cdot10^{11}\),而\(2^{16}=65536\).
如果可以把\(n\)砍一半,枚举子集就可以过了。
事实上是可以的,我们把所有数砍成两半,这样每一半都不超过\(2^{16}\)个子集,这样不妨枚举第一个集合的子集,二分查找第二个集合与它的最优配对。
可以实现得很暴力。如果换成dfs会更优一些。
//
// Created by vv123 on 2022/7/21.
//
#include <bits/stdc++.h>
#define int long long
using namespace std;
int a[40];
inline void solve() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) {
cin >> a[i];
a[i] %= m;
}
if (n == 1) { cout << a[1] << "\n"; return; }
set<int> s1, s2; s1.insert(0); s2.insert(0);
for (int i = 1; i <= n / 2; i++) {
int t = a[i];
vector<int> tmp;
for (auto u:s1) tmp.push_back(u);
for (auto u:tmp) s1.insert((u + t) % m);
}
for (int i = n / 2 + 1; i <= n; i++) {
int t = a[i];
vector<int> tmp;
for (auto u:s2) tmp.push_back(u);
for (auto u:tmp) s2.insert((u + t) % m);
}
//for (auto u:s1) cout << u << " "; cout << "\n";
//for (auto u:s2) cout << u << " "; cout << "\n";
int ans = 0;
for (auto u:s1) {
auto it = s2.lower_bound(m - u);
if (it == s2.begin()) continue;
it--;
ans = max(ans, u + *it);
}
cout << ans << "\n";
}
signed main() {
int T = 1;
//cin >> T;
while (T--) {
solve();
}
return 0;
}
day17
Sleepy Game(DFS,有向图判环)
懒得补了,搬运自 Codeforces 937 D. Sleepy Game(DFS 判断环) - 会打架的程序员不是好客服 - 博客园 (cnblogs.com)
题意:
Petya and Vasya 在玩移动旗子的游戏, 谁不能移动就输了。 Vasya在订移动计划的时候睡着了, 然后Petya 就想趁着Vasya睡着的时候同时定下策略, 如果可以赢得话输出Win 并输出路径, 如果步数在达到1e6的情况下,就认定为平局, 输出Draw,如果输的话就输出lost。
题解:
这题很容易就可以想到如果图中存在奇数长度的路径从起始点到叶子结点就是Win状态(这里还要注意绕环走的情况,比如说奇数长度的环是可以改变路径的奇偶性的),如果不存在奇数长度的路径但是存在环的话就是Draw,如果都不是就是Lost。写了以后深深地发现自己的DFS是真的挫,如何判断有向图的环@。@! DFS的时候:
res[u][0]==1
表示u这个点能从s点偶数路径到达
res[u][1]==1
表示u这个点能从s点奇数路径到达
如果dfs到某点发现res的值已经为1就不用再继续下去了,这样就能保证dfs时每个点最多被访问2次。那么如果存在一个点x,使得res[x][1]==1且x的出度为0,那么就是Win的方案。
#include<bits/stdc++.h>
using namespace std;
const int MAX_N = 1e5+9;
int res[MAX_N][2],point[MAX_N],num_s,st[MAX_N*4],vis[MAX_N];
vector<int> vec[MAX_N];
int N,M,x;
void init()
{
num_s = 0;
memset(st,0,sizeof(st));
memset(vis,0,sizeof(vis));
memset(res,0,sizeof(res));
memset(point,0,sizeof(point));
for(int i=0; i<MAX_N; i++) vec[i].clear();
}
bool dfs(int pos,int x)
{
if(res[pos][x^1] == 1) return false;
res[pos][x^1] = 1;
st[num_s ++] = pos;
if(vec[pos].size() == 0 && res[pos][1])
{
return true;
}
for(int i=0; i<vec[pos].size(); i++)
{
if(dfs(vec[pos][i],x^1)) return true;
}
num_s --;
return false;
}
bool cheak(int pos)
{
vis[pos]=1;
bool f=false;
for(int i=0; i<vec[pos].size(); i++)
{
if(vis[vec[pos][i]]==1) f=true;
else if(vis[vec[pos][i]]==0) f=cheak(vec[pos][i]);
if(f) return true;
}
vis[pos] = 2;
return false;
}
int main()
{
cin>>N>>M;
init();
for(int i=1; i<=N; i++)
{
int num,temp;
scanf("%d",&num);
for(int j=0; j<num; j++)
{
scanf("%d",&temp);
vec[i].push_back(temp);
}
}
int pos;
cin>>pos;
if(dfs(pos,1))
{
cout<<"Win"<<endl;
for(int i=0; i<num_s; i++)
{
printf("%d ",st[i]);
}
cout<<endl;
}
else if(cheak(pos))
cout<<"Draw"<<endl;
else
cout<<"Lose"<<endl;
}