AtCoder Beginner Contest 138
AtCoder Beginner Contest 138
https://atcoder.jp/contests/abc138
今天做的,f非常有意思,状态设计很巧妙,是限定范围内的数位dp;e还不知道为什么错,感觉思路没问题。
A - Red or Not
#include <bits/stdc++.h>
using namespace std;
int main () {
int n;
string s;
cin >> n >> s;
if (n >= 3200) cout << s;
else cout << "red";
}
B - Resistors in Parallel
#include <bits/stdc++.h>
using namespace std;
int main () {
int n;
string s;
cin >> n >> s;
if (n >= 3200) cout << s;
else cout << "red";
}
C - Alchemist
#include <bits/stdc++.h>
using namespace std;
int main () {
int n;
cin >> n;
vector <int> v;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
v.push_back (x);
}
sort (v.begin (), v.end ());
double sum = v[0];
for (int i = 1; i < v.size (); i++) {
sum += v[i], sum /= 2;
}
cout << sum;
}
D - Ki
树上差分。
建立dfs序之后根据子树size进行差分。
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 4e5 + 5;
int h[N], e[N], ne[N], idx;
int pos[N], sz[N], tmp[N], ans[N];
int n, q, d[N], cnt;
void add (int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
void dfs (int u, int fa) {
d[++cnt] = u, pos[u] = cnt;
for (int i = h[u]; ~i; i = ne[i]) {
int v = e[i];
if (v == fa) continue;
dfs (v, u);
sz[u] += sz[v];
}
}
signed main () {
memset (h, -1, sizeof h);
cin >> n >> q;
for (int i = 1; i < n; i++) {
int a, b;
cin >> a >> b;
add (a, b), add (b, a);
}
for (int i = 1; i <= n; i++) sz[i] = 1;
dfs (1, -1);
// for (int i = 1; i <= n; i++) cout << d[i] << ' ';cout << endl;
// for (int i = 1; i <= n; i++) cout << pos[i] << ' ';cout << endl;
// for (int i = 1; i <= n; i++) cout << sz[i] << ' ';cout << endl;
while (q --) {
int x, y;
cin >> x >> y;
int l = pos[x], r = pos[x] + sz[x];
// cout << l << ' ' << r << endl;
tmp[l] += y, tmp[r] -= y;
}
for (int i = 1; i <= n; i++) {
tmp[i] += tmp[i-1];
ans[d[i]] = tmp[i];
}
for (int i = 1; i <= n; i++) cout << ans[i] << ' ';
}
//因为回溯,所以需要反向边
E - Strings of Impurity
二分做法不知道为啥错(疑似超时?)
可以用二维数组预处理进行记录,类似dp的想法
记录匹配到模式串 \(s\) 的第 \(i\) 位时,字母 \(j\) 第一次出现的位置,就可以 \(O(1)\) 查找下标了
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int p[N][30]; //第i位字母j第一次出现的位置
int main () {
string s, t;
cin >> s >> t;
int n = s.size (), m = t.size ();
s = ' ' + s, t = ' ' + t;
for (int i = 0; i < 26; i++) p[n][i] = -1;
p[n][s[n] - 'a'] = n;
for (int i = n - 1; i; i--) {
for (int j = 0; j < 26; j++) p[i][j] = p[i+1][j]; //递推
p[i][s[i] - 'a'] = i;
}
//match
int cnt = 0, lst = 1;
for (int i = 1; i <= m; i++) {
if (p[1][t[i] - 'a'] == -1) {
cout << -1;
return 0;
}
if (p[lst][t[i] - 'a'] == -1) cnt ++, lst = 1;
lst = p[lst][t[i] - 'a'] + 1;
if (lst > n) cnt ++, lst = 1;
}
cout << 1ll * cnt * n + lst - 1 << endl;
}
F - Coincidence
限定范围内的数位dp。
这题可以加到视频里面
异或性质推导
证 \(y\mathrm{\,mod\,}x=y-x(x\leq y<2x\))
\[y \mathrm{\,mod\,} x = y-\lfloor {\frac yx} \rfloor x\rightarrow
(y-\lfloor {\frac yx} \rfloor x)-(y-x)=x(1-\lfloor {\frac yx} \rfloor)
\rightarrow \lfloor {\frac yx} \rfloor=1\rightarrow x\leq y<2x
\]
来自群主%%%
证 \(y\bigoplus x=y+x-2(y\&x)\)
感性理解:异或是不进位加法,正常加法减去两倍进位就是异或。
证明:列真值表(?) 不懂数学证明www
由上述两条性质可得,\(x=x\&y\)
dp状态设计
注意状态设计
注意上下界:这个上下界就是框定了他的枚举范围。
如果没达到下界,就可以把当前枚举下界的该位设为0,否则就是 \(l\) 的该位;
如果没达到上界,就可以把当前枚举上界的该位设为1,否则就是 \(r\) 的该位;
同时,这个属性具有单调倾向,一旦没达到某个界,之后都不会再达到了。
比如,当前如果是dp[i][0][0],再往后都一直是0,0...
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 70, mod = 1e9 + 7;
ll f[N][2][2]; //枚举到第i位, 1/0:是否达到下界L/上界R
ll l, r, L[N], R[N], ans;
ll dp (int u, int x1, int x2) {
if (u <= 0) return 1; //边界值!!防止数组越界
if (f[u][x1][x2]) return f[u][x1][x2];
ll l1 = 0, r1 = 1;
if (x1 == 1) l1 = L[u];
if (x2 == 1) r1 = R[u];
for (ll x = l1; x <= r1; x++) {
for (ll y = x; y <= r1; y++) {
ll xx1 = 0, xx2 = 0;
if (x1 && x == l1) xx1 = 1;
if (x2 && y == r1) xx2 = 1;
(f[u][x1][x2] += dp (u - 1, xx1, xx2)) %= mod;
}
}
return f[u][x1][x2];
}
int main () {
cin >> l >> r;
int kl = 0, kr = 0;
while (l > 0) {
L[++kl] = (l & 1);
l /= 2;
}
while (r > 0) {
R[++kr] = (r & 1);
r /= 2;
}
for (int i = kl; i <= kr; i++) {
(ans += dp (i - 1, (i == kl), (i == kr))) %= mod;
}
cout << ans << endl;
}