您的位置:首页 > 其它

Educational Codeforces Round 37 所有题目整理!

2018-02-05 00:01 218 查看

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
, sum
;
char s
;
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

;
int a
, b
;
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
;
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
[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[++c
4000
ur] = (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
;
struct node {
int pre, nxt;
} l
;
bool vis
, f
;
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
.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, 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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: