2024杭电多校第一场
1005 博弈(hdu7437)
好吧赛时根本没写到它()
开始看题以为得一步一步算(是什么让我有这么离谱的思路.jpg),看了官方题解才发现自己的愚蠢呜呜,就是说有没有一种可能,A和B前 \(\lfloor n/2 \rfloor\) 位的字符串情况是“等价”的?可以视为把A的字符直接换给B,两方取到每种结果的概率实际上一样。。。 但对于A而言,平局并不计入胜利情况,单独考虑平局即可。
于是开始快乐分类讨论如下:
当 \(n\) 为偶数时,设平局概率为 \(p\) ,A获胜的概率即为 \((1-p)/2\) ; \(n\) 为奇数时,由于A比B多取一个字符,若前 \((n-1)/2\) 个字符相同,则A必胜,设前 \((n-1)/2\) 个字符相同的概率为 \(p\) ,A获胜的概率为 \((1+p)/2\) .
至于如何计算平局的概率,本菜鸡的求法极其抽象()将情况总数看作“长为 \(2n\) 的序列,每个位置在原字符集里挑选一种字符的方案数”,其中前 \((n-\lfloor n/2 \rfloor)\) 位属于A,后 \(\lfloor n/2 \rfloor\) 属于B,这样就避免了重复情况,可以用高中的排列组合知识 \(O(1)\) 计算;用cnt数组记录每个字母出现的次数,前 \(\lfloor n/2 \rfloor\) 个字符相同的概率可同理看作“长为 \(\lfloor n/2 \rfloor\) 的序列,在字符集里挑选的方案数”,其中第 \(i\) 种字符只有 \(cnt[i]/2\) 个,仍然可以 \(O(1)\) 求出。此处注意 \(n\) 的奇偶情况略有不同,特判一下就好,问题不大。
主要代码如下:(还是好长一坨,QwQ)
int n;
scanf("%d", &n);
int s = 0;
for(int i = 1; i <= n; i++) {
char c = getchar();
while(c < 'a' || c > 'z') c = getchar();
int h;
scanf("%d", &h);
s += h;
cnt[c - 'a'] += h;
}
if(s & 1) {
int ok = 1;
for(int i = 0; i < 26; i++) {
if(cnt[i] & 1) {
if(ok) {
ok = 0;
} else {
ok = 1;
break;
}
}
}
if(ok) {
printf("%lld\n", inv(2));
continue;
}
ll x = 1, y = 1, ys = s / 2;
for(int i = 0; i < 26; i++) {
if(!cnt[i]) continue;
x = x * c(s, cnt[i]) % mo;
s -= cnt[i];
}
for(int i = 0; i < 26; i++) {
if(cnt[i] <= 1) continue;
cnt[i] /= 2;
y = y * c(ys, cnt[i]) % mo;
ys -= cnt[i];
}
ll p = y * inv(x) % mo;
printf("%lld\n", (1 + p) * inv(2) % mo);
} else {
int ok = 0;
for(int i = 0; i < 26; i++) {
if(cnt[i] & 1) {
ok = 1;
break;
}
}
if(ok) {
printf("%lld\n", inv(2));
continue;
}
ll x = 1, y = 1, ys = s / 2;
for(int i = 0; i < 26; i++) {
if(!cnt[i]) continue;
x = x * c(ys * 2, cnt[i]) % mo;
y = y * c(ys, cnt[i] / 2) % mo;
ys -= cnt[i] / 2;
}
ll p = y * inv(x) % mo;
printf("%lld\n", (1 + mo - p) * inv(2) % mo);
}
1006 序列立方 (hdu7438)
一言以蔽之:人类智慧题(bushi
需要求出序列中每个非空子序列出现次数的立方值的和(什么绕口令),这个立方值看上去十分抽象也不好处理,于是换一种思路:在序列中可重复地取三个子序列,它们相同的情况总数。
考虑动态规划,\(dp[i][j][k]\) 表示已经选了三个相同的子序列,其结尾分别在第 \(i,j,k\) 位,故 \(a[i]=a[j]=a[k]\) 时满足条件。使用前缀和数组 \(s[i][j][k]\) 表示以 \(i,j,k\) 位结尾的方案总数,有 \(a[i]=a[j]=a[k]\) 时 \(dp[i][j][k]=s[i-1][j-1][k-1]\) ,前缀和可用容斥思想维护。
代码如下,注意代码中用ans统计答案,可略去dp数组。
for(int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
for(int i = 0; i <= n; i++) {
for(int j = 0; j <= n; j++) {
s[i][j][0] = s[i][0][j] = s[0][i][j] = 1;
}
}
ll ans = 0;
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= n; j++) {
for(int k = 1; k <= n; k++) {
ll ss = (s[i - 1][j][k] + s[i][j - 1][k] + s[i][j][k - 1]) % mo;
ss -= (s[i - 1][j - 1][k] + s[i - 1][j][k - 1]
+ s[i][j - 1][k - 1]) % mo;
ss = (ss + mo) % mo;
ss += s[i - 1][j - 1][k - 1];
ss %= mo;
if(a[i] == a[j] && a[i] == a[k]) {
ans += s[i - 1][j - 1][k - 1];
ans %= mo;
ss += s[i - 1][j - 1][k - 1];
ss %= mo;
}
s[i][j][k] = ss;
}
}
}
printf("%lld\n", ans);
1012 并 (hdu7444)
赛时死活过不了,赛后全删掉重写了一遍就过了,真好,有一只狸花猫释怀地似了。
看上去非常有扫描线风味的题,其实并没有那么麻烦,由于 \(n \le 2000\) ,\(n^2\) 做法完全能过,我自己就写的差分,赛后听xht说也可以用类似扫描线的存边方式维护+统计。至于期望部分就不难了(我没挂在期望上那就是不难),前面的差分or扫描线统计每块面积被覆盖的次数,设 \(cov[i]\) 表示恰好被 \(i\) 个矩形所覆盖的总面积,选取 \(k\) 个矩形时,期望等于概率(1-没取到该面积的不合法情况数/总情况数,类似容斥思想)乘以面积,总和即 \(\sum\limits_{i=1}^{n}(\binom{n}{k}-\binom{n-i}{k})*cov[i]/\binom{n}{k}\).
以下为差分代码
int n;
scanf("%d", &n);
c[0][0] = 1;
for(int i = 1; i <= n; i++) {
c[i][0] = 1;
for(int j = 1; j <= i; j++) {
c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
if(c[i][j] > mo) c[i][j] -= mo;
}
}
for(int i = 1; i <= n; i++) {
int x0, y0, xx, yy;
scanf("%d%d%d%d", &x0, &y0, &xx, &yy);
x[i * 2 - 1] = x0;
x[i * 2] = xx;
y[i * 2 - 1] = y0;
y[i * 2] = yy;
sq[i] = {x0, y0, xx, yy};
}
sort(x + 1, x + n * 2 + 1);
int nx = unique(x + 1, x + n * 2 + 1) - x - 1;
sort(y + 1, y + n * 2 + 1);
int ny = unique(y + 1, y + n * 2 + 1) - y - 1;
for(int i = 1; i <= n; i++) {
sq[i].x0 = lower_bound(x + 1, x + nx + 1, sq[i].x0) - x;
sq[i].x = lower_bound(x + 1, x + nx + 1, sq[i].x) - x;
sq[i].y0 = lower_bound(y + 1, y + ny + 1, sq[i].y0) - y;
sq[i].y = lower_bound(y + 1, y + ny + 1, sq[i].y) - y;
d[sq[i].x0][sq[i].y0]++;
d[sq[i].x0][sq[i].y]--;
d[sq[i].x][sq[i].y0]--;
d[sq[i].x][sq[i].y]++;
}
for(int i = 1; i < nx; i++) {
for(int j = 1; j < ny; j++) {
ss[j] = ss[j - 1] + d[i][j];
d[i][j] += d[i - 1][j] + ss[j - 1];
s[d[i][j]] += (x[i + 1] - x[i]) * (y[j + 1] - y[j]) % mo;
s[d[i][j]] %= mo;
}
}
for(int k = 1; k <= n; k++) {
ll ans = 0;
for(int i = 1; i <= n; i++) {
ans += s[i] * (c[n][k] - c[n - i][k] + mo) % mo;
ans %= mo;
}
ans = ans * inv(c[n][k]) % mo;
printf("%lld\n", ans);
}
以下为扫描线代码,xht赛时非常顺利地过了,不像我)<-但我认为问题出在我而不是差分上
int n;
scanf("%d", &n);
c[0][0] = 1;
for(int i = 1; i <= n; i++) {
c[i][0] = 1;
for(int j = 1; j <= i; j++) {
c[i][j] = c[i - 1][j] + c[i - 1][j - 1];
if(c[i][j] > mo) c[i][j] -= mo;
}
}
for(int i = 1; i <= n; i++) {
int x0, y0, xx, yy;
scanf("%d%d%d%d", &x0, &y0, &xx, &yy);
x[i * 2 - 1] = x0;
x[i * 2] = xx;
a[i * 2 - 1] = {x0, xx, y0, 1};
a[i * 2] = {x0, xx, yy, -1};
}
n *= 2;
sort(x + 1, x + n + 1);
int sz = unique(x + 1, x + n + 1) - x - 1;
for(int i = 1; i <= n; i++) {
a[i].l = lower_bound(x + 1, x + sz + 1, a[i].l) - x;
a[i].r = lower_bound(x + 1, x + sz + 1, a[i].r) - x;
}
sort(a + 1, a + n + 1, cmp);
for(int i = 1; i < n; i++) {
for(int j = a[i].l; j < a[i].r; j++) {
cov[j] += a[i].k;
}
for(int j = 1; j < sz; j++) {
ans[cov[j]] += (x[j + 1] - x[j]) * (a[i + 1].h - a[i].h) % mo;
ans[cov[j]] %= mo;
}
}
n /= 2;
for(int k = 1; k <= n; k++) {
ll s = 0;
for(int i = 1; i <= n; i++) {
s += ans[i] * (c[n][k] - c[n - i][k] + mo) % mo;
s %= mo;
}
s = s * inv(c[n][k]) % mo;
printf("%lld\n", s);
}