T1
给定一个长度为 n n n 的数组 a a a,需要选择若干段不相交子串,使得每个子串的和相等。求最多能选取多少个子串。
n ≤ 1000 n\le 1000 n≤1000, 1 ≤ a i ≤ 20 1\le a_i\le20 1≤ai≤20。
发现 ∑ a \sum a ∑a 很小可以直接枚举每个子串的和,然后扫一遍即可。赛时用的队列贪心的选取,差点因为选取了相交子串而 20 p t s 20\mathrm{pts} 20pts。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1005;
int a[maxn], n, sum;
int q[maxn], l, r, ans, tot, now;
int main() {
scanf("%d", &n);
for (int i = 1; i <= n; i ++) scanf("%d", &a[i]), sum += a[i];
for (int s = 1; s <= sum; s ++) {
now = 0, l = 1, r = tot = 0;
for (int i = 1; i <= n; i ++) {
for (now += (q[++ r] = a[i]); l <= r && now > s; )
now -= q[l ++];
if (now == s) tot ++, l = r + 1, now = 0;
} ans = max(ans, tot);
} printf("%d\n", ans);
return 0;
}
T2
有 n n n 个套装,第 i i i 个套装有 c i c_i ci 个物品,这些物品有参数 w i w_i wi,选取这个套装代价为 v i v_i vi;还有 m m m 个任务,完成第 i i i 个任务需要 w w w 至少为 W i W_i Wi 的物品 C i C_i Ci 个,完成后获得 V i V_i Vi 的收益。求最大收益减代价。
n , m ≤ 2000 n,m\le 2000 n,m≤2000, 1 ≤ c i , C i ≤ 50 1\le c_i,C_i\le 50 1≤ci,Ci≤50, 1 ≤ w i , W i , v i , V i ≤ 1 0 9 1\le w_i,W_i,v_i,V_i\le 10^9 1≤wi,Wi,vi,Vi≤109。
套路:把买和卖放到一起做,对于买的情况价值取负。考虑 dp,令 f ( i ) f(i) f(i) 表示手上有 i i i 个物品时的最大收益。转移时滚动数组即可。对于 w w w 的限制,所有信息按照 w w w 从大到小做就行了。但是排序的时候没有考虑 w w w 与 W W W 相等的情况,显然应当先考虑买后考虑卖,痛失 16 p t s 16\mathrm{pts} 16pts。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 2005, maxc = 1e5 + 5;
int n, m;
struct Object {
int c, w, v;
inline bool operator<(const Object &oth) const {
return w == oth.w ? v < oth.v : w > oth.w;
}
} a[maxn << 1];
ll f[maxc]; const ll inf = 1e18;
inline int read() {
char c = getchar();
for (; c < '0' || c > '9'; c = getchar());
int s = 0;
for (; c >= '0' && c <= '9'; c = getchar())
s = (s << 1) + (s << 3) + (c ^ 48);
return s;
}
int main() {
n = read(), m = read(); int tmp = 0;
for (int i = 1; i <= n; i ++) a[i].c = read(), a[i].w = read(), a[i].v = -read(), tmp += a[i].c;
for (int i = n + 1; i <= n + m; i ++) a[i].c = read(), a[i].w = read(), a[i].v = read();
n += m, m = tmp; sort(a + 1, a + n + 1); ll ans = 0;
for (int i = 1; i <= m; i ++) f[i] = -inf;
for (int i = 1; i <= n; i ++) {
int c = a[i].c, v = a[i].v;
if (v < 0) for (int j = m; j >= c; j --) {
if (f[j - c] == -inf) continue;
ans = max(ans, f[j] = max(f[j], f[j - c] + v));
// cout << j << ' ' << f[j] << '\n';
} else for (int j = 0; j + c <= m; j ++) {
if (f[j + c] == -inf) continue;
ans = max(ans, f[j] = max(f[j], f[j + c] + v));
// cout << j << ' ' << f[j] << '\n';
}
} printf("%lld\n", ans);
return 0;
}
T3
给定一张 n n n 个点 m m m 条边的无向图,每条边有值 0 0 0 或 1 1 1。给定 d d d,对于所有起点为 1 1 1、长度为 d d d 的路径(可以走重复的点和边),将路径上经过的边的值依次连接可以组成若干 d d d 位的二进制数。求这些二进制数的种类数。
n ≤ 90 n\le 90 n≤90, m ≤ n ( n − 1 ) m\le n(n-1) m≤n(n−1), 1 ≤ d ≤ 20 1\le d\le 20 1≤d≤20。有重边和自环。
看起来就很像有关
2
d
2^d
2d 的复杂度的做法,但是没有想出来,最后写了一个
30
p
t
s
30\mathrm{pts}
30pts 的暴搜,如果和 cout << (1 << d)
拼起来大概能拿
40
p
t
s
40\mathrm{pts}
40pts 左右,但是忘了这么做。正解是考虑 meet-in-the-middle,告诉算法后也就会做了。记
f
(
i
,
j
,
k
)
f(i,j,k)
f(i,j,k) 和
g
(
i
,
j
,
k
)
g(i,j,k)
g(i,j,k) 表示到了
i
i
i 点已经走了
j
j
j 步是否可以有
k
k
k 这个二进制,其中
j
≤
10
j\le 10
j≤10。前者起点为
1
1
1 后者以所有点为起点,然后
O
(
n
2
d
)
O(n2^d)
O(n2d) 合并一下即可。快把这个算法忘了。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 95, maxm = maxn * maxn;
namespace Graph {
struct Edge { int to, nxt, v; } e[maxm << 1];
int head[maxn], ecnt;
void addEdge(int u, int v, int c) {
e[++ ecnt] = Edge { v, head[u], c }, head[u] = ecnt;
}
} using namespace Graph;
const int maxd = 15;
bool f[maxn][maxd][1 << 10], g[maxn][maxd][1 << 10]; int n, m, d;
void dfs1(int u, int now, int step) {
if (f[u][step][now]) return;
f[u][step][now] = 1;
if (step == (d >> 1)) return ;
for (int i = head[u]; i; i = e[i].nxt)
dfs1(e[i].to, now << 1 | e[i].v, step + 1);
}
void dfs2(int u, int now, int step) {
if (g[u][step][now]) return;
g[u][step][now] = 1;
if (step == (d >> 1) + (d & 1)) return ;
for (int i = head[u]; i; i = e[i].nxt)
dfs2(e[i].to, now | (e[i].v << step), step + 1);
}
int main() {
scanf("%d %d %d", &n, &m, &d);
for (int i = 1, u, v, c; i <= m; i ++)
scanf("%d %d %d", &u, &v, &c), addEdge(u, v, c), addEdge(v, u, c);
dfs1(1, 0, 0); for (int i = 1; i <= n; i ++) dfs2(i, 0, 0);
int ans = 0; for (int s = 0; s < (1 << d); s ++) {
// cout << "s = " << s << '\n';
int t0 = (s >> ((d >> 1) + (d & 1)));
int t1 = (s - (t0 << ((d >> 1) + (d & 1)))), tmp = 0;
for (int i = 1; i <= n; i ++) {
// cout << i << ' ' << t0 << ' ' << f[i][t0] << ',' << t1 << ' ' << g[i][t1] << '\n';
tmp |= (f[i][d >> 1][t0] & g[i][(d >> 1) + (d & 1)][t1]);
}
// cout << "result: " << tmp << '\n';
ans += tmp;
} printf("%d\n", ans);
return 0;
}
T4
维护一个长度为 n n n 的序列 a a a,支持两种操作 Q Q Q 次:
- 给定区间 [ l , r ] [l,r] [l,r],对于所有 i ∈ [ l , r ] i\in [l,r] i∈[l,r], a i ← a i 2 a_i\gets a_i^2 ai←ai2;
- 给定区间 [ l , r ] [l,r] [l,r],求 ∑ i = l r a i \sum_{i=l}^r a_i ∑i=lrai。
1 ≤ n , q ≤ 2 × 1 0 5 1\le n,q\le 2\times 10^5 1≤n,q≤2×105,结果对 p = 998244353 p=998244353 p=998244353 取模。
原题有一档
O
(
n
Q
)
O(nQ)
O(nQ) 的小数据分和两档特殊性质,分别是单点修改和单点询问。单点修改拿个树状数组维护一下即可。单点询问用一个差分树状数组维护每个点被平方了几次,假设
x
x
x 被平方了
k
k
k 次,那么它现在的值就是:
x
2
k
m
o
d
p
x^{2^k}\bmod p
x2kmodp
用一下扩展欧拉定理进行降幂,原式变为:
x
(
2
k
m
o
d
φ
(
p
)
)
+
φ
(
p
)
m
o
d
p
x^{(2^k\bmod\varphi(p))+\varphi(p)}\bmod p
x(2kmodφ(p))+φ(p)modp
做两次快速幂即可。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
bool MemoryST;
const int N = 2e5 + 5, P = 998244353;
int n, q, a[N];
struct Queries { int op, l, r; } Q[N];
namespace Subtask0 {
void work() {
for (int qq = 1; qq <= q; qq ++) {
int op = Q[qq].op, l = Q[qq].l, r = Q[qq].r;
if (op == 1)
for (int i = l; i <= r; i ++)
a[i] = 1ll * a[i] * a[i] % P;
else {
int sum = 0;
for (int i = l; i <= r; i ++)
sum = (sum + a[i]) % P;
printf("%d\n", sum);
}
}
}
}
namespace Subtask1 {
const int maxn = 2e5 + 5;
namespace BIT {
int b[maxn];
#define lowbit(x) ((x) & (-(x)))
void add(int i, int x) {
for (; i <= n; i += lowbit(i))
b[i] = (b[i] + x) % P;
}
int query(int i) {
int ans = 0;
for (; i; i -= lowbit(i)) ans = (b[i] + ans) % P;
return ans;
}
} using namespace BIT;
void work() {
for (int i = 1; i <= n; i ++) add(i, a[i]);
for (int qq = 1; qq <= q; qq ++) {
int op = Q[qq].op, l = Q[qq].l, r = Q[qq].r;
if (op == 1) add(l, 1ll * a[l] * ((a[l] + P - 1) % P) % P), a[l] = 1ll * a[l] * a[l] % P;
else printf("%d\n", (query(r) - query(l - 1) + P) % P);
}
}
}
namespace Subtask2 {
const int maxn = 2e5 + 5, P0 = 998244352;
namespace BIT {
int b[maxn];
#define lowbit(x) ((x) & (-(x)))
void add(int i, int x) {
for (; i <= n; i += lowbit(i))
b[i] += x;
}
int query(int i) {
int ans = 0;
for (; i; i -= lowbit(i)) ans += b[i];
return ans;
}
} using namespace BIT;
int qp(int x, int y, int p) {
int res = 1;
for (; y; y >>= 1, x = (1ll * x * x) % p)
if (y & 1) res = 1ll * res * x % p;
return res;
}
void work() {
// for (int i = 1; i <= n; i ++) add(i, a[i]);
for (int qq = 1; qq <= q; qq ++) {
int op = Q[qq].op, l = Q[qq].l, r = Q[qq].r;
if (op == 1)
add(l, 1), add(r + 1, -1);
else {
int tot = query(l), tmp = qp(2, tot, P0) + P0;
printf("%d\n", qp(a[l], tmp, P));
}
}
}
}
bool MemoryED;
int main() {
scanf("%d %d", &n, &q);
for (int i = 1; i <= n; i ++) scanf("%d", &a[i]);
bool stag1 = 1, stag2 = 1;
for (int i = 1; i <= q; i ++) {
scanf("%d %d %d", &Q[i].op, &Q[i].l, &Q[i].r);
if (Q[i].op == 1 && Q[i].l != Q[i].r) stag1 = 0;
if (Q[i].op == 2 && Q[i].l != Q[i].r) stag2 = 0;
} if (stag1) return Subtask1::work(), 0;
if (stag2) return Subtask2::work(), 0;
Subtask0::work();
cerr << fixed << setprecision(6) << (&MemoryED - &MemoryST) / 1024.0 << "KB\n";
cerr << 1e3 * clock() / CLOCKS_PER_SEC << "ms\n";
return 0;
}
正解是观察到 φ ( p ) = 998244352 = 2 23 × 119 \varphi(p)=998244352=2^{23}\times 119 φ(p)=998244352=223×119 会出现循环节,然后就可以用线段树维护了,循环节长度可以打表发现是 24 24 24。时间复杂度就是 O ( n log n ) O(n\log n) O(nlogn) 带 24 24 24 的常数,用来处理循环节。线段树上每个点记录一下是否进入循环节和循环节的数组即可。