Petrozavodsk Summer 2022. Day 1. Welcome Contest
作者:互联网
Petrozavodsk Summer 2022. Day 1. Welcome Contest
是不是又开新坑了,毛营我来了!
挑几道自己会的 & 有意思 的题写题解 QwQ
D - Double Sort
- 给定 \(n,m(n\leq m)\),随机一个值域在 \([1,m]\) 且数字不重复的的长度为 \(n\) 的序列 \(a_i\)。
- 令 \(a_0=0\),将 \(a\) 数组排序,差分,再排序,再求前缀和。
- 如上操作后对 \(i\in[1,n]\) 每个位置求 \(a'_i\) 的期望值。
- \(n\leq 50,m\leq 10000\)。
首先最后前缀和的过程可以省略,直接求每个位置差分的期望值,最后加回来即可。
直接设 \(f(i,j,k)\) 表示:序列长度为 \(i\),值域为 \([1,j]\) 时 \(a'_{k+1}-a'_{k}\) 的期望值,那么 \(\text{ans}_i=\sum_{j=0}^{i-1} f(n,m,j)\)。
因为将差分排序了,转移不妨将所有差分 \(-1\),并将所有大小为 \(1\) 的差分都去掉,来归入值域更小的子问题。
考虑枚举大小为 \(1\) 的差分个数 \(l\),不难得到递推式:
\[f(i,j,k)=\frac{1}{\binom{j}{i}}\sum_{l=0}^{i} \binom{i}{l}\binom{j-i}{i-l} ([l<k](f(i-l,j-i,k-l)+1)+[l\geq k]) \]直接计算就是 \(O(n^3m)\) 的,喜闻乐见的跑不满。
code
#include<bits/stdc++.h>
typedef long long ll;
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define per(i, a, b) for(int i = (a); i >= (b); i --)
#define Ede(i, u) for(int i = head[u]; i; i = e[i].nxt)
using namespace std;
inline int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? - 1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
const int N = 55, M = 10010;
int n, m; double c[M][N], f[N][M][N];
void prework() {
c[0][0] = 1.0;
rep(i, 1, m) {
c[i][0] = 1.0;
rep(j, 1, min(i, n)) c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
}
}
int main() {
n = read(), m = read();
prework();
rep(j, 1, m) rep(i, 1, min(n, j)) rep(x, 0, i) {
double p = c[i][x] * c[j - i][i - x] / c[j][i];
rep(k, 1, x)
f[i][j][k] += p * 1.0;
rep(k, x + 1, i)
f[i][j][k] += p * (f[i - x][j - i][k - x] + 1.0);
}
double ans = 0.0;
rep(i, 1, n) ans += f[n][m][i], printf("%.9f\n", ans);
return 0;
}
F - Leaderboard Effect
-
有 \(n\) 支队伍参加一场有 \(m\) 道题目的 ACM 比赛,限时 \(t\) 秒。
-
每道题有参数 \(r_i,c_i,p_i\),分别表示:读题时间,做题时间,做出题目的概率。
-
每支队伍决策均如下:
-
挑选一道没读过的题目 \(i\),如果全都读过了那么剩余时间什么都不做。
挑选方式:如果所有没做过的题均无人通过,那么随机一道。否则按照通过人数加权随机一道。
-
使用 \(r_i\) 时间读题(即使已知时间不够了),读完后能够立刻知道能否做出这题,以 \(p_i\) 的概率能做出。
-
如果能做出,那么使用 \(c_i\) 时间 AC 这道题(即使已知时间不够了),榜上通过这题的人数 \(+1\),并回到步骤 1。否则直接回到步骤 1。
-
-
对每道题 \(i\),令 \(f(n,i)\) 表示这 \(n\) 支队伍中最终做出这题的人数,求 \(\lim_{n\to \infty} \frac{f(n,i)}{n}\)。
-
\(m\leq 17,t\leq 100\)。
最后一句话实际就是求每道题通过的概率。
一开始拿到这题发现条件繁多,看数据范围知道是状压,但完全不知道加权平均怎么决策。
实际上根据概率的性质直接维护就好,设 \(f(i,j)\) 表示在时间 \(i\) 题目 \(j\) 已经被通过的概率。
\(g(i,s)\) 表示在时间 \(i\) 开始新一轮决策,已经读过的题目集合为 \(s\) 的局面出现的概率。
转移直接转移就好,感觉很厉害。
code
#include<bits/stdc++.h>
typedef long long ll;
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define per(i, a, b) for(int i = (a); i >= (b); i --)
#define Ede(i, u) for(int i = head[u]; i; i = e[i].nxt)
using namespace std;
inline int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? - 1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
const int N = 17, M = 110;
const double eps = 1e-10;
int n, m, r[N], c[N];
double p[N], f[M][N], g[M][1 << N];
int main() {
n = read(), m = read();
rep(i, 0, n - 1) r[i] = read(), c[i] = read(), scanf("%lf", &p[i]);
g[0][0] = 1.0;
rep(i, 0, m) {
if(i) rep(o, 0, n - 1) f[i][o] += f[i - 1][o];
rep(s, 0, (1 << n) - 1) {
double sum = 0.0; int cnt = 0;
rep(o, 0, n - 1) if(! (s >> o & 1)) sum += f[i][o], cnt ++;
rep(o, 0, n - 1) if(! (s >> o & 1)) {
double cur = (sum > eps) ? (f[i][o] / sum) : (1.0 / cnt);
if(i + r[o] <= m)
g[i + r[o]][s | (1 << o)] += cur * (1 - p[o]) * g[i][s];
if(i + r[o] + c[o] <= m)
f[i + r[o] + c[o]][o] += cur * p[o] * g[i][s],
g[i + r[o] + c[o]][s | (1 << o)] += cur * p[o] * g[i][s];
}
}
}
rep(i, 0, n - 1) printf("%.10f\n", f[m][i]);
return 0;
}
H - Ranked Choice Spoiling
- 有 \(2\) 或 \(3\) 个候选人参与选票,分别编号 A,B,以及可能有的 C。
- 已知 \(n\) 个选民心中的候选人排行,选举过程如下:
- 每个人向还存在的候选人中自己心中排行最高的投票。
- 如果有人得票超过半数那么直接胜利。
- 否则排除掉得票最少的候选人,得票一样少优先排除字典序大的。
- 现在你作为 A 候选人,希望自己赢得选举。你可以新加入一个候选人 Z,并随意控制选美心中他的排名。
- 询问 A 是否能赢得选举。
- \(n\leq 1000\)。
首先只有两个人是简单的,只有两种排行:AB 和 BA。
如果 A 本来就能赢那就赢了,否则第一轮就一定是要让 B 出局的,方法是将一些 BA 变为 ZBA。
当然同时不能让 Z 喧宾夺主了,不难计算发现极限情况是 AB : BA = 1 : 3。
对于三个的情况也是类似的。
首先容易证明 Z 只会放在开头或结尾。
然后枚举先排除 B 还是 C,并尽量少的放 Z 来排除掉他,以 B 为例。
一定是优先转变 BCA,因为 BAC 在 B 出局后就能给 A 贡献。之后考虑两种情况:
- Z 先出局。这种情况很好判定,所有 Z 的得票不减,其它人的不增,所以直接考虑当前局面 Z 能否出局,然后变成两个人的情况。
- C 先出局。那么类似的考虑不断添加 Z 来干掉 C 即可,注意到 C 出局后没有被添加 Z 的 C 一定是贡献给 A 的。
先排除 C 的决策是类似的,所以这题只是比较简单的贪心。但是这是赛时通过队伍最少的一道题?
code
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define per(i, a, b) for(int i = (a); i >= (b); i --)
#define Ede(i, u) for(int i = head[u]; i; i = e[i].nxt)
using namespace std;
inline int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? - 1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
const int N = 1010;
int n, m, num[3], cnt[3], sta[6], cur[6];
bool solve0() {
rep(i, 0, 2) cnt[i] = num[i];
if(cnt[1] > n / 2 || cnt[2] > n / 2) return false;
if(cnt[1] > cnt[0] && cnt[2] > cnt[0]) return false;
if(cnt[2] <= cnt[1]) {
cnt[0] += sta[4], cnt[1] += sta[5];
return 3 * cnt[0] >= cnt[1];
}
else {
cnt[0] += sta[2], cnt[2] += sta[3];
return 3 * cnt[0] >= cnt[2];
}
}
bool solve1() {
if(! num[2]) return false;
int now[4];
rep(i, 0, 3) now[i] = 0;
rep(i, 0, 5) cur[i] = sta[i];
if(num[1]) {
int aim = min(num[0], min(num[1], num[2]));
if(num[2] == aim) aim --;
int ned = num[1] - aim;
if(cur[3] >= ned) cur[3] -= ned, now[3] += ned;
else {
ned -= cur[3], now[3] += cur[3], cur[3] = 0;
cur[2] -= ned, now[2] += ned;
}
}
now[0] = num[0] + cur[2];
now[1] = num[2] + cur[3];
int x = now[0], y = now[1], z = now[2] + now[3];
if(y > x) z += y - x, y = x;
while(z <= y) z ++, y --;
if(x + y >= z) return true;
x = now[0], y = now[1], z = now[2] + now[3];
if(z <= x && z <= y && x + now[2] >= y + now[3]) return true;
return false;
}
bool solve2() {
int now[4];
rep(i, 0, 3) now[i] = 0;
rep(i, 0, 5) cur[i] = sta[i];
if(num[2]) {
int aim = min(num[0], min(num[1], num[2]));
int ned = num[2] - aim;
if(cur[5] >= ned) cur[5] -= ned, now[3] += ned;
else {
ned -= cur[5], now[3] += cur[5], cur[5] = 0;
cur[4] -= ned, now[2] += ned;
}
}
now[0] = num[0] + cur[4];
now[1] = num[1] + cur[5];
int x = now[0], y = now[1], z = now[2] + now[3];
if(y > x) z += y - x, y = x;
while(z <= y) z ++, y --;
if(x + y >= z) return true;
x = now[0], y = now[1], z = now[2] + now[3];
if(z <= x && z <= y && x + now[2] >= y + now[3]) return true;
return false;
}
int main() {
n = read(), m = read();
if(m == 2) {
rep(i, 1, n) {
char a[3]; scanf("%s", a);
char b[3]; scanf("%s", b);
if(a[0] == 'A') cnt[0] ++; else cnt[1] ++;
}
puts(3 * cnt[0] >= cnt[1] ? "1" : "0");
return 0;
}
rep(i, 1, n) {
char a[3]; scanf("%s", a);
char b[3]; scanf("%s", b);
char c[3]; scanf("%s", c);
num[a[0] - 'A'] ++;
if(a[0] == 'A') sta[b[0] == 'B' ? 0 : 1] ++;
else if(a[0] == 'B') sta[b[0] == 'A' ? 2 : 3] ++;
else sta[b[0] == 'A' ? 4 : 5] ++;
}
if(solve0() || solve1() || solve2()) puts("1"); else puts("0");
return 0;
}
K - Transparency
- 给定一个 \(n\) 个节点的自动机,\(1\) 为初始节点,有 \(p\) 个终止节点。
- 转移只有大小写字母,求构造两个自动机可接受的串 \(a,b\),使得 \(a,b\) 在去掉小写字母后完全相同,但是 \(a,b\) 本身不同。
- 输出最小的 \(|a|+|b|\)。
- \(n\leq 50\)。
思考时间最长的一道题。猛然发现直接优化建图就好……
相当于两个点在自动机上走,首先将所有终止节点都连向一个虚点 \(z\)。
\(\text{same}(i)\) 表示到 \(i\) 都一模一样。
\(\text{diff}(i,j)\) 表示一个到了节点 \(i\) 一个到了节点 \(j\) 且一定不完全一样。
发现这样的转移是乏力的,因为缺少了从 \(\text{same}\) 开始一直总小写字母的转移。
这种转移时不能归入 \(\text{diff}\) 的,因为 \(\text{diff}\) 两个节点都能任意走小写字母,如果从 \(\text{same}\) 出发很可能错误的构造了一模一样的串。
解决方法是再设 \(\text{walk}(i,j)\) 表示一个到了节点 \(i\) 一个到了节点 \(j\),且节点 \(i\) 比节点 \(j\) 多走了若干小写字母的情况。
转移按照定义就好,时刻保证大写字母的子序列完全一致。之后最短路的起点就是 \(\text{same}(1)\),终点就是 \(\text{diff}(z,z)\)。
点数是 \(O(n^2)\) 的,边数再乘字符集大小,还是很宽松的。
code
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define per(i, a, b) for(int i = (a); i >= (b); i --)
#define Ede(i, u) for(int i = head[u]; i; i = e[i].nxt)
using namespace std;
#define eb emplace_back
typedef pair<int, int> pii;
#define mp make_pair
#define fi first
#define se second
inline int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? - 1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
const int N = 55;
const int M = N * N * 3;
const int inf = 1e9;
int n, p, m, tr[N][60];
int tot, same[N], walk[N][N], diff[N][N];
int dis[M]; bool vis[M];
vector<pii> g[M];
void add(int u, int v, int w) {g[u].eb(mp(v, w));}
int main() {
n = read() + 1, p = read(), m = read();
rep(i, 1, p) {int x = read(); tr[x][0] = n;}
rep(i, 1, m) {
int x = read();
char c[3]; scanf("%s", c);
int y = read();
int cur = (c[0] <= 'Z') ? (c[0] - 'A' + 1) : (c[0] - 'a' + 27);
tr[x][cur] = y;
}
rep(i, 1, n) {
same[i] = ++ tot;
rep(j, 1, n) walk[i][j] = ++ tot, diff[i][j] = ++ tot;
}
rep(i, 1, n) {
rep(c, 0, 52) if(tr[i][c]) add(same[i], same[tr[i][c]], 2);
rep(c, 27, 52) if(tr[i][c]) add(same[i], walk[tr[i][c]][i], 1);
rep(c1, 27, 52) rep(c2, 27, 52) if(tr[i][c1] && tr[i][c2] && c1 != c2)
add(same[i], diff[tr[i][c1]][tr[i][c2]], 2);
}
rep(i, 1, n) rep(j, 1, n) {
rep(c, 27, 52) if(tr[i][c])
add(diff[i][j], diff[tr[i][c]][j], 1),
add(walk[i][j], walk[tr[i][c]][j], 1);
rep(c, 27, 52) if(tr[j][c])
add(diff[i][j], diff[i][tr[j][c]], 1);
rep(c, 0, 26) if(tr[i][c] && tr[j][c])
add(diff[i][j], diff[tr[i][c]][tr[j][c]], 2),
add(walk[i][j], diff[tr[i][c]][tr[j][c]], 2);
}
rep(i, 1, tot) dis[i] = inf;
priority_queue<pii> q;
int s = same[1]; q.push(mp(dis[s] = 0, s));
while(! q.empty()) {
int u = q.top().se; q.pop();
if(vis[u]) continue; else vis[u] = true;
for(pii e : g[u]) {
int v = e.fi, w = e.se;
if(dis[v] > dis[u] + w) dis[v] = dis[u] + w, q.push(mp(- dis[v], v));
}
}
int ans = dis[diff[n][n]];
printf("%d\n", ans == inf ? -1 : ans - 2);
return 0;
}
M - Word Ladder
- 构造长度为 \(5000\) 的单次表,使得每个单词长度均相等且 \(\leq 10\)。
- 且相邻单词的汉明距离为 \(1\),且不相邻单词的汉明距离不为 \(1\)。
- 单词只能由小写字母构成。
WA 了 7 发终于写对各种细节的构造。
实际也比较简单,从 aaaaaaaaaa
出发,每次挑相邻的两个一直执行如下过程:
aa -> ba -> bb -> cb -> cc -> ... -> zy -> zz
这样大概的总单词数就是 \(5\times 26\times 50\) 的,但是不同列的转换,以及不同底字母的转换有些细节,可能会牺牲一些单词。
最终造出的单词表大小为 \(6461\) QwQ
code
#include<bits/stdc++.h>
typedef long long ll;
typedef unsigned long long ull;
#define rep(i, a, b) for(int i = (a); i <= (b); i ++)
#define per(i, a, b) for(int i = (a); i >= (b); i --)
#define Ede(i, u) for(int i = head[u]; i; i = e[i].nxt)
using namespace std;
#define eb emplace_back
inline int read() {
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') f = (c == '-') ? - 1 : 1, c = getchar();
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
vector<vector<char>> ans;
vector<char> solve(vector<char> s, int c, int p) {
int cur = (c == 0 ? 1 : (c == 1 ? 2 : 0));
rep(i, 1, 24 + (c == 0)) {
s[p] = cur + 'a'; if(! p || i > 1) ans.eb(s);
s[p ^ 1] = cur + 'a'; if(! p || i > 1) ans.eb(s);
cur ++;
if(cur == c - 1) cur ++;
if(cur == c) cur ++;
}
return s;
}
int main() {
rep(c, 0, 24) {
vector<char> sta(10, c + 'a'), las;
if(c == 0) ans.eb(sta);
rep(i, 0, 4) {
las = solve(sta, c, i << 1);
if(i < 4) {
int nxt = (c == 0 ? 1 : (c == 1 ? 2 : 0));
las[(i << 1) + 2] = nxt + 'a', ans.eb(las);
las[(i << 1) + 3] = nxt + 'a', ans.eb(las);
las[i << 1] = c + 'a', ans.eb(las);
las[i << 1 | 1] = c + 'a', ans.eb(las);
}
}
if(c < 25) {
rep(j, 0, 2) las[j] = c + 1 + 'a', ans.eb(las);
per(j, 9, 3) las[j] = c + 1 + 'a', ans.eb(las);
}
}
int n = read();
rep(i, 0, n - 1) {rep(j, 0, 9) putchar(ans[i][j]); puts("");}
return 0;
}
标签:Summer,cnt,cur,Contest,int,rep,Welcome,num,now 来源: https://www.cnblogs.com/lpf-666/p/16676222.html