A Link with Bracket Sequence II
题意:给定长度为 n n n 的括号序列,有 m m m 种括号,该括号序列中有些位置为空。问利用这 m m m 种括号填充括号序列中的空位置,将该序列变成合法括号序列的方案数。 n ≤ 500 n \leq 500 n≤500, m ≤ 1 × 1 0 9 m \leq 1\times 10^9 m≤1×109。
解法:
n
≤
500
n \leq 500
n≤500 第一反应是区间 dp。但是朴素的
f
i
,
j
f_{i,j}
fi,j 表示区间
[
i
,
j
]
[i,j]
[i,j] 变成一个合法括号序列方案数会导致重复:例如 ()()()
在合并的时候会出现 ()()|()
和 ()|()()
这两种分法,但是其实是一个方案。其原因在于一次合并进来了多个同层的括号序列。其改进方法为:
f
i
,
j
f_{i,j}
fi,j 表示区间
[
i
,
j
]
[i,j]
[i,j] 变为合法的括号序列方案数,
g
i
,
j
g_{i,j}
gi,j 表示区间
[
i
,
j
]
[i,j]
[i,j] 由合法区间
[
i
+
1
,
j
−
1
]
[i+1,j-1]
[i+1,j−1] 再直接套上外层的合法括号对组成的方案数。则转移方法为:
g
i
,
j
=
{
f
i
+
1
,
j
−
1
,
a
i
和
a
j
可以完全确定一对合法的括号
m
f
i
+
1
,
j
−
1
,
a
i
=
a
j
=
0
f
i
,
j
=
∑
k
=
i
+
1
j
−
1
f
i
,
k
g
k
+
1
,
j
g_{i,j}= \begin{cases} f_{i+1,j-1},a_i\ \text{和}\ a_j\ \text{可以完全确定一对合法的括号}\\ mf_{i+1,j-1},a_i=a_j=0 \end{cases} \\ f_{i,j}=\sum_{k=i+1}^{j-1}f_{i,k}g_{k+1,j}
gi,j={fi+1,j−1,ai 和 aj 可以完全确定一对合法的括号mfi+1,j−1,ai=aj=0fi,j=k=i+1∑j−1fi,kgk+1,j
这样转移之后,上面提到的重复问题就会得到解决,因为每次都是合并进来一个同层的括号序列,而不是多个同层的括号序列。总复杂度
O
(
n
3
)
\mathcal O(n^3)
O(n3)。
#include <bits/stdc++.h>
using namespace std;
const long long mod = 1000000007;
const int N = 500;
long long f[N + 5][N + 5], g[N + 5][N + 5];
int a[N + 5];
int main()
{
int t, n, m;
scanf("%d", &t);
while(t--)
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n;i++)
scanf("%d", &a[i]);
for (int i = 1; i <= n;i++)
f[i][i - 1] = g[i][i - 1] = 1;
for (int len = 2; len <= n;len += 2)
for (int i = 1; i + len - 1 <= n;i++)
{
int j = i + len - 1;
if(a[i] == 0 && a[j] == 0)
f[i][j] = g[i][j] = f[i + 1][j - 1] * m % mod;
else if((a[i] > 0 && a[j] == 0) || (a[i] == 0 && a[j] < 0) || (a[i] > 0 && a[j] < 0 && a[i] + a[j] == 0))
f[i][j] = g[i][j] = f[i + 1][j - 1];
else
f[i][j] = g[i][j] = 0;
for (int k = i + 1; k < j; k += 2)
f[i][j] = (f[i][j] + f[i][k] * g[k + 1][j] % mod) % mod;
}
printf("%lld\n", f[1][n]);
for (int i = 1; i <= n; i++)
for (int j = 0; j <= n; j++)
f[i][j] = g[i][j] = 0;
}
return 0;
}
B Link with Running
题意: n n n 个点 m m m 条边的带权有向图,图上每个点有非负距离和非负价值,问从 1 1 1 号点走到 n n n 号点的最近距离,以及在最近距离下能获得的最大价值。 n ≤ 1 × 1 0 5 n\leq 1\times 10^5 n≤1×105, m ≤ 3 × 1 0 5 m \leq 3\times 10^5 m≤3×105。
解法:不可以直接多重费用的最短路因为有 0 0 0 边。同理最短路图上跑 Dijkstra 也是错误的,错误样例如下:
3 3
1 3 0 2
1 2 0 1
2 3 0 2
正解为先用 Dijkstra 计算出最短路图,但是由于可能出现 0 0 0 环因而这个图不一定是有向无环图 DAG,需要通过缩点使图变成 DAG,然后再利用拓扑排序进行 DAG 上 dp。总时间复杂度为 O ( n + n log n ) \mathcal O(n+n\log n) O(n+nlogn)。
#include <bits/stdc++.h>
using namespace std;
const long long inf = 0x3f3f3f3f3f3f3f3fll;
const int N = 200000, M = 300000;
struct line
{
int from;
int to;
long long v;
long long gain;
int next;
};
struct line que[M + 5];
struct edge
{
int from;
int to;
long long gain;
edge(int _from, int _to, long long _gain)
{
from = _from;
to = _to;
gain = _gain;
}
};
int cnt, headers[N + 5];
void add(int from, int to, long long v, long long gain)
{
cnt++;
que[cnt].from = from;
que[cnt].to = to;
que[cnt].v = v;
que[cnt].gain = gain;
que[cnt].next = headers[from];
headers[from] = cnt;
}
vector<edge> graph[N + 5], newgraph[N + 5];
struct node
{
int id;
long long dis;
node(int _id, long long _dis)
{
id = _id;
dis = _dis;
}
bool operator<(const node &b)const
{
return dis > b.dis;
}
};
int n, m;
long long dis[N + 5];
bool vis[N + 5];
void dijkstra(int s, int t)
{
for (int i = 1; i <= n;i++)
{
dis[i] = inf;
vis[i] = 0;
}
dis[s] = 0;
priority_queue<node> q;
q.emplace(s, 0);
while (!q.empty())
{
auto tp = q.top();
q.pop();
if (vis[tp.id])
continue;
vis[tp.id] = 1;
for (int i = headers[tp.id]; i; i = que[i].next)
if(dis[que[i].to] > dis[tp.id] + que[i].v)
{
dis[que[i].to] = dis[tp.id] + que[i].v;
q.emplace(que[i].to, dis[que[i].to]);
}
}
}
int dfn[N + 5], low[N + 5], ind, col[N + 5], tot;
stack<int> s;
void tarjan(int u)
{
dfn[u] = low[u] = ++ind;
s.push(u);
vis[u] = 1;
for (auto i : graph[u])
if(!dfn[i.to])
{
tarjan(i.to);
low[u] = min(low[u], low[i.to]);
}
else if (vis[i.to])
low[u] = min(dfn[i.to], low[u]);
if (low[u] == dfn[u])
{
tot++;
while (s.top() != u)
{
col[s.top()] = tot;
vis[s.top()] = 0;
s.pop();
}
col[u] = tot;
vis[u] = 0;
s.pop();
}
}
int deg[N + 5];
long long f[N + 5];
void topo(int s)
{
queue<int> q;
for (int i = 1; i <= tot;i++)
if(!deg[i])
q.push(i);
while(!q.empty())
{
int tp = q.front();
q.pop();
for (auto i : newgraph[tp])
{
deg[i.to]--;
f[i.to] = max(f[i.to], f[tp] + i.gain);
if(!deg[i.to])
q.push(i.to);
}
}
}
int main()
{
int t;
scanf("%d", &t);
while(t--)
{
scanf("%d%d", &n, &m);
for (int i = 1, u, v; i <= m; i++)
{
long long dis, gain;
scanf("%d%d%lld%lld", &u, &v, &dis, &gain);
add(u, v, dis, gain);
}
dijkstra(1, n);
for (int i = 1; i <= n; i++)
for (int j = headers[i]; j; j = que[j].next)
if(dis[que[j].to] == dis[i] + que[j].v)
graph[i].emplace_back(i, que[j].to, que[j].gain);
for (int i = 1; i <= n;i++)
vis[i] = 0;
printf("%lld ", dis[n]);
for (int i = 1; i <= n;i++)
if(!dfn[i])
tarjan(i);
for (int i = 1; i <= n;i++)
for (auto j : graph[i])
if (col[i] != col[j.to])
newgraph[col[i]].emplace_back(col[i], col[j.to], j.gain);
for (int i = 1; i <= tot;i++)
for (auto j : newgraph[i])
deg[j.to]++;
topo(col[1]);
printf("%lld\n", f[col[n]]);
for (int i = 1; i <= n; i++)
{
graph[i].clear();
newgraph[i].clear();
headers[i] = dfn[i] = low[i] = col[i] = deg[i] = f[i] = 0;
}
while(!s.empty())
s.pop();
cnt = ind = tot = 0;
}
return 0;
}
C Magic
题意: n n n 个城市,第 i i i 城市需要 p i p_i pi 的魔法值。给定范围 k k k,魔法值可以通过在城市里面堆放魔法原料来获得,对于第 i i i 个城市投放了一个魔法原料,可以让 [ max ( 1 , i − k + 1 ) , min ( n , i + k − 1 ) ] [\max(1,i-k+1),\min(n,i+k-1)] [max(1,i−k+1),min(n,i+k−1)] 的所有城市都获得一个魔法值。此外有 q q q 条限制 ( l i , r i , b i ) (l_i,r_i,b_i) (li,ri,bi),表示区间 [ l i , r i ] [l_i,r_i] [li,ri] 的城市中堆放的魔法原料总和不超过 b i b_i bi。问是否有合法方案,若有输出最少需要堆放多少魔法原料。 n , q ≤ 1 × 1 0 4 n,q \leq 1\times 10^4 n,q≤1×104。
解法:若考虑魔法原料堆放的前缀和 s i s_i si,则原问题转化为差分约束问题:
- 对于每个城市 i i i 的魔法值需求 p i p_i pi,需要满足 s min ( n , i + k − 1 ) − s max ( 1 , i − k ) ≥ p i s_{\min(n,i+k-1)}-s_{\max(1,i-k)} \geq p_i smin(n,i+k−1)−smax(1,i−k)≥pi——只有 [ max ( 1 , i − k + 1 ) , min ( n , i + k − 1 ) ] [\max(1,i-k+1),\min(n,i+k-1)] [max(1,i−k+1),min(n,i+k−1)] 的范围内堆放魔法原料才能让 i i i 城市获得魔法值。
- 对于 q q q 条限制,需要满足 s r i − s l i − 1 ≤ b i s_{r_i}-s_{l_i-1} \leq b_i sri−sli−1≤bi。
- 每个城市堆放的魔法原料个数非负, s i − s i − 1 ≥ 0 s_i-s_{i-1} \geq 0 si−si−1≥0。
因而直接使用差分约束的 spfa 算法找负环即可。
#include <bits/stdc++.h>
using namespace std;
const int inf = 0x3f3f3f3f;
const int N = 10000, M = 50000;
struct line
{
int from;
int to;
int v;
int next;
};
struct line que[M + 5];
int cnt, headers[N + 5], dis[N + 5], times[N + 5], n;
bool vis[N + 5];
void add(int from, int to, int v)
{
cnt++;
que[cnt].from = from;
que[cnt].to = to;
que[cnt].v = v;
que[cnt].next = headers[from];
headers[from] = cnt;
}
bool spfa(int s)
{
for (int i = 1; i <= n;i++)
dis[i] = -inf;
queue<int> q;
q.push(s);
vis[s] = 1;
while(!q.empty())
{
int tp = q.front();
q.pop();
if (times[tp] >= n)
return 0;
times[tp]++;
vis[tp] = 0;
for (int i = headers[tp];i;i=que[i].next)
if(dis[que[i].to]<dis[tp]+que[i].v)
{
dis[que[i].to] = dis[tp] + que[i].v;
if(!vis[que[i].to])
{
vis[que[i].to] = 1;
q.push(que[i].to);
}
}
}
return 1;
}
int main()
{
int t, k, q;
scanf("%d", &t);
while(t--)
{
scanf("%d%d", &n, &k);
for (int i = 1, x; i <= n;i++)
{
add(i - 1, i, 0);
scanf("%d", &x);
int l = max(1, i - k + 1), r = min(n, i + k - 1);
add(l - 1, r, x);
}
scanf("%d", &q);
for (int i = 1, l, r, b; i <= q;i++)
{
scanf("%d%d%d", &l, &r, &b);
add(r, l - 1, -b);
}
dis[0] = 0;
if(!spfa(0))
printf("-1\n");
else
printf("%d\n", dis[n]);
for (int i = 0; i <= n;i++)
vis[i] = headers[i] = times[i] = 0;
cnt = 0;
}
return 0;
}
E Link with Level Editor II
题意: n n n 层图,每层 m m m 个节点,每层图给定。在第 i i i 层每次可以选择走一步或者停留,然后上升到 i + 1 i+1 i+1 层,所处点编号不变。问一个最长的连续子段 [ l , r ] [l,r] [l,r],使得从第 l l l 层的 1 1 1 号节点出发,到 m m m 号节点的方案数不超过 k k k。 n ≤ 5 × 1 0 3 n \leq 5\times 10^3 n≤5×103, m ≤ 20 m \leq 20 m≤20, k ≤ 1 × 1 0 9 k \leq 1\times 10^9 k≤1×109。
解法:由于 m m m 非常小可以考虑使用 m × m m \times m m×m 的转移矩阵来辅助转移。首先预处理出每一层的转移矩阵 M i M_i Mi,考虑区间 [ l , r ] [l,r] [l,r] 从 1 1 1 到 m m m 的方案数,为 [ 1 0 0 ⋯ 0 ] ∏ i = l r M i \displaystyle \begin{bmatrix}1&0&0 &\cdots &0\end{bmatrix}\prod_{i=l}^r M_i [100⋯0]i=l∏rMi 的第 m m m 项。
当 l l l 从小到大的变化时,容易发现 r r r 是单调递增的,因而可以使用双指针。枚举 l l l 移动 r r r,维护初始的列向量,每次等效于向已有的矩阵乘以 M r M_r Mr,单次乘法复杂度为 O ( m 2 ) \mathcal O(m^2) O(m2)。之所以不维护初始的矩阵,是因为利用结合律可以加快朴素维护 m × m m\times m m×m 矩阵的 O ( m 3 ) \mathcal O(m^3) O(m3) 的矩阵乘法。每次 l l l 向右移动一位时,使用线段树计算出 [ l + 1 , r ] [l+1,r] [l+1,r] 的乘积。这样的复杂度为 O ( n m 2 log n ) \mathcal O(nm^2 \log n) O(nm2logn)。
#include <bits/stdc++.h>
using namespace std;
long long lim;
struct matrix
{
int n, m;
vector<vector<long long>> p;
matrix() = default;
matrix(int _n, int _m)
{
n = _n;
m = _m;
this->p.resize(_n);
for (auto &i : p)
i.resize(_m);
}
void set(int _n, int _m)
{
n = _n;
m = _m;
p.resize(_n);
for (auto &i : p)
i.resize(_m);
if (n == m)
for (int i = 0; i < n;i++)
p[i][i] = 1;
}
matrix operator*(matrix x)
{
int a = this->n, b = x.m, c = this->m;
matrix ans(a, b);
for (int i = 0; i < a;i++)
for (int j = 0; j < b;j++)
for (int k = 0; k < c;k++)
{
ans.p[i][j] += this->p[i][k] * x.p[k][j];
if (ans.p[i][j] > lim)
{
ans.p[i][j] = lim + 1;
break;
}
}
return ans;
}
bool check()
{
return p[0][m - 1] <= lim;
}
};
class segment_tree
{
vector<matrix> t;
int n, m;
matrix res;
void query(int place, int left, int right, int start, int end)
{
if (start <= left && right <= end)
{
res = res * t[place];
return;
}
int mid = (left + right) >> 1;
matrix ans(m, m);
if (start <= mid)
query(place << 1, left, mid, start, end);
if (end > mid)
query(place << 1 | 1, mid + 1, right, start, end);
return;
};
public:
segment_tree(int n, int m, vector<matrix> &que)
{
this->n = n;
this->m = m;
t.resize(4 * n + 5);
function<void(int, int, int)> build = [&](int place, int left, int right)
{
if (left == right)
{
t[place] = que[left];
return;
}
int mid = (left + right) >> 1;
build(place << 1, left, mid);
build(place << 1 | 1, mid + 1, right);
t[place] = t[place << 1] * t[place << 1 | 1];
};
build(1, 1, n);
}
matrix query(int left, int right)
{
res.set(1, m);
for (int i = 0; i < m; i++)
res.p[0][i] = 0;
res.p[0][0] = 1;
query(1, 1, n, left, right);
return res;
}
};
int main()
{
int caset, n, m;
scanf("%d", &caset);
while(caset--)
{
scanf("%d%d%lld", &n, &m, &lim);
vector<matrix> layer(n + 1);
for (int i = 1, l, x, y; i <= n;i++)
{
layer[i].set(m, m);
scanf("%d", &l);
if (l)
while (l--)
{
scanf("%d%d", &x, &y);
layer[i].p[x - 1][y - 1] = 1;
}
}
segment_tree t(n, m, layer);
int ans = 1;
matrix base = layer[1];
for (int l = 1, r = 1; l <= n; l++, base = t.query(l, r))
{
while (r < n)
{
matrix temp = base * layer[r + 1];
if(temp.check())
{
r++;
base = temp;
}
else
break;
}
ans = max(ans, r - l + 1);
if (r == n)
break;
}
printf("%d\n", ans);
}
return 0;
}
还可以使用对顶栈。枚举 r r r 然后 l l l 向右推进,维护一个栈,第 i i i 项存储 ∏ j = i r M j \displaystyle \prod_{j=i}^r M_j j=i∏rMj,栈顶为编号最小的元素。对于 r r r 一次右移,只需要给栈中所有元素乘以 M r + 1 M_{r+1} Mr+1 即可。这样的复杂度为 O ( n m 3 ) \mathcal O(nm^3) O(nm3)。
G Climb Stairs
题意:给定 n + 1 n+1 n+1 层楼 { 0 , 1 , 2 , ⋯ , n } \{0,1,2,\cdots,n\} {0,1,2,⋯,n},从一楼开始第 i i i 层有个怪物血量为 a i a_i ai,只有当攻击值大于等于 a i a_i ai 时才能击杀怪物,同时击杀血量为 a i a_i ai 的怪物可以使攻击值增加 a i a_i ai。初始攻击值为 a 0 a_0 a0,每次可以向上跳跃 1 , 2 , ⋯ , k 1,2,\cdots, k 1,2,⋯,k 层或者向下走一楼,前提是能击杀这个怪物,问能否恰好到访每层一次,击杀每一层的怪物然后走到 n n n 楼。 1 ≤ k ≤ n ≤ 1 × 1 0 5 1 \leq k \leq n \leq 1\times 10^5 1≤k≤n≤1×105。
解法:显然路径形如 0 , ( x 1 , x 1 − 1 , ⋯ , 1 ) , ( x 2 , x 2 − 1 , ⋯ , x 1 + 1 ) , ( x 3 , x 3 − 1 , ⋯ , x 2 + 1 ) , ⋯ , ( n − 1 , n − 2 , ⋯ , x k + 1 ) , n 0,(x_1,x_1-1,\cdots,1),(x_2,x_2-1,\cdots,x_1+1),(x_3,x_3-1,\cdots,x_2+1),\cdots,(n-1,n-2,\cdots,x_k+1),n 0,(x1,x1−1,⋯,1),(x2,x2−1,⋯,x1+1),(x3,x3−1,⋯,x2+1),⋯,(n−1,n−2,⋯,xk+1),n。而 x i x_i xi 越小越好。考虑目前在 x i + 1 x_i+1 xi+1 楼,目前最高到达层数为 x i + 1 x_{i+1} xi+1 楼,如何找到 x i + 2 x_{i+2} xi+2。
首先考虑 x i + 1 + 1 x_{i+1}+1 xi+1+1 楼能否跳上去。目前的攻击值已经达到了 ∑ j = 0 x i a j \displaystyle \sum_{j=0}^{x_{i}}a_j j=0∑xiaj,考虑二分答案,找到最近需要到 y y y 楼才能击杀 x i + 1 + 1 x_{i+1}+1 xi+1+1。显然楼层越高越能击杀怪物因为击杀到 x i + 1 x_{i}+1 xi+1 必然是从 y y y 楼一层层往下走的。找到这个 y y y 之后就需要检查从 x i + 1 + 2 x_{i+1}+2 xi+1+2 到 y y y 楼是不是都能顺利从 y y y 楼走下来,那么这就是刚刚的子问题,因而对于枚举的每一层,用同样的二分法可以找到满足最小的 y y y 使得从 y y y 开始一路往下可以一直杀到 x i + 1 + 1 x_{i+1}+1 xi+1+1。这时候这个 y y y 就是 x i + 2 x_{i+2} xi+2,检查能否从 x i + 1 x_{i}+1 xi+1 跳到 x i + 2 x_{i+2} xi+2 即可。时间复杂度 O ( n log n ) \mathcal O(n \log n) O(nlogn)。
#include <bits/stdc++.h>
using namespace std;
int main()
{
int t, n, k;
scanf("%d", &t);
while(t--)
{
long long base;
scanf("%d%lld%d", &n, &base, &k);
vector<long long> pre(n + 1), a(n + 1);
for (int i = 1; i <= n;i++)
{
scanf("%lld", &a[i]);
pre[i] = pre[i - 1] + a[i];
}
auto jump = [&](long long base, int target)
{
int left = target, right = n, ans = n + 1;
while (left <= right)
{
int mid = (left + right) >> 1;
if(base + pre[mid] - pre[target] >= a[target])
{
ans = mid;
right = mid - 1;
}
else
left = mid + 1;
}
return ans;
};
bool flag = 1;
int now = 0, up = 0;
while(up < n)
{
int maximum = up + 1, low = up;
while(low < maximum)
{
low++;
int min_jump = jump(base, low);
if (min_jump == n + 1)
{
flag = 0;
break;
}
maximum = max(maximum, min_jump);
}
if(!flag)
break;
if(maximum > now + k)
{
flag = 0;
break;
}
base += pre[maximum] - pre[up];
now = up + 1;
up = maximum;
}
if(flag)
printf("YES\n");
else
printf("NO\n");
}
return 0;
}
I Fall with Full Star
题意:给定 n n n 个关卡,初始有一个能力值 s 0 s_0 s0,打完第 i i i 关可以使得能力值增加 d i d_i di,若增加完成后能力值大于等于 s i s_i si,则认为第 i i i 关满星通过。在每关最多只能打一次的情况下,至多可以满星通过多少关。 n ≤ 1 × 1 0 5 n \leq 1\times 10^5 n≤1×105, ∣ d i ∣ , ∣ s i ∣ ≤ 1 × 1 0 9 |d_i|,|s_i| \leq 1\times 10^9 ∣di∣,∣si∣≤1×109。
解法:首先一个最显然的贪心,将所有关卡按照 d i d_i di 的正负分成两类, d i d_i di 为正的一定先打, d i d_i di 为负的一定后打。
考虑 d i d_i di 为非负的贪心。对于第 i i i 关,为了让它满星通关,在打这关之前至少需要 s i − d i s_i-d_i si−di 的能力值。由于现在全正,能力值单调不减,因而首先按照 s i − d i s_i-d_i si−di 从小到大进行排序。但是这样还可能会导致有些关卡的 s i − d i s_i-d_i si−di 过高无法达到,提前吃掉 d i d_i di 放弃本关,而让前面本来不能通关的通关反而更优。因而,如果打到第 i i i 关,那么在排序中比它下标小的一定是都打过的。
维护一个堆 E E E 来记录可能牺牲(不满星通关),但是通过一定的顺序可以不牺牲的关卡, n o w \rm now now 为明确牺牲,绝对不可能通过调整顺序满星通过关卡的 d i d_i di 和。且这部分必然放弃关卡一定是放在最前面打。对于 E E E 中关卡,按照 d i d_i di 从大到小排序。考虑从后( s i − d i s_i-d_i si−di 大的关卡)往前的放弃关卡。考虑第 i i i 关能否利用明确牺牲的所有的关卡和前 i − 1 i-1 i−1 的关卡获得的 d i d_i di 增量达成满星通关,分两种情况:
- 如果可以利用现有条件,那么不需要在 i i i 这里多牺牲关卡,即满星通过关卡增加。在这种情况下,在其余关卡都不变, d i > d j d_i > d_j di>dj 时,是可以将 i i i 牺牲掉换 j ( j > i ) j(j>i) j(j>i) 的不牺牲的。这是由于在判定 j j j 是否可以不牺牲的时候,其实是已经打了 i i i 了。能入堆的条件为可以满星通过 j j j,则此时如果选择牺牲 i i i 而打 j j j 也是可以办到的。因而满星通关数目增加 1 1 1,同时 i i i 入堆。
- 如果不能,那么还需要牺牲关卡。考虑当前堆中
d
d
d 最大的可以不牺牲的关卡
j
(
j
>
i
)
j(j>i)
j(j>i),则有
s
j
≤
d
j
+
n
o
w
+
∑
k
=
0
j
−
1
d
k
\displaystyle s_j \leq d_j+{\rm now}+\sum_{k=0}^{j-1} d_k
sj≤dj+now+k=0∑j−1dk,显然此时
i
i
i 已经计入了这个不等式中,因而如果不考虑最优,是一定可以救回来的。考虑两种子情况:
- d i > d j d_i>d_j di>dj。由于对于确认牺牲的关卡,评判其好坏的唯一标准就是 d d d 大。因而直接牺牲 i i i。
- d i ≤ d j d_i \leq d_j di≤dj。此时,牺牲 j j j 也可以换回来 i i i。首先由 j j j 可以进队有 s i − d i ≤ s j − d j , ∑ k = 0 j − 1 d k + n o w ≥ s j − d j \displaystyle s_i-d_i\leq s_j-d_j,\sum_{k=0}^{j-1}d_k+{\rm now} \geq s_j-d_j si−di≤sj−dj,k=0∑j−1dk+now≥sj−dj。考虑距离 i i i 最近的上一个进堆 E E E 的 k ( k ≤ j ) k(k \leq j) k(k≤j),有 ∑ l = 0 k − 1 d l + n o w ′ ≥ s k − d k \displaystyle \sum_{l=0}^{k-1}d_l+{\rm now'} \geq s_k-d_k l=0∑k−1dl+now′≥sk−dk,其中 n o w ′ {\rm now'} now′ 是 n o w {\rm now} now 加上了 [ k + 1 , j − 1 ] [k+1,j-1] [k+1,j−1] 中必然放弃关卡的 d d d 之和。进一步转移到 i i i,由于 [ i + 1 , k − 1 ] [i+1,k-1] [i+1,k−1] 区间全部放弃,那么当枚举量从 k k k 转移到 i i i 时, n o w ′ {\rm now'} now′ 还要增补 ∑ l = i + 1 k − 1 d l \displaystyle \sum_{l=i+1}^{k-1} d_l l=i+1∑k−1dl。同时 s i − d i ≤ s k − d k s_i-d_i \leq s_k-d_k si−di≤sk−dk,那么有 ∑ l = 0 i − 1 d l + d j + ( n o w ′ + ∑ l = i + 1 k − 1 d l ) ≥ s k − d k ≥ s i − d i \displaystyle \sum_{l=0}^{i-1}d_l+d_j+\left({\rm now'}+\sum_{l=i+1}^{k-1}d_l \right )\geq s_k-d_k \geq s_i-d_i l=0∑i−1dl+dj+(now′+l=i+1∑k−1dl)≥sk−dk≥si−di。既然可以拿 i i i 换 j j j 的不牺牲,那么谁的 d d d 大谁更优。
注意:上文中的 n o w \rm now now 变量在程序中是随着 i i i 的前移而不断变化的,但是在题解中,每一处的 n o w \rm now now 均为定值,是一个只与当前枚举的范围有关的一个函数。
对于 d i d_i di 为负数的情况,考虑倒过来执行整个操作,即从分数最低开始一点点涨上去,则过程与上面非负的情况完全相同。
因而总时间复杂度 O ( n log n ) \mathcal O(n \log n) O(nlogn)。
#include<bits/stdc++.h>
#define IL inline
#define LL long long
using namespace std;
const int N=1e5+3;
const LL inf=1e18;
priority_queue<LL>q;
struct hh{
LL d,s;
}a[N],b[N];
int n,na,nb;
LL s0,lia[N],lib[N];
bool cmpa(const hh &a,const hh &b){
return a.s-a.d<b.s-b.d;
}
bool cmpb(const hh &a,const hh &b){
return a.s>b.s;
}
IL LL in(){
char c;int f=1;
while((c=getchar())<'0'||c>'9')
if(c=='-') f=-1;
LL x=c-'0';
while((c=getchar())>='0'&&c<='9')
x=x*10+c-'0';
return x*f;
}
IL void solve(){
LL x,y;int ans=0;
n=in(),na=nb=0,s0=in();
for(int i=1;i<=n;++i){
x=in(),y=in();
if(x<0) b[++nb]=(hh){x,y};
else a[++na]=(hh){x,y};
}
sort(a+1,a+na+1,cmpa),sort(b+1,b+nb+1,cmpb);
LL pre=0,now=s0;
while(q.size()) q.pop();
for(int i=1;i<=na;++i) pre+=a[i].d,lia[i]=a[i].s-pre;
for(int i=na;i;--i){
s0+=a[i].d;
if(now>=lia[i]) ++ans,q.push(a[i].d);
else if(!q.size()||q.top()<a[i].d) now+=a[i].d;
else now+=q.top(),q.pop(),q.push(a[i].d);
}
pre=0,now=s0;
while(q.size()) q.pop();
for(int i=1;i<=nb;++i) pre+=b[i].d,lib[i]=b[i].s-pre;
for(int i=1;i<=nb;++i){
if(now>=lib[i]) ++ans,q.push(-b[i].d);
else if(!q.size()||q.top()<-b[i].d) now-=b[i].d;
else now+=q.top(),q.pop(),q.push(-b[i].d);
}
printf("%d\n",ans);
}
int main()
{
int T=in();
while(T--) solve();
return 0;
}
K Link is as bear
题意:给定一个序列 { a n } \{a_n\} {an},每次可以选定一个连续子区间 [ l , r ] [l,r] [l,r],使 a i ← ⨁ j = l r a j \displaystyle a_i \leftarrow \bigoplus_{j=l}^r a_j ai←j=l⨁raj(同时变化),且保证存在 x ≠ y x \neq y x=y, a x = a y a_x=a_y ax=ay,问当整个序列完全相同的时候,这个相同的最大值为多少。 n ≤ 1 × 1 0 5 n \leq 1\times 10^5 n≤1×105, a i ≤ 1 × 1 0 15 a_i \leq 1\times 10^{15} ai≤1×1015。
解法:本题和“给定序列 { a n } \{a_n\} {an},找到异或和最大的子序列”完全等价,因而使用线性基即可。
下证两问题等价:
记要选择的数字为 1 1 1,不选择的数字为 0 0 0。分以下几种情况:
- 原序列中出现连续的两个 0 0 0,即连续两个都不选,记位置为 i , i + 1 i,i+1 i,i+1。连续执行两次操作 [ i , i + 1 ] [i,i+1] [i,i+1],即可消除这两个数字。
- 原序列中出现两个连续的 1 1 1,即连续两个都选,记位置为 i , i + 1 i,i+1 i,i+1。若接下来的数字不选,则可以执行 [ i , i + 1 ] [i,i+1] [i,i+1],然后将第一个数字(此时为 a i ⊕ a i + 1 a_i \oplus a_{i+1} ai⊕ai+1)保留下来,后面两个消除。 i − 1 i-1 i−1 不选同理。
因而出现两个连续选择或者连续不选择的都可以使得两问题等价。考虑原序列形如 10101 10101 10101 或者 01010 01010 01010 这种完全交错形,但是注意题目中有条件 ∃ x ≠ y \exist x \neq y ∃x=y 使得 a x = a y a_x=a_y ax=ay,考虑以下两种情况:
- x , y x,y x,y 同奇偶,即表示都不选或者都选。此时 a x a_x ax 和 a y a_y ay 可以同时选和同时不选,所以可以作为 11 11 11 或 00 00 00,两种总有之一可以保证不是交错的。(感谢 Fried Chicken 的指正)
- x , y x,y x,y 不同奇偶。则选择 a x a_x ax 或者 a y a_y ay 可以将他们对应的 10 10 10 状态对调来创造连续的 1 1 1 或者 0 0 0。
因而原问题与“给定序列 { a n } \{a_n\} {an},找到异或和最大的子序列”完全等价。
#include <bits/stdc++.h>
using namespace std;
long long p[70];
bool insert(long long x)
{
for (int i = 60; i >= 0;i--)
{
if(!(x>>i))
continue;
if(!p[i])
{
p[i] = x;
return 1;
}
else
x ^= p[i];
}
return 0;
}
int main()
{
long long x;
int t, n;
scanf("%d", &t);
while(t--)
{
scanf("%d", &n);
for (int i = 0; i <= 60;i++)
p[i] = 0;
for (int i = 1; i <= n;i++)
{
scanf("%lld", &x);
insert(x);
}
long long ans = 0;
for (int i = 60; i >= 0; i--)
if ((ans ^ p[i]) > ans)
ans ^= p[i];
printf("%lld\n", ans);
}
return 0;
}