T1 内鬼
由于两名内鬼的行动互不干扰,因此可以单独求解后暴力合并。
很容易想到一种 dp 做法,设 \(f_{i,S}\) 表示当前位于第 \(i\) 个点,当前刀掉的人组成的集合为 \(S\) 时的最短时间,转移时考虑通过一条边或留在此处继续刀人,很容易使用 Dijikstra 进行转移,然而……
它 TLE 了……考虑一个优化,从小到大枚举集合 \(S\) ,同层之间使用 Dijikstra 转移,异层之间不使用 Dijikstra ,常数会小很多。
code
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
const int max1 = 1e4, max2 = 8, max3 = 1 << 8;
const int inf = 0x3f3f3f3f;
int n, m, k;
struct Node
{ int next, v, w; } edge[max1 * 4 + 5];
int head[max1 + 5], total;
void Add ( int u, int v, int w )
{
edge[++total].v = v;
edge[total].w = w;
edge[total].next = head[u];
head[u] = total;
return;
}
int len, Tmax;
struct Option
{
int id, u, tim;
bool operator < ( const Option &A ) const
{ return tim < A.tim; }
}opt[max1 * 10 + 5];
vector <int> tim[max1 + 5][max2 + 5];
int f[max1 + 5][max3 + 5];
bool vis[max1 + 5];
struct Point
{
int u, s, val;
Point () {}
Point ( int __u, int __val )
{ u = __u, val = __val; }
bool operator < ( const Point &A ) const
{ return val > A.val; }
};
priority_queue <Point> que;
int g[max3 + 5], h[max3 + 5];
void Solve ( int u )
{
for ( int i = 1; i <= n; i ++ )
for ( int s = 0; s < 1 << k; s ++ )
f[i][s] = inf;
f[u][0] = 0;
for ( int s = 0; s < 1 << k; s ++ )
{
while ( !que.empty() )
que.pop();
for ( int i = 1; i <= n; i ++ )
que.push(Point(i, f[i][s])), vis[i] = false;
while ( !que.empty() )
{
int now = que.top().u;
que.pop();
if ( vis[now] )
continue;
if ( f[now][s] > Tmax )
break;
vis[now] = true;
for ( int i = head[now]; i; i = edge[i].next )
{
int v = edge[i].v, w = edge[i].w;
if ( !vis[v] && f[v][s] > f[now][s] + w )
{
f[v][s] = f[now][s] + w;
que.push(Point(v, f[v][s]));
}
}
for ( int i = 1; i <= k; i ++ )
{
if ( !( s >> i - 1 & 1 ) )
{
int pos = *lower_bound(tim[now][i].begin(), tim[now][i].end(), f[now][s]);
f[now][s | 1 << i - 1] = min(f[now][s | 1 << i - 1], pos);
}
}
}
}
return;
}
int main ()
{
freopen("ghost.in", "r", stdin);
freopen("ghost.out", "w", stdout);
scanf("%d%d%d", &n, &m, &k);
for ( int i = 1, u, v, w; i <= m; i ++ )
{
scanf("%d%d%d", &u, &v, &w);
Add(u, v, w), Add(v, u, w);
}
scanf("%d%d", &len, &Tmax);
for ( int i = 1; i <= len; i ++ )
scanf("%d%d%d", &opt[i].id, &opt[i].u, &opt[i].tim);
sort(opt + 1, opt + 1 + len);
for ( int i = 1; i <= len; i ++ )
tim[opt[i].u][opt[i].id].push_back(opt[i].tim);
for ( int i = 1; i <= n; i ++ )
for ( int j = 1; j <= k; j ++ )
tim[i][j].push_back(inf);
int u, v;
scanf("%d%d", &u, &v);
Solve(u);
for ( int s = 0; s < 1 << k; s ++ )
{
g[s] = inf;
for ( int i = 1; i <= n; i ++ )
g[s] = min(g[s], f[i][s]);
}
Solve(v);
for ( int s = 0; s < 1 << k; s ++ )
{
h[s] = inf;
for ( int i = 1; i <= n; i ++ )
h[s] = min(h[s], f[i][s]);
}
int ans = inf;
for ( int s = 0; s < 1 << k; s ++ )
for ( int t = 0; t < 1 << k; t ++ )
if ( !( s & t ) && ( s | t ) == ( 1 << k ) - 1 && g[s] != inf && h[t] != inf )
ans = min(ans, max(g[s], h[t]));
if ( ans > Tmax )
ans = -1;
printf("%d\n", ans);
return 0;
}
T2 树上最长上升子序列
由于处理树链信息,因此考虑点分治。
如果我们可以得到跨过分治中心的以权值 \(x\) 结尾的最长下降子序列的长度,显然当前节点跨过分治中心的答案可以直接查询,因此考虑 dp 求解这段长度,很容易想到用动态开点线段树维护,使用线段树合并维护所有经过分治中心的链的信息,可以做到 \(O(n\log^2n)\) 。(然而我的常数大极了)
code
#include <cstdio>
#include <algorithm>
#include <vector>
#include <ctime>
#include <iostream>
#include <climits>
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
using namespace std;
const int max1 = 2e5;
const int inf = 0x3f3f3f3f;
int n, a[max1 + 5], save[max1 + 5], len;
vector <int> edge[max1 + 5];
int siz[max1 + 5], root, point[max1 + 5], cnt;
bool vis[max1 + 5];
int ans[max1 + 5];
int cnt_merge, cnt_insert, cnt_change, cnt_query;
struct Segment_Tree
{
#define lson(now) tree[now].son[0]
#define rson(now) tree[now].son[1]
struct Data
{ int son[2], max_val; } tree[max1 * 100 + 5];
int root[max1 + 5], total;
void Clear ()
{ total = lson(0) = rson(0) = tree[0].max_val = 0; return; }
void Push_Up ( int now )
{
tree[now].max_val = max(tree[lson(now)].max_val, tree[rson(now)].max_val);
return;
}
void Insert ( int &now, int L, int R, int pos, int val )
{
++cnt_insert;
if ( !now )
{ now = ++total; lson(now) = rson(now) = tree[now].max_val = 0; }
if ( L == R )
{ tree[now].max_val = max(tree[now].max_val, val); return; }
int mid = L + R >> 1;
if ( pos <= mid )
Insert(lson(now), L, mid, pos, val);
else
Insert(rson(now), mid + 1, R, pos, val);
Push_Up(now);
return;
}
void Insert ( int now, int pos, int val )
{ return Insert(root[now], 1, len, pos, val); }
void Change ( int &now, int L, int R, int pos, int val )
{
++cnt_change;
if ( !now )
{ now = ++total; lson(now) = rson(now) = tree[now].max_val = 0; }
if ( L == R )
{ tree[now].max_val = val; return; }
int mid = L + R >> 1;
if ( pos <= mid )
Change(lson(now), L, mid, pos, val);
else
Change(rson(now), mid + 1, R, pos, val);
Push_Up(now);
return;
}
void Change ( int now, int pos, int val )
{ return Change(root[now], 1, len, pos, val); }
int Merge ( int x, int y, int L, int R )
{
++cnt_merge;
if ( !tree[x].max_val )
return y;
else if ( !tree[y].max_val )
return x;
if ( L == R )
{ tree[x].max_val = max(tree[x].max_val, tree[y].max_val); return x; }
int mid = L + R >> 1;
lson(x) = Merge(lson(x), lson(y), L, mid);
rson(x) = Merge(rson(x), rson(y), mid + 1, R);
Push_Up(x);
return x;
}
void Merge ( int x, int y )
{ root[x] = Merge(root[x], root[y], 1, len); return; }
int Query ( int now, int L, int R, int pos )
{
++cnt_query;
if ( !tree[now].max_val )
return 0;
if ( L == R )
return tree[now].max_val;
int mid = L + R >> 1;
if ( pos <= mid )
return Query(lson(now), L, mid, pos);
return Query(rson(now), mid + 1, R, pos);
}
int Query ( int now, int pos )
{ return Query(root[now], 1, len, pos); }
int Query_Suffix ( int now, int L, int R, int pos )
{
++cnt_query;
if ( !tree[now].max_val )
return 0;
if ( L == R )
return tree[now].max_val;
int mid = L + R >> 1;
if ( pos <= mid )
return max(tree[rson(now)].max_val, Query_Suffix(lson(now), L, mid, pos));
return Query_Suffix(rson(now), mid + 1, R, pos);
}
int Query_Suffix ( int now, int pos )
{
if ( pos == len + 1 )
return 0;
return Query_Suffix(root[now], 1, len, pos);
}
}Tree;
void Get_Size ( int now, int fa )
{
siz[now] = 1;
for ( auto v : edge[now] )
{
if ( v == fa || vis[v] )
continue;
Get_Size(v, now);
siz[now] += siz[v];
}
return;
}
void Get_Root ( int now, int fa, int sum )
{
bool is_root = true;
for ( auto v : edge[now] )
{
if ( v == fa || vis[v] )
continue;
Get_Root(v, now, sum);
if ( siz[v] > sum >> 1 )
is_root = false;
}
if ( sum - siz[now] > sum >> 1 )
is_root = false;
if ( is_root )
root = now;
return;
}
void Get_Point ( int now, int fa )
{
point[++cnt] = now;
for ( auto v : edge[now] )
{
if ( v == fa || vis[v] )
continue;
Get_Point(v, now);
}
return;
}
void Update ( int now, int fa, int id )
{
int pre = Tree.Query(id, a[now]), up = Tree.Query_Suffix(id, a[now] + 1) + 1;
if ( ans[now] < cnt )
ans[now] = max(ans[now], Tree.Query_Suffix(id, a[now] + 1) + 1);
if ( up > pre )
Tree.Insert(id, a[now], up);
for ( auto v : edge[now] )
{
if ( v == fa || vis[v] )
continue;
Update(v, now, id);
}
if ( up > pre )
Tree.Change(id, a[now], pre);
return;
}
void Insert ( int now, int fa )
{
Tree.root[now] = 0;
for ( auto v : edge[now] )
{
if ( v == fa || vis[v] )
continue;
Insert(v, now);
Tree.Merge(now, v);
}
int up = Tree.Query_Suffix(now, a[now] + 1) + 1;
Tree.Insert(now, a[now], up);
return;
}
void Dfs ( int now )
{
cnt = 0;
Get_Point(now, 0);
for ( int i = 1; i <= cnt; i ++ )
save[i] = a[point[i]];
sort(save + 1, save + 1 + cnt);
len = unique(save + 1, save + 1 + cnt) - ( save + 1 );
for ( int i = 1; i <= cnt; i ++ )
a[point[i]] = lower_bound(save + 1, save + 1 + len, a[point[i]]) - save;
vis[now] = true;
int son = edge[now].size() - 1;
Tree.Clear();
Tree.root[now] = 0;
Tree.Insert(now, a[now], 1);
for ( int i = 0; i <= son; i ++ )
{
int v = edge[now][i];
if ( vis[v] )
continue;
Update(v, now, now);
if ( i < son )
{
Insert(v, now);
Tree.Merge(now, v);
int up = Tree.Query_Suffix(now, a[now] + 1) + 1;
Tree.Insert(now, a[now], up);
}
}
Tree.Clear();
Tree.root[now] = 0;
Tree.Insert(now, a[now], 1);
for ( int i = son; i >= 0; i -- )
{
int v = edge[now][i];
if ( vis[v] )
continue;
Update(v, now, now);
Insert(v, now);
Tree.Merge(now, v);
int up = Tree.Query_Suffix(now, a[now] + 1) + 1;
Tree.Insert(now, a[now], up);
}
if ( ans[now] < cnt )
ans[now] = max(ans[now], Tree.Query(now, a[now]));
for ( int i = 0; i <= son; i ++ )
{
int v = edge[now][i];
if ( vis[v] )
continue;
Get_Size(v, now);
Get_Root(v, now, siz[v]);
Dfs(root);
}
return;
}
int main ()
{
freopen("lot.in", "r", stdin);
freopen("lot.out", "w", stdout);
scanf("%d", &n);
for ( int i = 1; i <= n; i ++ )
scanf("%d", &a[i]);
for ( int i = 2, u, v; i <= n; i ++ )
{
scanf("%d%d", &u, &v);
edge[u].push_back(v);
edge[v].push_back(u);
}
Get_Size(1, 0);
Get_Root(1, 0, siz[1]);
Dfs(root);
cerr << cnt_merge << endl;
cerr << cnt_insert << endl;
cerr << cnt_change << endl;
cerr << cnt_query << endl;
for ( int i = 1; i <= n; i ++ )
printf("%d\n", ans[i]);
return 0;
}
T3 海盗
设当前节点为 \(i\) ,设当前节点传递 \(x\) 个金币给上一个节点,当前节点收到了下一个节点的 \(y\) 个金币( \(x,y\) 可以为正数,可以为负数),显然当前节点的金币数为 \(a_i-x+y\) ,那么有:
\[L_i\le a_i-x+y\le R_i\\ L_i-a_i+x\le y\le R_i-a_i+x \]如果我们可以确定 \(x\) ,那么 \(y\) 的范围也就确定了。
有一种比较显然的 dp 思路,设 \(f_{i,j}\) 表示考虑到第 \(i\) 个人,第 \(i+1\) 个人传递给第 \(i\) 个人 \(j\) 个硬币时的最小代价,显然转移有:
\[f_{i,j}=\min_{k=L_i-a_i}^{R_i-a_i}(f_{i-1,j-k})+|j| \]由于转移构成环,因此考虑枚举第 \(1\) 个人传递给第 \(n\) 个人的金币数 \(x\) ,此时可以得到一种 \(O((\sum a_i)^3)\) 的暴力 dp 做法。
考虑进行优化,设函数 \(f_i(x)=f_{i,x}\) ,此时证明 \(f_i(x)\) 关于 \(x\) 构成一个下凸函数。
考虑归纳法进行证明,首先对于 \(f_0(x)\) ,这显然成立。
考虑过程中的转移,首先存在一种取 \(\min\) 操作,假设 \(f_{i-1}(x)\) 是下凸函数,并且 \(mid\) 为该函数最低点,不放令 \(L_i-a_i\to L_i,R_i-a_i\to R_i\) ,那么考虑一下几种情况:
-
\(x-R_i\ge mid\) ,显然 \(f_i(x)=f_{i-1}(x-R_i)\) ;
-
\(x-L_i\le mid\) ,显然 \(f_i(x)=f_{i-1}(x-L_i)\) ;
-
\(x-R_i<mid<x-L_i\) ,此时 \(x\) 的转移范围包含 \(mid\) ,显然 \(f_i(x)=f_{i-1}(mid)\) 。
考虑形象化表示转移,将 \(f_{i-1}(x)\) 从 \(mid\) 处一分为二,左半部分向右平移 \(L_i\) ,右半部分向右平移 \(R_i\) ,中间部分使用函数最低点填平,显然操作过后,函数仍然为下凸函数。
由于 \(|x|\) 也是下凸函数,因此函数叠加后仍然是下凸函数。
实际上根据上述分析, \(f_i(x)\) 只需要维护两种操作,就可以进行转移:函数平移,叠加 \(|x|\) 。
考虑 slope trick ,维护一个对顶堆,左堆维护函数最低点向左的信息,右堆维护函数最低点向右的信息,左堆内从大到小第 \(i\) 个权值 \(x\) 表示函数在位置 \(x\) 斜率由 \(1-i\) 变为 \(-i\) ,右堆内从小到大第 \(i\) 个权值 \(x\) 表示函数在位置 \(x\) 斜率由 \(i-1\) 变为 \(i\) ,维护变量 \(min\_val\) 表示函数最低点的值。
考虑修改,对于函数平移,显然直接维护 lazy 标记即可。
对于叠加 \(|x|\) ,大致分以下三种情况考虑:
左堆 \(top\) \(\le 0\le\) 右堆 \(top\) ,发现此时左堆斜率减 \(1\) ,右堆斜率加 \(1\) ,因此直接分别在两个堆中插入位置 \(0\) 即可。
左堆 \(top\) \(>0\) ,此时左堆斜率由 \(0\) 变为 \(-1\) 的转折点变为斜率由 \(1\) 变为 \(0\) 的转折点,因此取出左堆内最大元素插入到右堆中并将其在左堆中删除,考虑位置 \(0\) 前后斜率相差 \(2\) ,因此在左堆内插入两个 \(0\) 即可。
右堆 \(top\) \(<0\) ,显然与左堆 \(top\) \(>0\) 同理。
于是我们可以在 \(O(n\log n)\) 的时间复杂度内完成转移。
继续考虑优化,设 \(g(x)\) 为第 \(1\) 个节点传递给第 \(n\) 个节点 \(x\) 个金币时的最小代价,通过打表发现 \(g(x)\) 是一个单谷函数,因此可以三分求解。
简单进行证明,我们需要证明的东西等价于 \(\tfrac{g(P)+g(Q)}{2}\ge g(\tfrac{P+Q}{2})\) 。
考虑将定义域扩展到实数,不难发现答案与整数相同。
设 \(p_i,q_i\) 分别为最小代价是第 \(i\) 个节点传递给上一个节点的金币个数,考虑构造 \(r_i=\tfrac{p_i+q_i}{2}\) ,由于 \(L_i\le p_i,q_i\le R_i\) ,因此 \(L_i\le r_i\le R_i\) ,因此 \(r\) 序列一定合法,由于 \(|\tfrac{p_i+q_i}{2}|\le |\tfrac{p_i}{2}|+|\tfrac{q_2}{2}|\) ,因此 \(r\) 序列所对应的答案 \(\le \tfrac{g(P)+g(Q)}{2}\) ,显然 \(r\) 序列的对应的答案 \(\ge g(\tfrac{P+Q}{2})\) ,因此结论成立。
code
#include <cstdio>
#include <algorithm>
#include <iostream>
#include <queue>
using namespace std;
const int max1 = 2e5;
const long long inf = 0x3f3f3f3f3f3f3f3f;
int n, L[max1 + 5], R[max1 + 5], sum;
struct Top_Queue
{
long long lazyL, lazyR, min_val;
priority_queue < long long, vector <long long>, less <long long> > queL;
priority_queue < long long, vector <long long>, greater <long long> > queR;
void Clear ()
{
lazyL = lazyR = min_val = 0;
while ( !queL.empty() )
queL.pop();
while ( !queR.empty() )
queR.pop();
return;
}
void Build ( long long x )
{
Clear();
for ( int i = 1; i <= n + 20; i ++ )
queL.push(x);
for ( int i = 1; i <= n + 20; i ++ )
queR.push(x);
return;
}
void MoveL ( long long x )
{ lazyL += x; return; }
void MoveR ( long long x )
{ lazyR += x; return; }
long long TopL ()
{ return queL.top() + lazyL; }
long long TopR ()
{ return queR.top() + lazyR; }
void InsertL ( long long x )
{ return queL.push(x - lazyL); }
void InsertR ( long long x )
{ return queR.push(x - lazyR); }
void DeleteL ()
{ return queL.pop(); }
void DeleteR ()
{ return queR.pop(); }
void Add ()
{
if ( TopL() > 0 )
{
InsertR(TopL());
min_val += abs(TopL());
DeleteL();
InsertL(0);
InsertL(0);
}
else if ( TopR() < 0 )
{
InsertL(TopR());
min_val += abs(TopR());
DeleteR();
InsertR(0);
InsertR(0);
}
else
{
InsertL(0);
InsertR(0);
}
return;
}
long long Query ( long long x )
{
long long ans = min_val;
if ( TopL() > x )
{
long long cnt = 1;
while ( TopL() > x )
{
long long now = TopL();
DeleteL();
ans += cnt * ( now - max(x, TopL()) );
++cnt;
}
}
else if ( TopR() < x )
{
long long cnt = 1;
while ( TopR() < x )
{
long long now = TopR();
DeleteR();
ans += cnt * ( min(x, TopR()) - now );
++cnt;
}
}
return ans;
}
}Top;
long long Solve ( long long x )
{
Top.Build(x);
for ( int i = 1; i <= n; i ++ )
{
Top.MoveR(R[i]);
Top.MoveL(L[i]);
Top.Add();
}
return Top.Query(x);
}
int main ()
{
freopen("pirate.in", "r", stdin);
freopen("pirate.out", "w", stdout);
scanf("%d", &n);
for ( int i = 1, w; i <= n; i ++ )
{
scanf("%d%d%d", &w, &L[i], &R[i]);
sum += w;
L[i] = L[i] - w;
R[i] = R[i] - w;
}
long long L = -sum, R = sum, ans = inf;
while ( R - L > 5 )
{
long long mid = L + R >> 1;
long long ansL = Solve(mid), ansR = Solve(mid + 1);
ans = min(ans, ansL);
ans = min(ans, ansR);
if ( ansL < ansR )
R = mid - 1;
else
L = mid + 1;
}
for ( long long i = L; i <= R; i ++ )
ans = min(ans, Solve(i));
printf("%lld\n", ans);
return 0;
}
标签:cnt,val,int,ans,long,清北营,冲刺,2023,now
From: https://www.cnblogs.com/KafuuChinocpp/p/17363287.html