Codeforces1070 2018-2019 ICPC, NEERC, Southern Subregional Contest (Online Mirror, ACM-ICPC Rules, Teams Preferred)总结

2022-11-12,,,

第一次打ACM比赛,和yyf两个人一起搞事情

感觉被两个学长队暴打的好惨啊


然后我一直做傻子题,yyf一直在切神仙题

然后放一波题解(部分)


A. Find a Number

LINK

题目大意

给你d和s,求你一个最小的数满足是d的倍数且数字和是s

思路

从高位到低位考虑广搜,把当前的长度和模d的余数作为状态,然后添加一个数就在对应的位置上加

一个模数只记录长度最小的状态,然后可以反着贪心回去

yyf的神仙code:

#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <queue>
using namespace std;
typedef bool boolean; const int N = 5005, D = 505;
#define pii pair<int, int>
#define fi first
#define sc second int d, s;
int f[N][D];
char lst[N][D];
int lstr[N][D];
boolean vis[N][D]; pii trans(int s, int r, int dig) {
return pii(s + dig, (r * 10 + dig) % d);
} inline void init() {
scanf("%d%d", &d, &s);
} queue<pii> que;
boolean bfs() {
que.push(pii(0, 0));
memset(f, 0x3f, sizeof(f));
f[0][0] = 0, vis[0][0] = true;
while (!que.empty()) {
pii e = que.front();
que.pop(); for (int i = 0; i <= 9; i++) {
pii eu = trans(e.fi, e.sc, i);
if (eu.fi > s)
break;
if (vis[eu.fi][eu.sc])
continue;
vis[eu.fi][eu.sc] = true;
lst[eu.fi][eu.sc] = i + '0';
lstr[eu.fi][eu.sc] = e.sc;
f[eu.fi][eu.sc] = f[e.fi][e.sc] + 1;
que.push(eu);
}
}
return vis[s][0];
} void print(int s, int r) {
if (!s && !r)
return ;
int nr = lstr[s][r];
print(s - lst[s][r] + '0', nr);
putchar(lst[s][r]);
} inline void solve() {
if (bfs()) {
print(s, 0);
} else
puts("-1");
} int main() {
init();
solve();
return 0;
}

B. Berkomnadzor

LINK

题目大意

不懂。。。。。

思路

没有。。。。。


C. Cloud Computing

LINK

题目大意

你连续n天需要k个cpu,有m个供给方案,每个形如在区间\([l_i,r_i]\)每天有\(c_i\)个价格是\(p_i\)的cpu,如果有一天选不满就必须全部选择

思路

两个树状数组/一个线段树维护当前这一天可以用的大小和代价,把\([l_i,r_i]\)进行一个小小的差分,然后直接扫过去每次找前k小就可以了

yyf的神仙code:

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <vector>
using namespace std;
typedef bool boolean; typedef class Opt {
public:
int i, p, c;
int sgn; Opt(int i, int p, int c, int sgn):i(i), p(p), c(c), sgn(sgn) { } boolean operator < (Opt b) const {
return i < b.i;
}
}Opt; const int bzmax = 21; template <typename T>
class IndexedTree {
public:
int s;
T* ar; IndexedTree() { }
IndexedTree(int s):s(s) {
ar = new T[(s + 1)];
memset(ar, 0, sizeof(T) * (s + 1));
} void add(int idx, T val) {
for ( ; idx <= s; idx += (idx & (-idx)))
ar[idx] += val;
} T query(int idx) {
T rt = 0;
for ( ; idx; idx -= (idx & (-idx)))
rt += ar[idx];
return rt;
} int kth(int k) {
int rt = 0, cur = 0;
for (int l = (1 << (bzmax - 1)); l; l >>= 1)
if ((rt | l) <= s && (cur + ar[rt | l]) < k)
rt |= l, cur += ar[rt];
return rt + 1;
}
}; #define ll long long const int V = 1e6 + 3; int n, K, m;
IndexedTree<ll> itc;
IndexedTree<ll> its;
vector<Opt> vs; inline void init() {
scanf("%d%d%d", &n, &K, &m);
itc = IndexedTree<ll>(V);
its = IndexedTree<ll>(V);
for (int i = 1, l, r, c, p; i <= m; i++) {
scanf("%d%d%d%d", &l, &r, &c, &p);
vs.push_back(Opt(l, p, c, 1));
vs.push_back(Opt(r + 1, p, c, -1));
}
} ll res = 0;
inline void solve() {
sort(vs.begin(), vs.end());
int pv = 0, s = (signed) vs.size();
for (int i = 1; i <= n; i++) {
while (pv < s && vs[pv].i == i) {
itc.add(vs[pv].p, vs[pv].c * vs[pv].sgn);
its.add(vs[pv].p, vs[pv].c * 1ll * vs[pv].p * vs[pv].sgn);
pv++;
}
ll cnt = itc.query(V);
if (cnt < K) {
res += its.query(V);
} else {
int p = itc.kth(K);
cnt = itc.query(p);
res += its.query(p) - (cnt - K) * p;
}
}
cout << res << endl;
} int main() {
init();
solve();
return 0;
}

D. Garbage Disposal

LINK

题目大意

有n天,每天会生产一些垃圾,每天的垃圾只当前这一天和第二天可以进行处理,并且在第n+1天不能剩下垃圾,一个垃圾袋可以装k单位垃圾,问你最少用多少的垃圾袋

思路

比较傻子的贪心,你当前每一天考虑把今天所有的垃圾装完,如果还有剩余就把明天的垃圾也装了(就相当于是在第二天装了),然后第n天全部装完就可以了

我的垃圾code:

//Author: dream_maker
#include<bits/stdc++.h>
using namespace std;
//----------------------------------------------
//typename
typedef long long ll;
//convenient for
#define fu(a, b, c) for (int a = b; a <= c; ++a)
#define fd(a, b, c) for (int a = b; a >= c; --a)
#define fv(a, b) for (int a = 0; a < (signed)b.size(); ++a)
//inf of different typename
const int INF_of_int = 1e9;
const ll INF_of_ll = 1e18;
//fast read and write
template <typename T>
void Read(T &x) {
bool w = 1;x = 0;
char c = getchar();
while (!isdigit(c) && c != '-') c = getchar();
if (c == '-') w = 0, c = getchar();
while (isdigit(c)) {
x = (x<<1) + (x<<3) + c -'0';
c = getchar();
}
if (!w) x = -x;
}
template <typename T>
void Write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9) Write(x / 10);
putchar(x % 10 + '0');
}
//----------------------------------------------
const int N = 3e5 + 10;
ll a[N], n, k, ans = 0;
int main() {
Read(n), Read(k);
fu(i, 1, n) Read(a[i]);
fu(i, 1, n - 1) {
if (!a[i]) continue;
ll num = a[i] / k;
if (a[i] % k) ++num;
a[i + 1] = max(0ll, a[i + 1] - (num * k - a[i]));
ans += num;
}
if (a[n]) {
ans += a[n] / k;
if (a[n] % k) ++ans;
}
Write(ans);
return 0;
}

E. Getting Deals Done

题目大意

你有n个任务,t单位时间,你需要确定一个值d

对任务进行操作的方式是:

按顺序遍历
如果当前任务的难度\(p_i\leq d\),你必须要执行这个任务,并花费\(p_i\)的时间
如果\(d<p_i\),你会直接跳过这个任务
每当你做完m个任务你需要休息上m个任务的工作时间的总时间这么久
问你在t单位时间内你最多能完成多少任务

有一些细节:

最后不一定做满m个任务,完成多少算多少
最后的休息时间可以不满,因为马上就是愉快的周末啦

思路1

首先明白在所有出现过的\(p_i\)中选一定可以选出最优解

比较暴力的话可以对于每个pi二分答案,维护一个\(p_j\leq p_i\)的树状数组,然后check可不可以选完就行了

yyf的神仙code:

#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#ifndef WIN32
#define Auto "%lld"
#else
#define Auto "%I64d"
#endif
using namespace std;
typedef bool boolean;
#define ll long long template <typename T>
class IndexedTree {
public:
int s;
T* ar; IndexedTree() { }
IndexedTree(int s):s(s) {
ar = new T[(s + 1)];
memset(ar, 0, sizeof(T) * (s + 1));
} void add(int idx, T val) {
for ( ; idx <= s; idx += (idx & (-idx)))
ar[idx] += val;
} T query(int idx) {
T rt = 0;
for ( ; idx; idx -= (idx & (-idx)))
rt += ar[idx];
return rt;
} int kth(int k) {
if (!k)
return 0;
int rt = 0, cur = 0;
for (int i = (1 << 18); i; i >>= 1)
if ((rt | i) <= s && ar[rt | i] + cur < k)
rt |= i, cur += ar[rt];
return rt + 1;
} void clear() {
s = 0;
delete[] ar;
}
}; const int N = 2e5 + 5;
#define pii pair<int, int>
#define fi first
#define sc second int T;
int n, m;
ll t;
IndexedTree<int> itc;
IndexedTree<ll> its;
int ps[N];
pii ar[N]; inline void init() {
scanf("%d%d"Auto, &n, &m, &t);
itc = IndexedTree<int>(n);
its = IndexedTree<ll>(n);
for (int i = 1; i <= n; i++)
scanf("%d", ps + i), ar[i] = pii(ps[i], i);
} int query(int i) {
int l = 1, r = i, mid, k, ti;
ll s = 0;
while (l <= r) {
mid = (l + r) >> 1;
k = itc.kth(mid);
s = its.query(k);
ti = (mid - 1) / m * m;
k = itc.kth(ti);
s += its.query(k);
if (s > t)
r = mid - 1;
else
l = mid + 1;
}
return l - 1;
} inline void solve() {
int ansc = 0, ansd = 1;
sort(ar + 1, ar + n + 1);
int i = 1, c;
while (i <= n) {
int cur = ar[i].fi;
while (i <= n && ar[i].fi == cur) {
itc.add(ar[i].sc, 1);
its.add(ar[i].sc, ar[i].fi);
i++;
}
c = query(i - 1);
if (c > ansc)
ansc = c, ansd = ar[i - 1].fi;
}
printf("%d %d\n", ansc, ansd);
} void clear() {
itc.clear();
its.clear();
} int main() {
scanf("%d", &T);
while (T--) {
init();
solve();
clear();
}
return 0;
}

思路2

如果贪心学的好一点,你发现肯定是尽量把从1到n遍历一遍的

如果对于\(d_1<d_2\),\(d_1\)可以遍历完但是\(d_2\)不可以,很显然是\(d_1\)比\(d_2\)更优的

因为相同的时间\(d_1\)可以做的任务一定不少于\(d_2\),如果两个都可以遍历完,那么\(d_2\)是肯定比\(d_1\)更优的

所以直接在排序后的\(p\)数组上二分最后的d

因为可能出现每个d都走不完,所以最后需要check一边\(d+1\)

我的垃圾code:

//Author: dream_maker
#include<bits/stdc++.h>
using namespace std;
//----------------------------------------------
//typename
typedef long long ll;
//convenient for
#define fu(a, b, c) for (int a = b; a <= c; ++a)
#define fd(a, b, c) for (int a = b; a >= c; --a)
#define fv(a, b) for (int a = 0; a < (signed)b.size(); ++a)
//inf of different typename
const int INF_of_int = 1e9;
const ll INF_of_ll = 1e18;
//fast read and write
template <typename T>
void Read(T &x) {
bool w = 1;x = 0;
char c = getchar();
while (!isdigit(c) && c != '-') c = getchar();
if (c == '-') w = 0, c = getchar();
while (isdigit(c)) {
x = (x<<1) + (x<<3) + c -'0';
c = getchar();
}
if (!w) x = -x;
}
template <typename T>
void Write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9) Write(x / 10);
putchar(x % 10 + '0');
}
//----------------------------------------------
const int N = 2e5 + 10;
ll n, m, t, p[N], a[N];
ll check(ll vl, bool typ) {
ll nowt = 0, cnt = 0, lastt = 0;
fu(i, 1, n) {
if (p[i] > vl) continue;
if (nowt + p[i] > t) return typ ? cnt : 0;
++cnt;
nowt += p[i], lastt += p[i];
if (cnt % m == 0) {
if (lastt + nowt > t) return typ ? cnt : 0;
nowt += lastt;
lastt = 0;
}
}
return typ ? cnt : 1;
}
void solve() {
Read(n), Read(m), Read(t);
fu(i, 1, n) Read(p[i]), a[i] = p[i];
sort(a + 1, a + n + 1);
a[0] = a[n + 1] = 1;
ll l = 1, r = n, res = 0;
while (l <= r) {
ll mid((l + r) >> 1);
if (check(a[mid], 0)) l = mid + 1, res = mid;
else r = mid - 1;
}
ll ans1 = check(a[res], 1);
ll ans2 = check(a[res + 1], 1);
if (ans1 >= ans2) {
printf("%lld %lld\n", ans1, a[res]);
} else {
printf("%lld %lld\n", ans2, a[res + 1]);
}
}
int main() {
#ifdef dream_maker
freopen("input.txt", "r", stdin);
#endif
int T;Read(T);
while (T--) solve();
return 0;
}

F. Debate

LINK

题目大意

有两个候选人(a,b)竞选,有n个人表达观点,这n个人每个人有一个影响力

这n个人可能是支持a,支持b,两个都不支持和两个都支持

选出一些非空的子集大小为m满足a的票数和b的票数不少于总人数m的一半

让你最大化影响力

思路

比较显然的是先把两个都支持的选完,然后只支持a和b的从大到小配对选

发现这个时候无论后面怎么选一定有一个人支持票数不会变,然后就可以算出最多能加入多少人了

直接贪心选最大就可以了

我的垃圾code:

//Author: dream_maker
#include<bits/stdc++.h>
using namespace std;
//----------------------------------------------
//typename
typedef long long ll;
//convenient for
#define fu(a, b, c) for (int a = b; a <= c; ++a)
#define fd(a, b, c) for (int a = b; a >= c; --a)
#define fv(a, b) for (int a = 0; a < (signed)b.size(); ++a)
//inf of different typename
const int INF_of_int = 1e9;
const ll INF_of_ll = 1e18;
//fast read and write
template <typename T>
void Read(T &x) {
bool w = 1;x = 0;
char c = getchar();
while (!isdigit(c) && c != '-') c = getchar();
if (c == '-') w = 0, c = getchar();
while (isdigit(c)) {
x = (x<<1) + (x<<3) + c -'0';
c = getchar();
}
if (!w) x = -x;
}
template <typename T>
void Write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9) Write(x / 10);
putchar(x % 10 + '0');
}
//----------------------------------------------
deque<ll> p[4];
ll n, w, ans = 0;
char s[3];
bool cmp(ll a, ll b) {
return a > b;
}
int main() {
Read(n);
fu(i, 1, n) {
scanf("%s", s);
Read(w);
if (s[0] == '0') {
if (s[1] == '0') p[0].push_back(w);
else p[1].push_back(w);
} else {
if (s[1] == '0') p[2].push_back(w);
else p[3].push_back(w);
}
}
fu(i, 0, 3) sort(p[i].begin(), p[i].end(), cmp);
ll ans = 0, num = 0, cnt0 = 0, cnt1 = 0;
fv(i, p[3]) {
ans += p[3][i];
num++;
cnt0++, cnt1++;
}
while (p[1].size() && p[2].size()) {
num += 2;
cnt1++, cnt0++;
ans += p[1].front() + p[2].front();
p[1].pop_front();
p[2].pop_front();
}
int last = cnt1 * 2 - num;
while (last--) {
if (!p[0].size() && !p[1].size() && !p[2].size()) break;
if (p[0].size()) {
bool can = 1;
if (p[1].size() && p[1].front() > p[0].front()) can = 0;
if (p[2].size() && p[2].front() > p[0].front()) can = 0;
if (can) {
ans += p[0].front();
p[0].pop_front();
continue;
}
}
if (p[1].size()) {
bool can = 1;
if (p[0].size() && p[0].front() > p[1].front()) can = 0;
if (p[2].size() && p[2].front() > p[1].front()) can = 0;
if (can) {
ans += p[1].front();
p[1].pop_front();
continue;
}
}
if (p[2].size()) {
bool can = 1;
if (p[1].size() && p[1].front() > p[2].front()) can = 0;
if (p[0].size() && p[0].front() > p[2].front()) can = 0;
if (can) {
ans += p[2].front();
p[2].pop_front();
continue;
}
}
}
Write(ans);
return 0;
}

G. Monsters and Potions

LINK

题目大意

有一个长度是n的棋盘(宽度是1),上面每个格子可能有药水,怪兽和空白,效果分别是把英雄的hp(加上一个值:药水),(减去一个值:怪兽),(不变:空白)

一个格子被经过就会变成空白

让你选定一个集结点,让你分布在棋盘上的m个英雄(每个有初始位置和初始hp)按照一定顺序走到这个点(每次移动一个英雄到这个点)

如果有一个英雄在半路因为遇到怪兽hp变成负数你就输了

问你是否有必胜策略(集结点位置和移动英雄的顺序)

思路

模拟吧。。枚举最后的集结点

把英雄按照和这个点的距离排序

然后贪心一下:如果一个离得近的英雄可以走到集结点那么就先走,因为他可以为后面的英雄扫清道路,这样一定是最优的

然后剩下的就是模拟

我的垃圾code:

//Author: dream_maker
#include<bits/stdc++.h>
using namespace std;
//----------------------------------------------
//typename
typedef long long ll;
//convenient for
#define fu(a, b, c) for (int a = b; a <= c; ++a)
#define fd(a, b, c) for (int a = b; a >= c; --a)
#define fv(a, b) for (int a = 0; a < (signed)b.size(); ++a)
//inf of different typename
const int INF_of_int = 1e9;
const ll INF_of_ll = 1e18;
//fast read and write
template <typename T>
void Read(T &x) {
bool w = 1;x = 0;
char c = getchar();
while (!isdigit(c) && c != '-') c = getchar();
if (c == '-') w = 0, c = getchar();
while (isdigit(c)) {
x = (x<<1) + (x<<3) + c -'0';
c = getchar();
}
if (!w) x = -x;
}
template <typename T>
void Write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9) Write(x / 10);
putchar(x % 10 + '0');
}
//----------------------------------------------
const int N = 1010;
ll n, m;
ll s[N], h[N], p[N];
ll now[N], tp[N], tot;
bool wk[N];
void init() {
fu(i, 1, n) now[i] = p[i];
memset(tp, 0, sizeof(tp));
memset(wk, 0, sizeof(wk));
tot = 0;
}
bool check_walk(ll fro, ll to, ll vl) {
if (fro == to) return 1;
if (fro > to) {
fd(i, fro, to) {
if (now[i] < 0 && vl + now[i] < 0) return 0;
vl += now[i];
}
return 1;
} else {
fu(i, fro, to) {
if (now[i] < 0 && vl + now[i] < 0) return 0;
vl += now[i];
}
return 1;
}
}
void get_clean(ll l, ll r) {
if (l >= r) {
fd(i, l, r) now[i] = 0;
} else {
fu(i, l, r) now[i] = 0;
}
}
struct Node {
int id, dis;
} w[N];
bool cmp(Node a, Node b) {
return a.dis < b.dis;
}
bool check(int pos) {
init();
fu(i, 1, m) {
w[i].id = i;
w[i].dis = labs(pos - s[i]);
}
sort(w + 1, w + m + 1, cmp);
fu(i, 1, m) {
if (check_walk(s[w[i].id], pos, h[w[i].id])) {
tp[++tot] = w[i].id;
get_clean(s[w[i].id], pos);
wk[w[i].id] = 1;
}
}
fu(i, 1, m) {
if (!wk[w[i].id]) {
if (!check_walk(s[w[i].id], pos, h[w[i].id])) return 0;
tp[++tot] = w[i].id;
wk[w[i].id] = 1;
}
}
Write(pos); putchar('\n');
fu(i, 1, m) {
Write(tp[i]);
putchar(' ');
}
return 1;
}
int main() {
Read(n), Read(m);
fu(i, 1, m) Read(s[i]), Read(h[i]);
fu(i, 1, n) Read(p[i]);
fu(i, 1, n)
if (check(i)) return 0;
printf("-1");
return 0;
}

H. BerOS File Suggestion

LINK

题目大意

给你n个串q个询问,问你这个字符串在多少字符串中出现过并输出任意一个

思路

hash。。就完了

yyf的神仙code:

#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <map>
using namespace std;
typedef bool boolean;
#define ull unsigned long long ull base = 233; const int N = 1e4 + 3; int n, q;
char strs[N][10];
map<ull, int> cnt;
map<ull, int> lst; inline void init() {
scanf("%d", &n);
for (int i = 1; i <= n; i++) {
scanf("%s", strs[i]);
char *s = strs[i];
for (int l = 0; s[l]; l++) {
ull ha = 0;
for (int r = l; s[r]; r++) {
ha = ha * base + s[r];
if (lst[ha] != i) {
lst[ha] = i;
cnt[ha]++;
// cerr << "added" << " " << ha << endl;
}
}
}
}
} char str[10];
inline void solve() {
scanf("%d", &q);
while (q--) {
scanf("%s", str);
ull ha = 0;
for (int i = 0; str[i]; i++)
ha = ha * base + str[i];
int r1 = cnt[ha];
if (r1)
printf("%d %s\n", r1, strs[lst[ha]]);
else
puts("0 -");
}
} int main() {
init();
solve();
return 0;
}

I. Privatization of Roads in Berland

LINK

题目大意

有n个城市,城市之间有一些道路,你需要把这些道路分给若干个公司,每个公司之多拥有2条道路,每个城市连接的道路最多有k个公司掌管,问你可不可行?方案?

思路

网络流

发现任意一个公司掌管尽量多的路,并且这两条路一定是有一个公共顶点,不然是不优的

所以可以考虑建模,直接把每个边分到每个城市上,然后一个城市掌管的边最多是\((du_i-k)*2\)

就可以随便建模了,最后看一下哪些边被割掉了就可以了

我的垃圾code:

//Author: dream_maker
#include<bits/stdc++.h>
using namespace std;
//----------------------------------------------
//typename
typedef long long ll;
//convenient for
#define fu(a, b, c) for (int a = b; a <= c; ++a)
#define fd(a, b, c) for (int a = b; a >= c; --a)
#define fv(a, b) for (int a = 0; a < (signed)b.size(); ++a)
//inf of different typename
const int INF_of_int = 1e9;
const ll INF_of_ll = 1e18;
//fast read and write
template <typename T>
void Read(T &x) {
bool w = 1;x = 0;
char c = getchar();
while (!isdigit(c) && c != '-') c = getchar();
if (c == '-') w = 0, c = getchar();
while (isdigit(c)) {
x = (x<<1) + (x<<3) + c -'0';
c = getchar();
}
if (!w) x = -x;
}
template <typename T>
void Write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9) Write(x / 10);
putchar(x % 10 + '0');
}
//----------------------------------------------
const int N = 1e5 + 10;
struct Edge {
int u, v, cap, flow;
};
vector<Edge> E;
vector<int> G[N];
int S, T;
int d[N], vis[N];
void init(int n) {
E.clear();
fu(i, 0, n) G[i].clear();
}
void add(int u, int v, int cap) {
E.push_back((Edge){u, v, cap, 0});
E.push_back((Edge){v, u, 0, 0});
int m = E.size();
G[u].push_back(m - 2);
G[v].push_back(m - 1);
}
bool bfs(){
memset(vis, 0, sizeof(vis));
memset(d, 0, sizeof(d));
queue<int> q; q.push(S);
vis[S] = 1;
while (!q.empty()) {
int u = q.front(); q.pop();
fv(i, G[u]) {
Edge e = E[G[u][i]];
if (!vis[e.v] && e.cap > e.flow) {
vis[e.v] = 1;
d[e.v] = d[u] + 1;
q.push(e.v);
}
}
}
return vis[T];
}
int dfs(int u, int a) {
if (u == T || !a) return a;
int flow = 0;
fv(i, G[u]) {
Edge &e = E[G[u][i]];
if (d[e.v] != d[u] + 1) continue;
int f = dfs(e.v, min(a, e.cap - e.flow));
flow += f;
a -= f;
e.flow += f;
E[G[u][i] ^ 1].flow -= f;
if (!a) break;
}
if (!flow) d[u] = 0;
return flow;
}
int Max_flow() {
int flow = 0;
while (bfs()) flow += dfs(S, INF_of_int);
return flow;
}
int du[N], u[N], v[N], f[N], pre[N], bel[N];
vector<int> pool[N];
void solve() {
int n, m, k;
Read(n), Read(m), Read(k);
fu(i, 1, n) {
du[i] = 0;
pool[i].clear();
}
fu(i, 1, m) {
Read(u[i]), Read(v[i]);
++du[u[i]], ++du[v[i]];
}
int cnt = n, sum = 0;
S = ++cnt, T = ++cnt;
fu(i, 1, n) {
f[i] = 2 * max(du[i] - k, 0);
add(i, T, f[i]);
sum += f[i];
}
fu(i, 1, m) {
int now = ++cnt;
add(S, now, 1);
add(now, u[i], 1);
pre[i] = E.size() - 2;
add(now, v[i], 1);
}
if (sum != Max_flow()) {
fu(i, 1, m) printf("0 ");
putchar('\n');
init(cnt);
return;
}
fu(i, 1, m) {
if (E[pre[i]].cap == E[pre[i]].flow)
pool[u[i]].push_back(i);
else
pool[v[i]].push_back(i);
}
sum = 0;
fu(i, 1, n)
fv(j, pool[i]) {
if (j & 1) bel[pool[i][j]] = bel[pool[i][j - 1]];
else bel[pool[i][j]] = ++sum;
}
fu(i, 1, m) {
Write(bel[i]);
putchar(' ');
}
putchar('\n');
init(cnt);
}
int main() {
#ifdef dream_maker
freopen("input.txt", "r", stdin);
#endif
int T; Read(T);
while (T--) solve();
return 0;
}

J. Streets and Avenues in Berhattan

LINK

题目大意

有一些颜色,每个颜色有一些数量

你需要选出大小为n的集合A和大小为m的集合B满足\((c_i=c_j)(i\in A,j\in B)\)的对数最少

输出最小的度数

思路

首先发现一个性质,可能存在颜色相等的数量最多只有一种,如果有两种及以上肯定是不优的

然后考虑枚举重复的颜色进行DP,\(dp_i\)表示A集合选了i个的时候B集合最多可以选多少个

我的垃圾code:

//Author: dream_maker
#include<bits/stdc++.h>
using namespace std;
//----------------------------------------------
//typename
typedef long long ll;
//convenient for
#define fu(a, b, c) for (int a = b; a <= c; ++a)
#define fd(a, b, c) for (int a = b; a >= c; --a)
#define fv(a, b) for (int a = 0; a < (signed)b.size(); ++a)
//inf of different typename
const int INF_of_int = 1e9;
const ll INF_of_ll = 1e18;
//fast read and write
template <typename T>
void Read(T &x) {
bool w = 1;x = 0;
char c = getchar();
while (!isdigit(c) && c != '-') c = getchar();
if (c == '-') w = 0, c = getchar();
while (isdigit(c)) {
x = (x<<1) + (x<<3) + c -'0';
c = getchar();
}
if (!w) x = -x;
}
template <typename T>
void Write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9) Write(x / 10);
putchar(x % 10 + '0');
}
//----------------------------------------------
const int N = 2e5 + 10;
int n, m, k, ans;
int cnt[30], dp[N];
char c[N];
bool cmp(int a, int b) {
return a > b;
}
void solve() {
memset(cnt, 0, sizeof(cnt));
ans = INF_of_int;
Read(n), Read(m), Read(k);
scanf("%s", c + 1);
fu(i, 1, k) cnt[c[i] - 'A' + 1]++;
fu(i, 1, 26) {
fu(j, 0, n + 1) dp[j] = -INF_of_int;
dp[0] = 0;
fu(j, 1, 26) {
if (i == j) continue;
fd(k, n, 0) {
dp[k] = max(dp[k], max(dp[k + 1], dp[k] + cnt[j]));
if (k >= cnt[j]) dp[k] = max(dp[k], dp[k - cnt[j]]);
}
}
fu(j, max(0, n - cnt[i]), n) {
if (m - dp[j] + n - j > cnt[i]) continue;
ans = min(ans, max(0, m - dp[j]) * (n - j));
}
}
Write(ans); putchar('\n');
}
int main() {
int T; Read(T);
while (T--) solve();
return 0;
}

K. Video Posts

LINK

题目大意

有n个视频,每一个有一个时间,让你把他们分成连续的k段使每一段时间相等

思路

傻逼模拟。。。

我的垃圾code:

//Author: dream_maker
#include<bits/stdc++.h>
using namespace std;
//----------------------------------------------
//typename
typedef long long ll;
//convenient for
#define fu(a, b, c) for (int a = b; a <= c; ++a)
#define fd(a, b, c) for (int a = b; a >= c; --a)
#define fv(a, b) for (int a = 0; a < (signed)b.size(); ++a)
//inf of different typename
const int INF_of_int = 1e9;
const ll INF_of_ll = 1e18;
//fast read and write
template <typename T>
void Read(T &x) {
bool w = 1;x = 0;
char c = getchar();
while (!isdigit(c) && c != '-') c = getchar();
if (c == '-') w = 0, c = getchar();
while (isdigit(c)) {
x = (x<<1) + (x<<3) + c -'0';
c = getchar();
}
if (!w) x = -x;
}
template <typename T>
void Write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9) Write(x / 10);
putchar(x % 10 + '0');
}
//----------------------------------------------
const int N = 1e5 + 10;
ll n, k, a[N], ans[N];
int main() {
Read(n), Read(k);
fu(i, 1, n) Read(a[i]), a[i] += a[i - 1];
if (a[n] % k) {
printf("No");
return 0;
}
ll len = a[n] / k, last = 0, tot = 0;
fu(i, 1, n) {
if (a[i] - a[last] == len) {
ans[++tot] = i - last;
last = i;
} else if(a[i] - a[last] > len) {
printf("No");
return 0;
}
}
printf("Yes\n");
fu(i, 1, k) {
Write(ans[i]);
putchar(' ');
}
return 0;
}

L. Odd Federalization

LINK

题目大意

n个点m条边,分成最小数量的块使得每个块内部所有点连向块内点的边数量都是偶数,保证没有重边

思路

首先发现答案一定小于等于2,答案是1的情况很好判断

考虑答案是2的情况怎么解决

如果把第i点放在哪个集合设成变量\(x_i\)

那么可以得到方程\(x_1g_{1,u}\bigoplus x_2g_{2,u}\bigoplus x_3g_{3,u}...\bigoplus x_ng_{n,u}\bigoplus x_u(du_u\&1)=(du_u\&1)\)

这个异或方程组直接可以bitset优化guass消元

我的垃圾code:

//Author: dream_maker
#include<bits/stdc++.h>
using namespace std;
//----------------------------------------------
//typename
typedef long long ll;
//convenient for
#define fu(a, b, c) for (int a = b; a <= c; ++a)
#define fd(a, b, c) for (int a = b; a >= c; --a)
#define fv(a, b) for (int a = 0; a < (signed)b.size(); ++a)
//inf of different typename
const int INF_of_int = 1e9;
const ll INF_of_ll = 1e18;
//fast read and write
template <typename T>
void Read(T &x) {
bool w = 1;x = 0;
char c = getchar();
while (!isdigit(c) && c != '-') c = getchar();
if (c == '-') w = 0, c = getchar();
while (isdigit(c)) {
x = (x<<1) + (x<<3) + c -'0';
c = getchar();
}
if (!w) x = -x;
}
template <typename T>
void Write(T x) {
if (x < 0) {
putchar('-');
x = -x;
}
if (x > 9) Write(x / 10);
putchar(x % 10 + '0');
}
//----------------------------------------------
const int N = 2010;
bitset<N> g[N];
int n, m, du[N];
void guass() {
fu(i, 1, n) {
int k = 0;
fu(j, i, n)
if (g[j].test(i)) {k = j; break;}
if (!k) continue;
swap(g[i], g[k]);
fu(j, 1, n) {
if (j != i && g[j].test(i))
g[j] ^= g[i];
}
}
}
void solve() {
Read(n), Read(m);
fu(i, 1, n) du[i] = 0, g[i].reset();
fu(i, 1, m) {
int u, v;
Read(u), Read(v);
g[u].set(v), g[v].set(u);
du[u] ^= 1, du[v] ^= 1;
}
bool can = 1;
fu(i, 1, n) {
if (du[i]) {
can = 0;
break;
}
}
if (can) {
puts("1");
fu(i, 1, n) putchar('1'), putchar(' ');
putchar('\n');
return;
}
fu(i, 1, n)
if (du[i]) g[i].set(i), g[i].set(n + 1);
guass();
puts("2");
fu(i, 1, n) putchar(g[i].test(n + 1) ? '1' : '2'), putchar(' ');
putchar('\n');
}
int main() {
int T;Read(T);
while (T--) solve();
return 0;
}

M. Algoland and Berland

LINK

题目大意

平面上有两个点集A和B需要链接一些边保证这A+B个点联通,边不能有交叉,给出B集合每个点的度数

每条边一定是一个端点是A集合中的点一个端点是B集合中的点

让你构造方案

思路

什么神仙题。。。不会


Codeforces1070 2018-2019 ICPC, NEERC, Southern Subregional Contest (Online Mirror, ACM-ICPC Rules, Teams Preferred)总结的相关教程结束。

《Codeforces1070 2018-2019 ICPC, NEERC, Southern Subregional Contest (Online Mirror, ACM-ICPC Rules, Teams Preferred)总结.doc》

下载本文的Word格式文档,以方便收藏与打印。