题意:有一个人从 0 0 0 要走到 n n n,在 x = i , i ∈ [ 0 , n − 1 ] x=i,i \in [0,n-1] x=i,i∈[0,n−1] 处可以花费 c i c_i ci 的代价,有 p i p_i pi 的概率走到 x = i + 1 x=i+1 x=i+1;然后给定长度为 n − 1 n-1 n−1 的权值数组 { w i } \{w_i\} {wi} 表示往下掉 i i i 步的概率,即在 x = i x=i x=i 处有 w j ∑ k = 1 i w k \displaystyle \dfrac{w_j}{\sum_{k=1}^i w_k} ∑k=1iwkwj 的概率落到 i − j i-j i−j 处。问期望走到 n n n 所花费的代价。 n ≤ 1 × 1 0 5 n \leq 1\times 10^5 n≤1×105。
解法:设 F i F_i Fi 表示从 0 0 0 走到 i i i 的期望代价。考虑 x = i x=i x=i 处转移到 F i + 1 F_{i+1} Fi+1 的情况:
设
W
W
W 为已经确认掉下去了,再爬回来的期望代价,则有:
F
i
+
1
=
∑
j
=
0
+
∞
p
i
(
1
−
p
i
)
j
(
(
1
+
j
)
c
i
+
j
W
)
=
c
i
p
i
+
p
i
c
i
W
∑
j
=
0
+
∞
(
1
−
p
i
)
j
j
=
c
i
p
i
+
1
−
p
i
p
i
c
i
W
考虑
W
W
W 的构成。对于掉到
i
−
j
i-j
i−j,其概率为
w
j
∑
k
=
1
i
w
k
\dfrac{w_j}{\sum_{k=1}^i w_k}
∑k=1iwkwj,再从
i
−
j
i-j
i−j 回到
i
i
i 的期望代价为
f
i
−
f
j
f_i-f_j
fi−fj。因而
W
=
1
∑
k
=
1
i
w
k
∑
j
=
1
i
w
j
(
F
i
−
F
i
−
j
−
1
)
=
F
i
−
1
∑
k
=
1
i
w
k
∑
j
=
1
i
w
j
F
i
−
j
−
1
\displaystyle W=\dfrac{1}{\sum_{k=1}^i w_k}\sum_{j=1}^iw_j(F_i-F_{i-j-1})=F_i-\dfrac{1}{\sum_{k=1}^i w_k}\sum_{j=1}^{i}w_{j}F_{i-j-1}
W=∑k=1iwk1j=1∑iwj(Fi−Fi−j−1)=Fi−∑k=1iwk1j=1∑iwjFi−j−1。
因而最终化简的式子为 F i + 1 = c i + F i p i + 1 − p i p i ∑ k = 1 i w k ∑ j = 1 i w j F i − j − 1 \displaystyle F_{i+1}=\dfrac{c_i+F_i}{p_i}+\dfrac{1-p_i}{p_i\sum_{k=1}^i w_k}\sum_{j=1}^i w_jF_{i-j-1} Fi+1=pici+Fi+pi∑k=1iwk1−pij=1∑iwjFi−j−1。这是一个典型的半在线卷积形式,因而使用分治 NTT 可以 O ( n log 2 n ) \mathcal O(n \log^2n) O(nlog2n) 通过本题。
题意:有 n n n 个技能,每个技能使用完成之后有 t i t_i ti 秒的冷却时间,以及一个长度为 l i l_i li 的序列 { d i } \{d_i\} {di} 表示这个技能接下来的 l i l_i li 秒中,每一秒这个技能对敌人的伤害。给定敌人的血量 H H H,问在每个技能至多使用一次的情况下,至少使用多长时间才能将敌人击杀,或者报告不能击杀。 n ≤ 18 n \leq 18 n≤18, ∑ l i ≤ 3 × 1 0 5 \sum l_i \leq 3\times 10^5 ∑li≤3×105, H ≤ 1 × 1 0 18 H \leq 1\times 10^{18} H≤1×1018。
解法:显然伤害对时间是单调递增的,且为了尽快击杀,技能是连续释放的。同时由于 n n n 非常小,考虑二分答案套状态压缩。二分击杀时间 T T T,设 f S f_{S} fS 表示技能使用情况为 S S S,在 t t t 时刻前所能打出的最大伤害。记 t S t_S tS 为使用 S S S 这些技能所用时间,则枚举未使用的技能 i i i,其开始使用时间为 t s t_s ts, i i i 技能所能打出的伤害秒数为 j = min ( T − t S , l i ) j=\min(T-t_S,l_i) j=min(T−tS,li),则伤害利用 { d i } \{d_i\} {di} 的前缀和可以 O ( 1 ) \mathcal O(1) O(1) 的计算。因而 f S ∪ i ← max i ∉ S { f S + ∑ k = 0 j d i , k } \displaystyle f_{S \cup i} \leftarrow \max_{i \not \in S}\left \{f_S+\sum_{k=0}^jd_{i,k}\right \} fS∪i←i∈Smax{fS+k=0∑jdi,k}。总时间复杂度 O ( n 2 n log ∑ l i ) \mathcal O(n2^n \log \sum l_i) O(n2nlog∑li)。
此题使用二分的核心在于:如果时间不知道,那么每个技能所能打出伤害的时间是不可知的,因而非常不好转移。本题卡常。
#include
using namespace std;
const long long inf = 0x3f3f3f3f3f3f3f3fll;
const int N = 1 << 18, M = 100000;
int oldtime[N];
long long f[N], t[20];
long long d[20][M + 5], h;
int len[20], n;
bool check(int val)
{
for (int i = 0; i < 1 << n;i++)
f[i] = 0;
for (int i = 1; i < 1 << n; i++)
for (int j = 0; j < n; j++)
if (i & (1 << j))
{
int old = i ^ (1 << j);
if (oldtime[old] > val)
continue;
int hitlen = min(len[j] - 1, val - oldtime[old]);
f[i] = max(f[i], f[old] + d[j][hitlen]);
}
for (int i = 0; i < 1 << n; i++)
if (f[i] >= h)
return true;
return false;
}
int main()
{
int test;
scanf("%d", &test);
while(test--)
{
scanf("%d%lld", &n, &h);
for (int i = 0, x; i < n;i++)
{
scanf("%d%d", &t[i], &len[i]);
for (int j = 0; j < len[i];j++)
{
scanf("%lld", &d[i][j]);
if(j)
d[i][j] += d[i][j - 1];
}
}
long long all = 0;
for (int i = 0; i < n;i++)
all += d[i][len[i] - 1];
if(all < h)
{
printf("-1\n");
continue;
}
for (int i = 0; i < 1 << n;i++)
{
oldtime[i] = 0;
for (int j = 0; j < n;j++)
if (i & (1 << j))
oldtime[i] += t[j];
}
int left = 0, right = 5e6, ans = -1;
while (left <= right)
{
int mid = (left + right) >> 1;
if (check(mid))
{
ans = mid;
right = mid - 1;
}
else
left = mid + 1;
}
printf("%lld\n", ans);
}
return 0;
}
题意:对于一串用空格分隔的若干字符串,将其首字母大写。
解法:直接模拟即可。
题意:给定一个 n n n 个点 m m m 条边的联通无向图,和两个长度为 m m m 的序列 { a i } \{a_i\} {ai} 和 { b i } \{b_i\} {bi},求出:对于 k ∈ [ 0 , m ] k \in [0,m] k∈[0,m],Alice 从 m m m 条边中选取 k k k 条边 { e 1 , e 2 , ⋯ , e k } \{e_1,e_2,\cdots,e_k\} {e1,e2,⋯,ek},它们的权值为 { a e 1 , a e 2 , ⋯ , a e k } \{a_{e_1},a_{e_2},\cdots,a_{e_k}\} {ae1,ae2,⋯,aek},对于剩下的边 { e k + 1 , e k + 2 , ⋯ , e m } \{e_{k+1},e_{k+2},\cdots,e_m\} {ek+1,ek+2,⋯,em},其权值为 { b e k + 1 , b e k + 2 , ⋯ , b e m } \{b_{e_{k+1}},b_{e_{k+2}},\cdots,b_{e_m}\} {bek+1,bek+2,⋯,bem},Bob 对于这张带权图求最小生成树,Alice 希望生成树权值尽可能大,问这颗树的最大权值。 n ≤ 9 n \leq 9 n≤9, m ≤ 30 m \leq 30 m≤30。
解法:考虑将每条边拆成两个:
(
u
i
,
v
i
,
a
i
)
(u_i,v_i,a_i)
(ui,vi,ai) 和
(
u
i
,
v
i
,
b
i
)
(u_i,v_i,b_i)
(ui,vi,bi),若
a
i
≥
b
i
a_i \geq b_i
ai≥bi 则存入
(
u
i
,
v
i
,
b
i
,
−
1
)
,
(
u
i
,
v
i
,
a
i
,
0
)
(u_i,v_i,b_i,-1),(u_i,v_i,a_i,0)
(ui,vi,bi,−1),(ui,vi,ai,0);若
a
i
<
b
i
a_i
首先依照 Kruskal 对所有边排序,依照权值为第一关键字,集合变化量为第二关键字, 0 0 0 边后置。依次对所有的边进行遍历,考虑枚举到的边有以下三种情况:
考虑所有的图连通情况,即是将 n n n 个数分到若干个集合,集合非空。这一方案数为 ∑ i = 1 n { n i } = ϖ n \displaystyle \sum_{i=1}^n {n \brace i}=\varpi_n i=1∑n{in}=ϖn,注意到 ϖ 9 = 21147 \varpi_9=21147 ϖ9=21147,因而设置 dp 数组: f i , j , k f_{i,j,k} fi,j,k 表示对于前 i i i 条边,连通状态为 j j j,且 a a a 集合选取了 k k k 条边的最大权值,每次的 dp 转移都是 O ( 1 ) \mathcal O(1) O(1),使用滚动数组优化空间,总的时间复杂度为 O ( m 2 ϖ n ) \mathcal O(m^2 \varpi_n) O(m2ϖn)。
题意:平面上有 n n n 个点 ( x i , y i ) (x_i,y_i) (xi,yi),有以下 q q q 次、两种操作:
n , q ≤ 1 × 1 0 5 n,q \leq 1\times 10^5 n,q≤1×105,点随机给出。
解法:对于随机给出的 n n n 个点,其期望的凸包点数为 O ( log n ) O(\log n) O(logn) 个的。因而使用线段树暴力维护区间 [ l , r ] [l,r] [l,r] 的凸包点集,对于子区间的合并可以每次暴力使用 Andrew 算法重新计算,然后使用随机情况下 O ( n ) O(n) O(n) 的最小圆覆盖算法,总的时间复杂度为 O ( q log 2 n log log n ) \mathcal O(q\log^2 n \log \log n) O(qlog2nloglogn)。或者可以归并排序的写法,减去 Andrew 算法的排序,减少这一 log log n \log \log n loglogn。本题卡常。
题意:有 n n n 个包裹,每个包裹取件时间为 [ l i , r i ] [l_i,r_i] [li,ri]。可以在任意一天取任意多次包裹,每次至多取 k k k 个包裹,问最少取多少次包裹。 1 ≤ k ≤ n ≤ 1 × 1 0 6 1 \leq k \leq n \leq 1\times 10^6 1≤k≤n≤1×106。
解法:考虑贪心,显然需要在最晚的一天取尽可能多的包裹。
首先对于所有的区间,按左端点 l l l 排序。同时维护一个堆,表示现在可以取的包裹,堆内按 r r r 排序。按右端点先后枚举区间,每次取出还未取走的最小的 r r r 的那个包裹,将还未塞入堆中的所有满足 l i ≤ r l_i \leq r li≤r 的包裹全部塞进堆中,在堆内找到 r r r 最小的 k k k 个包裹(或取空)取走,并且必须保证当前这个枚举的包裹被取走。
总时间复杂度为 O ( n log n ) \mathcal O(n \log n) O(nlogn)。
#include
using namespace std;
struct node
{
int l, r;
int id;
bool operator<(const node &b)const
{
return r > b.r;
}
};
int main()
{
int t, n, k;
scanf("%d", &t);
while(t--)
{
scanf("%d%d", &n, &k);
vector<node> que(n);
for (int i = 0; i < n;i++)
{
scanf("%d%d", &que[i].l, &que[i].r);
que[i].id = i;
}
vector<int> vis(n, 0);
vector<node> quer = que;
auto cmpl = [&](node a, node b)
{
return a.l < b.l;
};
auto cmpr = [&](node a, node b)
{
return a.r < b.r;
};
sort(que.begin(), que.end(), cmpr);
sort(quer.begin(), quer.end(), cmpl);
priority_queue<node> q;
int ans = 0, place = 0;
for (int i = 0; i < n;i++)
{
if(vis[que[i].id])
continue;
while (place < n && quer[place].l <= que[i].r)
q.push(quer[place++]);
int cnt = 0;
while(!q.empty() && cnt + (!vis[q.top().id]) <= k)
{
cnt += (!vis[q.top().id]);
vis[q.top().id] = 1;
q.pop();
}
ans++;
if(!vis[que[i].id])
i--;
}
if(!q.empty())
ans += (q.size() + k - 1) / k;
printf("%d\n", ans);
}
return 0;
}
题意:给定平面上 n n n 个点 ( x i , y i ) (x_i,y_i) (xi,yi) 和权值 w i w_i wi,任意一个点 ( x , y ) (x,y) (x,y) 到它的代价为 min ( w i , ∣ x − x i ∣ + ∣ y − y i ∣ ) \min(w_i,|x-x_i|+|y-y_i|) min(wi,∣x−xi∣+∣y−yi∣)。 q q q 次询问,每次给出一个点 ( x , y ) (x,y) (x,y),问到这 n n n 个点的最大权值。 n , q ≤ 5 × 1 0 5 n,q \leq 5\times 10^5 n,q≤5×105。
解法:单纯考虑 ∣ x − x i ∣ + ∣ y − y i ∣ |x-x_i|+|y-y_i| ∣x−xi∣+∣y−yi∣,拆开绝对值可以分成 4 4 4 个情况—— x + y + max ( − x i − y i ) x+y+\max(-x_i-y_i) x+y+max(−xi−yi), x − y + max ( − x i + y i ) x-y+\max(-x _i+y_i) x−y+max(−xi+yi), − x + y + max ( x i − y i ) -x+y+\max(x_i-y_i) −x+y+max(xi−yi), − x − y + max ( x i + y i ) -x-y+\max(x_i+y_i) −x−y+max(xi+yi),依次分开维护 max ( − x i − y i ) , max ( x i − y i ) , max ( − x i , y i ) , max ( x i + y i ) \max(-x_i-y_i),\max(x_i-y_i),\max(-x_i,y_i),\max(x_i+y_i) max(−xi−yi),max(xi−yi),max(−xi,yi),max(xi+yi) 即可。
考虑引入 w w w——按 w w w 排序从小到大排序,再加入 ∣ x − x i ∣ + ∣ y − y i ∣ |x-x_i|+|y-y_i| ∣x−xi∣+∣y−yi∣ 的限制,即是在 w w w 这一参数单调递增的情况下维护这四个权值的后缀最大值。预处理后可以做到 O ( 1 ) \mathcal O(1) O(1) 查询曼哈顿距离最大值。
考虑单调性:后缀最大值单调递减, w w w 单调递增,对这两个取 min \min min 后必然有一单峰。因而使用二分——对于一个分界点 k k k,首先查询后缀 k k k 的曼哈顿距离最大值 d k d_k dk,和权值最小值 w k w_k wk,若 d k ≤ w k d_k \leq w_k dk≤wk 则表示 w w w 不对会曼哈顿距离的最大值造成影响,分界点可以向前;否则 w k w_k wk 就会影响答案,需要继续向后移动 k k k。利用这一隐藏的单调性可以做到单次询问 O ( log n ) \mathcal O(\log n) O(logn)。
#include
using namespace std;
struct node
{
long long x, y;
long long w;
bool operator<(const node &b)const
{
return w < b.w;
}
};
int main()
{
int t, n, q;
long long x, y;
scanf("%d", &t);
while(t--)
{
scanf("%d%d", &n, &q);
vector<node> que(n);
for (int i = 0; i < n;i++)
scanf("%lld%lld%lld", &que[i].x, &que[i].y, &que[i].w);
sort(que.begin(), que.end());
vector<long long> suf1(n), suf2(n), suf3(n), suf4(n);
for (int i = n - 1; i >= 0;i--)
{
suf1[i] = que[i].x + que[i].y;
suf2[i] = que[i].x - que[i].y;
suf3[i] = -que[i].x + que[i].y;
suf4[i] = -que[i].x - que[i].y;
if (i + 1 < n)
{
suf1[i] = max(suf1[i], suf1[i + 1]);
suf2[i] = max(suf2[i], suf2[i + 1]);
suf3[i] = max(suf3[i], suf3[i + 1]);
suf4[i] = max(suf4[i], suf4[i + 1]);
}
}
auto cal = [&](long long x, long long y, int id)
{
return max({suf1[id] - x - y, suf2[id] - x + y, suf3[id] + x - y, suf4[id] + x + y});
};
while(q--)
{
scanf("%lld%lld", &x, &y);
int left = 0, right = n - 1;
long long ans = 0;
while (left <= right)
{
int mid = (left + right) >> 1;
ans = max(ans, min(cal(x, y, mid), que[mid].w));
if (cal(x, y, mid) <= que[mid].w)
right = mid - 1;
else
left = mid + 1;
}
printf("%lld\n", ans);
}
}
return 0;
}
题意:给定两个长度为 n n n 的排列 A , B A,B A,B,进行归并,问能够归并成长度为 2 n 2n 2n 的序列 C C C,如果能输出方案数。 n ≤ 1 × 1 0 6 n \leq 1\times 10^6 n≤1×106。
解法:考虑一个暴力 dp: f i , j f_{i,j} fi,j 表示 A A A 序列取出 i i i 个, B B B 序列取出 j j j 个的方案数。则对于 C C C 序列已经覆盖了 i + j i+j i+j 个数。其转移为 f i , j + 1 ← f i , j [ C i + j + 1 = B j + 1 ] f_{i,j+1} \leftarrow f_{i,j}[C_{i+j+1}=B_{j+1}] fi,j+1←fi,j[Ci+j+1=Bj+1], f i + 1 , j ← f i , j [ C i + j + 1 = A i + 1 ] f_{i+1,j} \leftarrow f_{i,j}[C_{i+j+1}=A_{i+1}] fi+1,j←fi,j[Ci+j+1=Ai+1]。但是这个暴力其实复杂度是 O ( 4 n ) \mathcal O(4n) O(4n) 的——因为满足 C i + j + 1 = B j + 1 C_{i+j+1}=B_{j+1} Ci+j+1=Bj+1 和 C i + j + 1 = A i + 1 C_{i+j+1}=A_{i+1} Ci+j+1=Ai+1 的只有 4 n 4n 4n 次。因而总状态数也只有 4 n 4n 4n,可以使用记忆化搜索通过。
#include
using namespace std;
const long long mod = 998244353;
int main()
{
int t, n;
scanf("%d", &t);
while(t--)
{
scanf("%d", &n);
unordered_map<long long, long long> f;
vector<int> p(n + 1), q(n + 1), s(2 * n + 1);
for (int i = 1; i <= n;i++)
scanf("%d", &p[i]);
for (int i = 1; i <= n;i++)
scanf("%d", &q[i]);
vector<int> times(n + 1, 0);
for (int i = 1; i <= 2 * n;i++)
{
scanf("%d", &s[i]);
times[s[i]]++;
}
bool flag = 0;
for (int i = 1; i <= n;i++)
if (times[i] != 2)
{
flag = 1;
break;
}
if(flag)
{
printf("0\n");
continue;
}
vector<int> memo(n + 1, -1);
function<int(int, int, int)> dfs = [&](int x, int y, int z)
{
if (z == 2 * n + 1)
return 1;
if (x <= n && y <= n && p[x] == q[y])
{
if (p[x] == s[z])
{
if(memo[p[x]] == -1)
return memo[p[x]] = (dfs(x + 1, y, z + 1) + dfs(x, y + 1, z + 1)) % mod;
else
return memo[p[x]];
}
else
return 0;
}
if (x <= n && p[x] == s[z])
return dfs(x + 1, y, z + 1);
if (y <= n && q[y] == s[z])
return dfs(x, y + 1, z + 1);
return 0;
};
printf("%d\n", dfs(1, 1, 1));
}
return 0;
}