T1
模拟即可。
const int maxn = 2e5 + 5;
pair<int, int> que[maxn << 1];
int head = 1, tail = 0;
int main() {
int Q; scanf("%d", &Q);
while (Q --) {
int op; scanf("%d", &op);
if (op == 1) {
int x, c; scanf("%d %d", &x, &c);
que[++ tail] = { x, c };
} else {
int x; scanf("%d", &x);
long long sum = 0;
while (x > 0) {
auto [num, tot] = que[head];
if (x >= tot) { sum += 1ll * num * tot, x -= tot; head ++; }
else sum += 1ll * x * num, que[head] = { num, tot - x }, x = 0;
} printf("%lld\n", sum);
}
}
return 0;
}
T2
考虑对第 i i i 个点计算 L ∈ [ 1 , i ] , R = i L\in[1,i],R=i L∈[1,i],R=i 的答案,找到 x , y x,y x,y 中出现最晚的比较靠前的位置 k k k(找不到则暂时无解), L ∈ [ 1 , k ] L\in[1,k] L∈[1,k] 都是合法的。若出现 > x >x >x 或 < y <y <y 的数则之后的答案将这个数的后一位视为 1 1 1。
const int maxn = 2e5 + 5;
int a[maxn], x, y, n;
int mx[maxn], mn[maxn];
int main() {
scanf("%d %d %d", &n, &x, &y); mx[0] = mn[0] = -1;
for (int i = 1; i <= n; i ++) {
scanf("%d", &a[i]);
if (a[i] > x || a[i] < y) { mx[i] = mn[i] = -1; continue; }
mx[i] = (a[i] == x ? i : mx[i - 1]), mn[i] = (a[i] == y ? i : mn[i - 1]);
} long long ans = 0;
for (int i = 1, lst = 0; i <= n; i ++) {
if (a[i] > x || a[i] < y) lst = i;
if (mn[i] == -1 || mx[i] == -1) continue;
ans += min(mn[i], mx[i]) - lst;
} printf("%lld", ans);
return 0;
}
T3
如果有数量相等的球直接换即可。否则需要构造。设初始
x
,
y
x,y
x,y 数量的球被消去,令
x
≤
y
x\le y
x≤y,剩下一种颜色数量为
z
z
z;那么考虑将
x
,
z
x,z
x,z 不断换成
y
y
y 使得
x
=
y
x=y
x=y 后进行消除。设要换
k
k
k 次,那么有
x
−
k
=
y
+
2
k
∴
k
=
x
−
y
3
x-k=y+2k\\ \therefore k=\cfrac{x-y}{3}
x−k=y+2k∴k=3x−y
若
x
−
y
≡
0
(
m
o
d
3
)
x-y\equiv0\pmod3
x−y≡0(mod3) 成立,那么最终就需要
x
x
x 次,否则此时无解。如果一开始
z
z
z 不够换的,那么可以先将
x
,
y
x,y
x,y 换成
z
z
z 再开始上述步骤,程序里没有表现出来。一开始以为
z
z
z 不够大就无解挂了三发。
#define int long long
const int inf = 1e18;
int check(int x, int y, int z) {
if (z > x) return inf;
if (x % 3 != z % 3) return inf;
return x;
}
signed main() {
int T, r, g, b;
for (scanf("%lld", &T); T --; ) {
int ans = inf;
scanf("%lld %lld %lld", &r, &g, &b);
if (r == g || r == b || g == b) {
if (r == g || r == b) ans = min(ans, r);
if (g == b) ans = min(ans, g);
}
int S[6] = { check(r, g, b), check(r, b, g), check(b, r, g), check(b, g, r), check(g, r, b), check(g, b, r) };
for (int i = 0; i < 6; i ++) ans = min(ans, S[i]);
if (ans == inf) ans = -1;
printf("%lld\n", ans);
}
return 0;
}
T4
一开始想了个以满足前 i i i 个数字为状态的 dp,发现会重,糖丸了。
考虑一个等价转换:对于数字
i
i
i,我们将它所在的两张(或者一张,此时为自环)卡连一条边。对于建出的图,边代表数字,点代表卡牌,
1
,
2
,
⋯
,
n
1,2,\cdots,n
1,2,⋯,n 至少出现一次转化为每个端点至少被一条边覆盖。由于每个点连出去两条边(正反各一个数字),最终的图一定由若干个不相交的环组成,环与环之间独立,只需计算每个环的答案然后乘起来即可。注意到环的答案只与环的大小有关,设大小为
m
m
m 的环答案为
f
(
m
)
f(m)
f(m),手模一下:
f
(
1
)
=
1
,
f
(
2
)
=
3
,
f
(
3
)
=
4
,
f
(
4
)
=
7
,
⋯
⇒
f
(
m
)
=
f
(
m
−
1
)
+
f
(
m
−
2
)
f(1)=1,f(2)=3,f(3)=4,f(4)=7,\cdots \Rightarrow f(m)=f(m-1)+f(m-2)
f(1)=1,f(2)=3,f(3)=4,f(4)=7,⋯⇒f(m)=f(m−1)+f(m−2)
环的大小用并查集维护即可。
const int maxn = 2e5 + 5, P = 998244353;
int n, p[maxn];
namespace DSU {
int fa[maxn], siz[maxn];
int find(int u) { return fa[u] == u ? u : fa[u] = find(fa[u]); }
void Union(int u, int v) {
u = find(u), v = find(v);
if (u == v) return ;
siz[u] += siz[v], fa[v] = u;
}
void init1() { for (int i = 1; i <= n; i ++) fa[i] = i, siz[i] = 1; }
} using namespace DSU;
namespace Fab {
int f[maxn];
void init2() {
f[1] = 1, f[2] = 3;
for (int i = 3; i <= n; i ++) f[i] = (1ll * f[i - 1] + 1ll * f[i - 2]) % P;
}
} using namespace Fab;
int main() {
scanf("%d", &n); init1(), init2();
for (int i = 1, x; i <= n; i ++)
scanf("%d", &x), p[x] = i;
for (int i = 1, x; i <= n; i ++)
scanf("%d", &x), Union(p[x], i);
int ans = 1;
for (int i = 1; i <= n; i ++)
if (fa[i] == i) ans = (1ll * ans * f[siz[i]]) % P;
return printf("%d", ans), 0;
}
T5
考虑贪心,先不考虑 m m m 的限制,我们找到一段后缀,使得后缀和除以后缀长度最大,然后把 S S S 均匀分配到这段后缀的每个位置上即可。 m m m 的限制可能会使找到一段后缀后 S S S 分配不完,那么我们把这段后缀抠掉接着找即可。复杂度 O ( n 2 ) O(n^2) O(n2)。
一开始找后缀时找的是 [ max { a i } , l s t ] [\max\{a_i\},lst] [max{ai},lst],没看数据范围导致不敢想 O ( n 2 ) O(n^2) O(n2) 的暴力找。
const int maxn = 5005;
int n, m, a[maxn]; double s;
#define ll long long
ll sum[maxn]; double x[maxn];
int main() {
scanf("%d %d %lf", &n, &m, &s);
for (int i = 1; i <= n; i ++)
scanf("%d", &a[i]);
for (int i = n; i >= 1; i --) sum[i] = sum[i + 1] + a[i];
for (int lst = n; s > 0 && lst > 0; ) {
int pos; double mx = -1;
for (int i = 1; i <= lst; i ++)
if (1.0 * (sum[i] - sum[lst + 1]) / (lst - i + 1) > mx)
pos = i, mx = 1.0 * (sum[i] - sum[lst + 1]) / (lst - i + 1);
double ss = s;
for (int len = lst - pos + 1; lst >= pos; lst --)
x[lst] = min(1.0 * s / len, 1.0 * m), ss -= x[lst];
s = ss;
} double ans = 0;
for (int i = 1; i <= n; i ++)
ans += x[i] * a[i];
return printf("%.8lf", ans), 0;
}
T6
赛时并没想到这个非常套路的 dp 状态设计。
先考虑一下对于一段序列
[
l
,
r
]
[l,r]
[l,r] 能否将
[
l
+
1
,
r
−
1
]
[l+1,r-1]
[l+1,r−1] 全部删除如何判断。显然如果有相邻的两个数相等就不行,其次如果
[
l
,
r
]
[l,r]
[l,r] 中只有两种值那也不行(2 1 2
等情况除外,特判即可),因为删掉一个元素后立即会出现两个相邻且相等的数。考虑不满足这些条件的序列是否可以被删成只剩左右端点。比如说
a
=
{
1
,
2
,
3
,
2
,
1
}
a=\{1,2,3,2,1\}
a={1,2,3,2,1},我们发现
3
3
3 若被删去那么会出现相邻的两个
2
2
2,考虑以这个
3
3
3 为端点删掉两个
2
2
2 之后再把
3
3
3 删了即可。于是我们得到了
[
l
,
r
]
[l,r]
[l,r] 能把
[
l
+
1
,
r
+
1
]
[l+1,r+1]
[l+1,r+1] 删掉的条件:
- [ l , r ] [l,r] [l,r] 中值的种类数 ≥ 3 \ge3 ≥3;
- [ l , r ] [l,r] [l,r] 不存在相邻两个相等的数。
对于计算方案数,我们考虑 dp,设 f ( i ) f(i) f(i) 表示只考虑 [ 1 , i ] [1,i] [1,i] 且 i i i 为右端点的答案。显然可以啥都不删,即 f ( i ) ← f ( i ) + f ( i − 1 ) f(i)\gets f(i)+f(i-1) f(i)←f(i)+f(i−1)。考虑找到一段后缀 [ j , i ] [j,i] [j,i] 满足 [ j + 1 , i − 1 ] [j+1,i-1] [j+1,i−1] 能被删完,那么答案就可以是 f ( j ) f(j) f(j) 的方案数和考虑 [ j + 1 , i − 1 ] [j+1,i-1] [j+1,i−1] 删不删,不删的情况在 f ( i − 1 ) f(i-1) f(i−1) 中,所以直接 f ( i ) ← f ( i ) + f ( j ) f(i)\gets f(i)+f(j) f(i)←f(i)+f(j)。
若直接枚举 j j j 判断 [ i , j ] [i,j] [i,j] 中间是否可删是 O ( n 2 ) O(n^2) O(n2) 的。在扫的过程中,如果发现了一对 a i = a i − 1 a_i=a_{i-1} ai=ai−1,那么 f ( i − 1 ) f(i-1) f(i−1) 及以前的答案都取不到了;值的种类数随着区间长度的缩小而减少。于是我们考虑双指针,维护两个指针 l , r l,r l,r,对于 j ∈ [ l , r ) j\in[l,r) j∈[l,r) 表示 [ i , j ] [i,j] [i,j] 中 [ i + 1 , j − 1 ] [i+1,j-1] [i+1,j−1] 是可删除的。每次扫到 a i a_i ai 时考虑移动 l l l,然后考虑移动 r r r,用一个桶记录当前 [ r , i ] [r,i] [r,i] 值的种类数, r r r 一直右移直到不合法或 r = i − 2 r=i-2 r=i−2(此时再短就不可能删了),如果 l < r l<r l<r 那么 ∀ j ∈ [ l , r ) \forall j\in[l,r) ∀j∈[l,r), f ( j ) f(j) f(j) 的答案都能取,维护一个前缀和即可。时间复杂度 O ( n ) O(n) O(n)。
const int maxn = 2e5 + 5;
int a[maxn], f[maxn], sum[maxn], n;
int table[maxn], tot;
void add(int val) { tot += ((++ table[val]) == 1); }
void del(int val) { tot -= ((-- table[val]) == 0); }
const int P = 998244353;
int main() {
scanf("%d", &n), f[0] = 1;
for (int i = 1, l = 1, r = 1; i <= n; i ++) {
scanf("%d", &a[i]); f[i] = f[i - 1];
if (a[i] == a[i - 1]) l = i;
if (i > 2 && a[i] != a[i - 1] && a[i - 1] != a[i - 2]) f[i] = (1ll * f[i] + 1ll * f[i - 2]) % P;
for (add(a[i]); r < i - 2 && tot >= 3; del(a[r ++]));
if (l <= r) f[i] = (1ll * f[i] + 1ll * (1ll * sum[r - 1] - 1ll * sum[l - 1] + P) % P) % P;
sum[i] = (1ll * sum[i - 1] + 1ll * f[i]) % P;
} printf("%d", f[n]);
return 0;
}