A.Water The Garden
int ct = a / p;
if (a % p != 0) ++ct;
等价于
ct = (a + p - 1) / p;
B. Tea Queue
solution:按照题意模拟即可
C. Swap Adjacent Elements
题意:给定1-n的一个排列,存在一些i,其中1<=i < n,a[i]可以和a[i+1]交换, 询问是否可以通过一系列交换操作,使得序列升序。
思路:首先考虑数字1,假设其位置为pos1,必然需要从pos1位置通过交换移到1位置,需要a[1]、a[2]….a[pos-1]均可交换;然后考虑数字2,假设初始位置pos2,可能由于之前数字1的移动,导致数字2在pos2的基础上右移,但是由于交换是可逆的,必然存在一种方式,可逆的从现位置移动到pos2位置,因此我们只需要考虑是否可以从pos2位置通过交换移动到2位置,需要a[2]…a[pos2-1]均可交换。
对于任意的数字k,只需要保证a[k]…..a[posk-1]均可交换即可
#include<bits/stdc++.h>
using namespace std;
#define inf 0x3f3f3f3f3f3f3f3f
#define N 250037
#define pb push_back
#define mp make_pair
#define ff first
#define ss second
int n, m;
int a[N], sum[N];
char s[N];
bool cal(int l, int r) {
return sum[r] - sum[l - 1] == r - l + 1;
}
int main()
{
scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", a + i);
scanf("%s", s + 1);
for(int i = 1; i < n; i++) {
sum[i] = sum[i - 1];
if (s[i] == '1') sum[i]++;
}
bool res = true;
for(int i = 1; i <= n; i++) {
if (a[i] == i) continue;
if (a[i] < i && !cal(a[i], i - 1)) res = false;
if (a[i] > i && !cal(i, a[i] - 1)) res = false;
}
if (res) puts("YES"); else puts("NO");
return 0;
}
D. Tanks
Solution
由于勺子每次可以改变k的水量,因此我们首先考虑是否可以构造出m的水量,其中v≡m(mod k),我们可以通过朴素的01背包预处理出来O(nk),然后通过f数组倒推出对应的方案。
假设此方案需要t个tank,将这t个tank的水分都集中到一个tank上,其余n-t的tank水分集中到一个tank上,假设这两个坦克编号分别为now1和now2,其中a[now1]%k必定为m,a[now1]的水量与目标v只差若干个k。
如果a[now1]的水量多,直接将多的水量移到now2上就可以;如果他少的话,将a[now2]的水量移到a[now1]上,尽量移,不够移的则无解
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5010;
int n, k, V;
bool f[N][N];
int a[N], b[N];
int Cal(int x, int y) {
int ct = a[x] / k;
if (a[x] % k != 0) ++ct;
a[y] += a[x]; a[x] = 0;
return ct;
}
struct node {
int cnt, x, y;
} ans[N];
int cur = 0;
int main(){
scanf("%d%d%d", &n, &k, &V);
int sum = 0;
for(int i = 1; i <= n; i++) scanf("%d", a + i), b[i] = a[i] % k, sum += a[i];
if (sum < V) { puts("NO"); return 0; }
f[0][0] = true;
for(int i = 0; i < n; i++) for(int j = 0; j < k; j++) if (f[i][j]) f[i + 1][j] = true, f[i + 1][(j + b[i + 1]) % k] = true;
if (!f[n][V % k]) { puts("NO"); return 0; }
vector<int> s;
int res = V % k;
for(int i = n; i >= 1; i--) {
if (f[i-1][res]) continue;
s.push_back(i);
res -= b[i];
if (res < 0) res %= k, res += k;
}
int top = s.size(), now1, now2 = -1;
if (top == 0) {
ans[++cur] = (node) { Cal(1, 2), 1, 2};
now1 = 1;
} else now1 = s[0];
for(int i = 1; i < top; i++) ans[++cur] = (node){Cal(s[i], now1), s[i], now1};
for(int i = 1; i <= n; i++) if (i != now1 ) { now2 = i; break; }
for(int i = 1; i <= n; i++) if (i != now1 && i != now2 && a[i]) ans[++cur] = (node){Cal(i, now2), i, now2};
if (a[now1] < V) {
//printf("test %d %d %d %d\n", now1, now2, a[now1], a[now2]);
int res = (V - a[now1]) / k;
int tot = min(a[now2] / k, res);
if (tot < res) { puts("NO"); return 0; }
ans[++cur] = (node){tot, now2, now1};
a[now1] += tot * k;
a[now2] -= tot * k;
}
else if (a[now1] > V) {
// printf("test %d %d %d %d\n", now1, now2, a[now1], a[now2]);
int res = (a[now1] - V) / k;
ans[++cur] = (node){res, now1, now2};
}
puts("YES");
for(int i = 1; i <= cur; i++) if (ans[i].cnt) printf("%d %d %d\n", ans[i].cnt, ans[i].x, ans[i].y);
return 0;
}
E Connected Components?
题意:给定无向图,求其补图的联通块数目及每个联通块的点数量
思路:很nb的做法…..设原图边集为E,点集为V
初始我们任意拿出一个点x,遍历点集V,对于点y,若(x, y) 不属于E,则(x, y)存在于补图中,因此我们将y加入到此联通块中,及压入队列中,将一系列的y及x从V中删除;然后继续拿队列的其他节点遍历V,直至队列为空。此时相当于找到了一个联通块,队列中出现过的元素个数即为联通块点数量。
为了降低复杂度,我们用链表来维护点集,复杂度O(V+E)
首先每个点最多进队列一次,队列的每个元素遍历点集V可能会造成较高的复杂度,但是我们从另一个方向上去考虑,点集V的每个元素x最多会被枚举多少次? x被枚举多次是因为枚举完以后没有删除导致的,那什么时候枚举完x后,不会从链表中删除x呢? 当队列元素与x在原图联边时..x
不会被删除,因此x最大枚举次数就是原图中x的连边数
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 350010;
int n, m;
vector<int> adj[N];
struct node {
int pre, nxt;
} l[N];
bool vis[N], f[N];
vector<int> ans;
void del(int x) {
l[l[x].pre].nxt = l[x].nxt;
l[l[x].nxt].pre = l[x].pre;
}
void bfs(int st) {
queue<int> q;
q.push(st);
f[st] = true;
int cnt = 0;
while(!q.empty()) {
int x = q.front();
cnt++;
q.pop();
for(auto v : adj[x]) vis[v] = true;
for(int i = l[0].nxt; i != 0; i = l[i].nxt) if (!vis[i] && !f[i]) q.push(i), del(i), f[i] = true;
for(auto v : adj[x]) vis[v] = false;
}
ans.push_back(cnt);
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++) {
int x, y;
scanf("%d%d", &x, &y);
adj[x].push_back(y);
adj[y].push_back(x);
}
for(int i = 1; i <= n; i++) l[i].pre = i - 1, l[i - 1].nxt = i;
l[n].nxt = 0;
for(int i = 1; i <= n; i++) if (!f[i]) bfs(i);
sort(ans.begin(), ans.end());
printf("%d\n", ans.size());
printf("%d", ans[0]);
for(int i = 1; i < ans.size(); i++) printf(" %d", ans[i]);
return 0;
}
F. SUM and REPLACE
思路:线段树的套路题,当一种操作在多次重复后不对序列产生影响时,用这种暴力打标记的方法就好。举个例子,对一个数字开根号,常数次以后就会变成数字一,那么之后的开根号操作就不会产生影响。
这道题目我们可以对于线段树的每个节点i,打一个标记vis[i],代表这个节点对应的区间[l, r],replace 操作是否还会产生影响;对于每个replace 操作, 如果当前区间vis值为1,直接return掉就好,否则分层replace,当分到叶子层的时候,判断一下vis值,上调整的时候update vis值即可
#include <bits/stdc++.h>
using namespace std;
#define lson x << 1, l, mid
#define rson x << 1 | 1, mid + 1, r
#define ls x << 1
#define rs x << 1 | 1
typedef long long ll;
const int N = 350010;
struct node {
ll sum;
bool delta;
} tree[N << 2];
int a[N], n, m;
int d[1000007];
int ql, qr;
void pushUp(int x) {
tree[x].sum = tree[ls].sum + tree[rs].sum;
tree[x].delta = tree[ls].delta & tree[rs].delta;
}
void build(int x, int l, int r) {
if (l == r) {
tree[x].sum = a[l];
tree[x].delta = (d[a[l]] == a[l]);
return;
}
int mid = (l + r) >> 1;
build(lson), build(rson);
pushUp(x);
}
void update(int x, int l, int r) {
node& e = tree[x];
if (ql <= l && qr >= r) {
if (e.delta) return;
if (l == r) {
e.sum = d[e.sum];
e.delta = (d[e.sum] == e.sum);
return;
}
}
int mid = (l + r) >> 1;
if (ql <= mid) update(lson);
if (qr > mid) update(rson);
pushUp(x);
}
ll query(int x, int l, int r) {
if (ql <= l && qr >= r) return tree[x].sum;
ll ans = 0;
int mid = (l + r) >> 1;
if (ql <= mid) ans += query(lson);
if (qr > mid) ans += query(rson);
return ans;
}
int main(){
for(int i = 1; i <= 1000000; i++) for(int j = i; j <= 1000000; j += i) d[j]++;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; i++) scanf("%d", a + i);
build(1, 1, n);
while(m--) {
int t;
scanf("%d%d%d", &t, &ql, &qr);
if (t == 1) update(1, 1, n);
else cout << query(1, 1, n) << endl;
}
return 0;
}
G. List Of Integers
题目:考虑满足y>x,gcd(p, y) = 1条件下的第k小的y
思路:通过gcd(p, y)=1 可以读到满满的容斥气息(可能因为我比较辣鸡…没有读到),我们二分答案y,然后利用容斥算出x,mid之间满足gcd(p, y)=1的数量,与k比较一下可以判断得到答案是大了还是小了;
注意一点,我们找的是num>=k对应的最小的y
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 350010;
int n, m;
ll cal(ll mid, vector<ll> f) {
ll res = 0;
int n = f.size();
for(int i = 0; i < (1 << n); i++) {
ll tot = 1, k = 1;
for(int j = 0; j < n; j++) if (i >> j & 1) tot *= f[j], k *= -1;
res += k * (mid / tot);
}
// printf("%lld->%lld %d\n", mid, res, n);
return res;
}
int main(){
int T;
scanf("%d", &T);
while(T--) {
ll x, p, k;
scanf("%lld%lld%lld", &x, &p, &k);
vector<ll> f;
int tot = sqrt(p);
for(int i = 2; i <= tot; i++) if (p % i == 0){
f.push_back(i);
while(p % i == 0) p /= i;
}
if (p > 1) f.push_back(p);
ll sub = cal(x, f);
ll l = x + 1, r = 1e12;
ll ans = -1;
while(l <= r) {
ll mid = (l + r) >> 1;
if (cal(mid, f) - sub >= k) {
ans = mid;
r = mid - 1;
}
else l = mid + 1;
}
printf("%lld\n", ans);
}
return 0;
}