@Dmaxiya
2020-12-15T23:32:10.000000Z
字数 11089
阅读 1014
暑期集训
链接:2018 Multi-University Training Contest 2
过题数:3
排名:139/821
成员:官展鹏,冯彦博,孙昊哲
在一个 个节点 条边的图中,用最少的一笔画把图上所有的边都覆盖一次,问最少要画多少划,并输出方案。
多组输入(不超过 组),每组输入第一行为两个整数 ,接下去 行每行两个整数 ,表示在节点 和 之间有一条路径,数据保证没有重边与自环。
对于每组输入,第一行输出一个整数 ,表示最少的笔画数,接下去 行,每行表示一条路径,每行的第一个数字 表示这条路径经过的边的数量,后面跟着 个整数,每个整数表示经过编号为 的边,如果经过第 条路径时是从 到达 (路径方向与输入相同),则输出 ,否则(路径方向与输入相反)输出 ,路径需要按照笔画顺序输出。如果有多组输出任意一组。
输入 |
---|
3 3 1 2 1 3 2 3 |
输出 |
1 3 1 3 -2 |
对每个连通块分开处理,如果某个连通块内只有一个点,则不需要笔画,否则统计这个连通块内度为奇数的点的数量 ,则需要的最少笔画数量为 (欧拉路径性质),求和就是整张图的最少笔画数。然后对于每个连通块内度为奇数的点(点的个数一定为偶数),两两加边,这样就能使得所有点的度都为偶数,在新图上跑出欧拉路径,再将这个路径上之前加上去的边从环上删掉,就可以得到 条路径。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <cstring>
#include <string>
#include <vector>
#include <list>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <bitset>
#include <algorithm>
#include <sstream>
using namespace std;
#define LL long long
const int maxn = 100000 + 100;
struct Node {
int pos, Index;
Node() {}
Node(int p, int I) {
pos = p;
Index = I;
}
};
struct Edge {
int u, v;
};
int n, m, u, v, cnt, mm, cnttmp, cas;
int fa[maxn], num[maxn], deg[maxn], ans[maxn << 2], anstmp[maxn];
vector<Node> G[maxn];
vector<int> GG[maxn];
Edge edge[maxn << 2];
int vis[maxn << 2];
bool cmp(const int &a, const int &b) {
return deg[a] > deg[b];
}
void Init() {
cnt = 0;
mm = m + 1;
for(int i = 1; i <= n; ++i) {
fa[i] = i;
num[i] = 0;
deg[i] = 0;
G[i].clear();
GG[i].clear();
}
}
int Find(int x) {
return (x == fa[x]? x: fa[x] = Find(fa[x]));
}
void unit(int x, int y) {
int xx = Find(x);
int yy = Find(y);
if(deg[xx] % 2 == 1) {
swap(xx, yy);
}
if(xx != yy) {
fa[xx] = yy;
num[yy] += num[xx];
}
}
void dfs(int x) {
int len = G[x].size();
for(int i = 0; i < len; ++i) {
int pos = G[x][i].pos;
int Index = G[x][i].Index;
if(vis[abs(Index)] != cas) {
vis[abs(Index)] = cas;
dfs(pos);
ans[cnt++] = Index;
}
}
}
int Next(int x, int mod) {
return ((x - 1) % mod + mod) % mod;
}
void solve(int pos) {
cnt = 0;
sort(GG[pos].begin(), GG[pos].end(), cmp);
int len = GG[pos].size();
for(int i = 0; i < len; i += 2) {
if(deg[GG[pos][i]] == 1) {
u = GG[pos][i];
v = GG[pos][i + 1];
G[u].push_back(Node(v, mm));
G[v].push_back(Node(u, -mm));
++mm;
} else {
break;
}
}
dfs(pos);
int End = -1;
for(int i = 0; i < cnt; ++i) {
if(abs(ans[i]) > m) {
End = i;
break;
}
}
if(End == -1) {
printf("%d", cnt);
for(int i = cnt - 1; i >= 0; --i) {
printf(" %d", ans[i]);
}
printf("\n");
return ;
}
for(int i = Next(End, cnt); i != End; ) {
cnttmp = 0;
bool flag = false;
for(int j = i; j != End; j = Next(j, cnt)) {
if(abs(ans[j]) > m) {
i = Next(j, cnt);
flag = true;
break;
}
anstmp[cnttmp++] = ans[j];
}
if(!flag) {
i = End;
}
if(cnttmp == 0) {
continue;
}
printf("%d", cnttmp);
for(int j = 0; j < cnttmp; ++j) {
printf(" %d", anstmp[j]);
}
printf("\n");
}
}
int main() {
#ifdef Dmaxiya
freopen("test.txt", "r", stdin);
#endif // Dmaxiya
ios::sync_with_stdio(false);
while(scanf("%d%d", &n, &m) != EOF) {
++cas;
Init();
for(int i = 1; i <= m; ++i) {
scanf("%d%d", &u, &v);
G[u].push_back(Node(v, i));
G[v].push_back(Node(u, -i));
++deg[u];
++deg[v];
edge[i].u = u;
edge[i].v = v;
}
for(int i = 1; i <= n; ++i) {
deg[i] %= 2;
if(deg[i] == 1) {
++num[i];
}
}
for(int i = 1; i <= m; ++i) {
unit(edge[i].u, edge[i].v);
}
for(int i = 1; i <= n; ++i) {
GG[Find(i)].push_back(i);
}
for(int i = 1; i <= n; ++i) {
if((int)GG[i].size() <= 1) {
continue;
}
cnt += max(num[i] / 2, 1);
}
printf("%d\n", cnt);
for(int i = 1; i <= n; ++i) {
if((int)GG[i].size() <= 1) {
continue;
}
solve(i);
}
}
return 0;
}
和 两人玩一个博弈游戏,由 先开始,两人轮流进行下面的操作:在 到 个数字构成的集合中选择一个整数 ,然后将 及 的所有约数都从集合中删去,无法进行操作的一方失败。问如果双方都采取最优策略, 能否取得胜利。
多组输入(不超过 组),每组输入包含一个整数 。
对于每组输入,如果 可以获胜,则输出 ,否则输出 。
输入 |
---|
1 |
输出 |
Yes |
假设初始集合中只有 到 个整数,若 选择了数字 可以获胜,那么对于 到 的集合选择 之后剩下的数字与初始集合为 到 的下一个状态完全相同,都是必败态,若 初始集合 到 是一种必败态,那么 只要选择数字 就可以将必败态转移给 ,所以无论如何 都将获胜。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <cfloat>
#include <cstring>
#include <string>
#include <vector>
#include <list>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <algorithm>
using namespace std;
#define LL long long
int n;
int main() {
#ifdef Dmaxiya
freopen("test.txt", "r", stdin);
#endif // Dmaxiya
ios::sync_with_stdio(false);
while(scanf("%d", &n) != EOF) {
printf("Yes\n");
}
return 0;
}
构造一个 的 矩阵,要求矩阵内的 的个数不小于 且对于矩阵内任意一个子矩阵,子矩阵中的四个角的数字不同时为 。
无。
第一行为一个整数 ,表示将要输出的矩阵大小,接下来 行 列的字符串,字符串内每个字符只能为 或者 。
输入 |
---|
输出 |
3 010 000 000 |
提示 |
显然这个输出并不合法,只是为了说明输出格式。 |
关于构造方法的证明思路,不会,详见大佬给出的证明(也可以直接跳过证明看构造的方法):
文字描述构造方案比较麻烦,直接看图找规律比较好些,构造方案如下( 的情况下):
于是对于 的情况,找到子矩阵大小为 的就可以满足条件,将多出 的部分截断即可。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <cstring>
#include <string>
#include <vector>
#include <list>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <bitset>
#include <algorithm>
#include <sstream>
using namespace std;
#define LL long long
const int n = 47;
const int maxn = n * n + 100;
char str[maxn][maxn];
int main() {
#ifdef Dmaxiya
freopen("test.txt", "r", stdin);
#endif // Dmaxiya
ios::sync_with_stdio(false);
memset(str, '0', sizeof(str));
for(int i = 0; i < n; ++i) {
for(int j = 0; j < n; ++j) {
str[i][j * n + (i * j) % n] = '1';
}
}
for(int i = 1; i < n; ++i) {
for(int j = 0; j < n; ++j) {
for(int k = 0; k < n; ++k) {
for(int l = 0; l < n; ++l) {
str[i * n + j][k * n + l] = str[(i - 1) * n + j][k * n + ((l - 1) % n + n) % n];
}
}
}
}
printf("2000\n");
for(int i = 0; i < 2000; ++i) {
str[i][2000] = '\0';
printf("%s\n", str[i]);
}
return 0;
}
对于一个 的网格,每一个小格只能涂上黑色或者白色,问所有涂色方案中,至少有 行 列为黑色的方案数。
多组输入(不超过 组),每组为四个整数
输出方案数对 取模的结果。
输入 |
---|
3 4 1 2 |
输出 |
169 |
根据容斥原理可以知道,至少 行 列为黑色的方案数为 ,如果 和 是从 开始的,那么 的值就为 ,然而 和 不是从 开始的,而是从某个数字开始的,于是该系数就为 。系数 具体的推导可以根据在 容斥的加减计算过程中,至少为 行的状态被重复计算的次数来得到。最后对 和 进行 次预处理,再 地计算容斥公式,就可以得到答案。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <cstring>
#include <string>
#include <vector>
#include <list>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <bitset>
#include <algorithm>
#include <sstream>
using namespace std;
#define LL long long
const int maxn = 3001;
const int MOD = 998244353;
int n, m, A, B, ans;
int C[maxn][maxn], two[maxn * maxn];
int fa[maxn], fb[maxn];
void Init() {
for(int i = 0; i < maxn; ++i) {
for(int j = 0; j <= i; ++j) {
if(j == i || j == 0) {
C[i][j] = 1;
} else {
C[i][j] = C[i - 1][j - 1] + C[i - 1][j];
if(C[i][j] >= MOD) {
C[i][j] -= MOD;
}
}
}
}
two[0] = 1;
for(int i = 1; i < maxn * maxn; ++i) {
two[i] = two[i - 1] * 2;
if(two[i] >= MOD) {
two[i] -= MOD;
}
}
}
int main() {
#ifdef Dmaxiya
freopen("test.txt", "r", stdin);
#endif // Dmaxiya
ios::sync_with_stdio(false);
Init();
while(scanf("%d%d%d%d", &n, &m, &A, &B) != EOF) {
ans = 0;
for(int i = A; i <= n; ++i) {
fa[i] = 0;
for(int j = A; j < i; ++j) {
fa[i] = (fa[i] + (LL)C[i][j] * fa[j]) % MOD;
}
fa[i] = 1 - fa[i];
if(fa[i] < 0) {
fa[i] += MOD;
}
}
for(int i = B; i <= m; ++i) {
fb[i] = 0;
for(int j = B; j < i; ++j) {
fb[i] = (fb[i] + (LL)C[i][j] * fb[j]) % MOD;
}
fb[i] = 1 - fb[i];
if(fb[i] < 0) {
fb[i] += MOD;
}
}
for(int i = A; i <= n; ++i) {
LL tmp = (LL)fa[i] * C[n][i] % MOD;
for(int j = B; j <= m; ++j) {
ans = (ans + ((tmp * fb[j] % MOD) * C[m][j] % MOD) * two[(n - i) * (m - j)] % MOD) % MOD;
}
}
printf("%d\n", ans);
}
return 0;
}
有两个长度为 的序列 和 , 是一个 到 的排列, 序列初始每个值都为 ,有 次操作,每次操作为以下两种之一:
- :将区间 内的所有数字都加上 ;
- :询问 的值。
多组输入(不超过 组),每组输入第一行包含两个整数 ,接下去一行为 个整数 ,序列 是 到 的一个排列,接下去 行每行为一个字符串和两个区间的端点 ,若 ,则执行操作 ,否则执行操作 ,
对于每组询问,输出询问的答案。
输入 |
---|
5 12 1 5 2 4 3 add 1 4 query 1 4 add 2 5 query 2 5 add 3 5 query 1 5 add 2 4 query 1 4 add 2 5 query 2 5 add 2 2 query 1 5 |
输出 |
1 1 2 4 4 6 |
由于每次操作都只对区间 ,最坏情况下,如果每次操作都对整个区间 ,那么 次操作后区间的和为 ,因此对于每次 增加 时都更新一次(用树状数组更新复杂度为 ),这样区间更新就用区间增减区间最小值的线段树维护,线段树上每个数初始为 ,每次操作 就将区间 内的数字都 ,每当区间最小值为 时就单点更新这个最小值,并将这个值赋值为 ,总的时间复杂度约为 。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <cstring>
#include <string>
#include <vector>
#include <list>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <bitset>
#include <algorithm>
#include <sstream>
using namespace std;
#define LL long long
const int maxn = 100000 + 100;
int n, q, x, y;
char str[20];
int num[maxn];
int Min[maxn << 2], lazy[maxn << 2];
int sum[maxn];
int lowbit(int x) {
return x & (-x);
}
void update(int Index, int x) {
while(Index <= n) {
sum[Index] += x;
Index += lowbit(Index);
}
}
int query(int Index) {
int ret = 0;
while(Index > 0) {
ret += sum[Index];
Index -= lowbit(Index);
}
return ret;
}
int query(int l, int r) {
return query(r) - query(l - 1);
}
void push_up(int rt) {
Min[rt] = min(Min[rt << 1], Min[rt << 1 | 1]);
}
void push_down(int rt) {
if(lazy[rt] != 0) {
lazy[rt << 1] += lazy[rt];
lazy[rt << 1 | 1] += lazy[rt];
Min[rt << 1] += lazy[rt];
Min[rt << 1 | 1] += lazy[rt];
lazy[rt] = 0;
}
}
void build(int l, int r, int rt) {
lazy[rt] = 0;
if(l == r) {
sum[l] = 0;
Min[rt] = num[l];
return ;
}
int m = (l + r) >> 1;
build(l, m, rt << 1);
build(m + 1, r, rt << 1 | 1);
push_up(rt);
}
void solve(int l, int r, int rt) {
if(l == r) {
update(l, 1);
Min[rt] = num[l];
return ;
}
push_down(rt);
int m = (l + r) >> 1;
if(Min[rt << 1] == 0) {
solve(l, m, rt << 1);
}
if(Min[rt << 1 | 1] == 0) {
solve(m + 1, r, rt << 1 | 1);
}
push_up(rt);
}
void update(int L, int R, int l, int r, int rt) {
if(L <= l && r <= R) {
--Min[rt];
--lazy[rt];
if(Min[rt] == 0) {
solve(l, r, rt);
}
return ;
}
push_down(rt);
int m = (l + r) >> 1;
if(L <= m) {
update(L, R, l, m, rt << 1);
}
if(m < R) {
update(L, R, m + 1, r, rt << 1 | 1);
}
push_up(rt);
}
int main() {
#ifdef Dmaxiya
freopen("test.txt", "r", stdin);
#endif // Dmaxiya
ios::sync_with_stdio(false);
while(scanf("%d%d", &n, &q) != EOF) {
for(int i = 1; i <= n; ++i) {
scanf("%d", &num[i]);
}
build(1, n, 1);
for(int i = 0; i < q; ++i) {
scanf("%s%d%d", str, &x, &y);
if(str[0] == 'a') {
update(x, y, 1, n, 1);
} else {
printf("%d\n", query(x, y));
}
}
}
return 0;
}
对于一个长度为 的序列,如果序列中存在一个逆序对,则需要花费 的代价,为了减少代价,可以先将任意个相邻的数字进行交换,每次交换需要花费 的代价,问总的最小代价为多少?
有 组输入,每组输入的第一行为三个整数 ,第二行为 个整数 。
输出最小代价。
输入 |
---|
3 233 666 1 2 3 3 1 666 3 2 1 |
输出 |
0 3 |
由于每次代价为 的操作最多能减少一个逆序对,所以代价为 的操作次数与最终序列中剩下的逆序对的数量和,等于初始序列的逆序对数,因此答案就是总的逆序对数乘上 。对整个序列离散化然后用树状数组求逆序对。
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <climits>
#include <cstring>
#include <string>
#include <vector>
#include <list>
#include <queue>
#include <stack>
#include <map>
#include <set>
#include <bitset>
#include <algorithm>
using namespace std;
#define LL long long
const int maxn = 100000 + 100;
struct Node {
int num, Index;
};
bool operator<(const Node &a, const Node &b) {
return a.num < b.num;
}
int n, cnt;
Node node[maxn];
LL x, y, ans;
int num[maxn], sum[maxn];
int lowbit(int x) {
return (x & (-x));
}
void update(int Index, LL x) {
while(Index <= n) {
sum[Index] += x;
Index += lowbit(Index);
}
}
int query(int Index) {
LL ret = 0;
while(Index > 0) {
ret += sum[Index];
Index -= lowbit(Index);
}
return ret;
}
int query(int l, int r) {
return query(r) - query(l - 1);
}
int main() {
#ifdef Dmaxiya
freopen("test.txt", "r", stdin);
#endif // Dmaxiya
while(scanf("%d%I64d%I64d", &n, &x, &y) != EOF) {
ans = 0;
for(int i = 1; i <= n; ++i) {
scanf("%d", &num[i]);
node[i].num = num[i];
node[i].Index = i;
sum[i] = 0;
}
sort(node + 1, node + n + 1);
cnt = 1;
num[node[1].Index] = cnt;
for(int i = 2; i <= n; ++i) {
if(node[i].num != node[i - 1].num) {
++cnt;
}
num[node[i].Index] = cnt;
}
for(int i = 1; i <= n; ++i) {
ans += query(num[i] + 1, n);
update(num[i], 1);
}
printf("%I64d\n", min(x, y) * ans);
}
return 0;
}