stream pack 1.

收录动态规划题

1. LuoguP11189 -「KDOI-10」水杯降温

tag:二分、状态设计;link

发现只用操作 1 就能够完成当且仅当:

  • i,ai0
  • aijsoniaj=0

bi=aijsoniaj,考虑操作 2 对于 b 数组的影响。设 ci 表示点 i 上操作 2 的次数,b,c 的关系并不简单,但是若设 fi 表示 i1c 的和,那么有简单的式子:

bi=(jsonifj)fi

猜测 fi 的取值是一个区间,其实真是。

对于叶子,fi[max(0,bi),+inf);对于非叶子,有

bi+fi=fj,fifj

计算左端点后即可二分查找右端点。

点击查看代码
//P11189
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
typedef long long ll;
const ll inf = 5e18;
int c, T, n, lf[N];
vector<int> g[N];
ll a[N], b[N], le[N], ri[N];
bool chk(int x, ll v, int fa){
ll mn = 0, mx = 0;
for(int i : g[x]){
if(i == fa){
continue;
}
if(ri[i] < v){
return 0;
}
mn += max(le[i], v);
mx += ri[i];
mx = min(mx, inf);
}
ll val = b[x] + v;
return val >= mn && val <= mx;
}
void dfs(int x, int fa){
if(lf[x]){
le[x] = max(0ll, -b[x]);
ri[x] = 1e18;
return;
}
for(int i : g[x]){
if(i != fa){
dfs(i, x);
if(le[i] > ri[i]){
le[x] = 1;
ri[x] = 0;
return;
}
}
}
ll sum = 0;
for(int i : g[x]){
if(i != fa){
sum += le[i];
}
}
le[x] = max(0ll, sum - b[x]);
ll L = le[x]-1, R = 1.1e13;
while(L < R){
ll mid = L + R + 1 >> 1;
if(chk(x, mid, fa)){
L = mid;
} else {
R = mid - 1;
}
}
ri[x] = L;
}
int main(){
scanf("%d%d", &c, &T);
while(T--){
scanf("%d", &n);
for(int i = 1; i <= n; ++ i){
lf[i] = 1;
vector<int> ().swap(g[i]);
}
for(int i = 2; i <= n; ++ i){
int fa;
scanf("%d", &fa);
lf[fa] = 0;
g[fa].push_back(i);
}
for(int i = 1; i <= n; ++ i){
scanf("%lld", &a[i]);
}
for(int i = 1; i <= n; ++ i){
b[i] = a[i];
for(int j : g[i]){
b[i] -= a[j];
}
}
dfs(1, 0);
if(le[1] <= ri[1]){
puts("Huoyu");
} else {
puts("Shuiniao");
}
}
return 0;
}

2. qoj365/JOISC2017 - 鉄道旅行 (Railway Trip)

tag:倍增、正确性证明;link

容易发现一个结论:假设一条路径 A=x1,x2,...,xp=B,那么一定有一条最短路满足存在 q 使得 i[1,q),LxiLxi+1;i[q,p),LxiLxi+1,即经过的所有点的 L 是非严格单峰的。

所以重新建图:对点 i,找到它两侧离它最近的两个点 p,q 满足 LpLiLq 并连边 LiLp,LiLq。那么原图的一条最短路可以转化为新图的 AC,BC 的两条路径。

倍增,设 l/ri,j 表示 i 开始跳 2j 步两侧到哪些点,容易证明这也意味着能到 L 最大的哪两个点。

所以询问时从 A 开始逼近 B 变成 A,然后 B 逼近 A 得到答案,相当于先跑单峰左边的递增,再跑右边的,所以是正确的。

点击查看代码
//qoj365
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, k, q, a[N], le[N][20], ri[N][20];
int st[N], tp;
int main(){
scanf("%d%d%d", &n, &k, &q);
st[tp=0] = 1;
for(int i = 1; i <= n; ++ i){
scanf("%d", &a[i]);
while(tp && a[st[tp]] < a[i]) -- tp;
le[i][0] = st[tp];
st[++tp] = i;
}
st[tp=0] = n;
for(int i = n; i >= 1; -- i){
while(tp && a[st[tp]] < a[i]) -- tp;
ri[i][0] = st[tp];
st[++tp] = i;
}
for(int i = 1; i <= 19; ++ i){
for(int j = 1; j <= n; ++ j){
le[j][i] = min(le[le[j][i-1]][i-1], le[ri[j][i-1]][i-1]);
ri[j][i] = max(ri[ri[j][i-1]][i-1], ri[le[j][i-1]][i-1]);
}
}
while(q--){
int x, y, ans = 0;
scanf("%d%d", &x, &y);
if(x > y) swap(x, y);
int p = x, q = x;
for(int i = 19; i >= 0; -- i){
int pp = min(le[p][i], le[q][i]);
int qq = max(ri[p][i], ri[q][i]);
if(qq < y){
p = pp;
q = qq;
ans += 1 << i;
}
}
x = q;
p = y, q = y;
for(int i = 19; i >= 0; -- i){
int pp = min(le[p][i], le[q][i]);
int qq = max(ri[p][i], ri[q][i]);
if(pp > x){
p = pp;
q = qq;
ans += 1 << i;
}
}
printf("%d\n", ans);
}
return 0;
}

3. 2023Xi'anR - Random Variables

tag:容斥;link

F(k) 表示所有数出现次数 k 的方案数,那么 mnF(k) 即为存在数出现次数 >k 的方案数,则 n×mnk=0n1F(k) 即为答案,因为一个 mnF(k) 对应着所有众数出现次数 >k 的方案。根据定义一定有 f(0)=0,所以只用对于每个 k[1,n) 求出 F(k) 即可。

考虑容斥,设 fi,j 表示目前填了 i 个数,有 j 个数已经被选定次数 >k。那么转移有:

fi,j=(mj)fi1,j(mj+1)(ni+kk)fik1,j1

左边表示在未填的第一个位置中填入一个未被选定的数;右边表示在未填的第一个位置以及后面任意 k 个位置填一个未被选定的数并且选定它,因为多了一个选定的数所以要乘 1

最后答案即为 F(k)=fn,i。单次复杂度 O(n2k),总复杂度 O(n2logn)

点击查看代码
//qoj9254
#include <bits/stdc++.h>
using namespace std;
const int N = 1e3 + 10;
typedef long long ll;
int T, n;
ll m, P, f[N][N], C[N][N], ans;
struct Mod{
ll m, p;
void init(int pp){
m = ((__int128)1 << 64) / pp;
p = pp;
}
ll operator ()(ll x){
if(p == 2){
return x & 1;
}
return x - ((__int128(x) * m) >> 64) * p;
}
} mod;
ll qp(ll x, ll y){
ll ans = 1;
while(y){
if(y & 1){
ans = mod(ans * x);
}
x = mod(x * x);
y >>= 1;
}
return ans;
}
int main(){
scanf("%d%lld", &T, &P);
n = 1000;
mod.init(P);
C[0][0] = 1;
for(int i = 1; i <= n; ++ i){
C[i][0] = 1;
for(int j = 1; j <= i; ++ j){
C[i][j] = C[i-1][j] + C[i-1][j-1];
if(C[i][j] >= P){
C[i][j] -= P;
}
}
}
while(T--){
ans = 0;
scanf("%d%lld", &n, &m);
for(int k = 1; k < n; ++ k){
f[0][0] = 1;
for(int i = 1; i <= n; ++ i){
f[i][i/(k+1)+1] = 0;
for(int j = 0; j * (k + 1) <= i; ++ j){
f[i][j] = mod(f[i-1][j] * (m - j));
if(i >= k + 1 && j){
ll tmp = mod(f[i-k-1][j-1] * (m - j + 1));
tmp = mod(tmp * C[n-(i-k)][k]);
f[i][j] -= tmp;
if(f[i][j] < 0){
f[i][j] += P;
}
}
}
}
for(int j = 0; j * (k + 1) <= n; ++ j){
ans += f[n][j];
f[n][j] = 0;
if(ans >= P){
ans -= P;
}
}
}
printf("%lld\n", mod(- ans + P + n * qp(m, n)) % P);
}
return 0;
}

4. APIO2024 - Train

考虑按时间顺序 dp,设 fx,i 表示 i 时刻在点 x 的最小费用,则对于每条星琼铁道有:

fY,B=miniA{fX,i+Tw(i,A)}+C

其中 w(a,b) 表示 [L,R](a,b) 的吃饭时间数,可以使用主席数求出单个的 w

容易发现有 w(a,b+1)+w(a+1,b)w(a,b)+w(a+1,b+1),即相交优于包含,可以在每个点开一个队列表示决策,二分队列决策单调性求解。

二分队列算法步骤:

  • 计算队首两个决策 l,l+1 的交点是否 i,如果是则弹出队首;
  • 若存在队首,用队首更新此时的答案;
  • 计算队尾两个决策 r1,r 的交点是否 队尾决策 r 与当前决策的交点,如果是则弹出队尾;
  • 插入当前决策。
  • 可以使用第二个队列维护队内交点减少算法常数。
  • 使用二分求解两个决策交点,具体的,求出第一个下标使得前者劣于后者。
点击查看代码
//qoj8725
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int> vint;
#ifndef ONLINE_JUDGE
#include "train.h"
#endif
const int N = 1e5 + 10;
int n, m, w, pl[N*4], tp;
basic_string<int> eat[N*4], que[N], qcr[N];
basic_string<pair<int, int> > tra[N*4];
int lp[N], rp[N], mc[N];
unordered_map<int, ll> f[N];
unordered_map<int, int> vs[N];
struct SegTree{
int rt[N*4], tot = 1;
struct node{
int sum, ls, rs;
} t[N*100];
void add(int &p, int l, int r, int x){
++tot;
t[tot] = t[p];
p = tot;
if(l == r){
++ t[p].sum;
} else {
int mid = l + r >> 1;
if(x <= mid){
add(t[p].ls, l, mid, x);
} else {
add(t[p].rs, mid+1, r, x);
}
t[p].sum = t[t[p].ls].sum + t[t[p].rs].sum;
}
}
int ask(int p, int l, int r, int ql, int qr){
if(qr < l || r < ql || !p){
return 0;
} else if(ql <= l && r <= qr){
return t[p].sum;
} else {
int mid = l + r >> 1;
return ask(t[p].ls, l, mid, ql, qr) +
ask(t[p].rs, mid+1, r, ql, qr);
}
}
ll ask(int l, int r){
return ask(rt[r-1], 1, tp, l+1, tp);
}
} T;
ll fx(int x, int p, int val){
return f[x][p] + mc[x] * T.ask(p, val);
}
int crs(int x, int p, int q){
int l = q, r = tp + 1;
while(l < r){
int mid = l + r >> 1;
if(fx(x, p, mid) < fx(x, q, mid)){
l = mid + 1;
} else {
r = mid;
}
}
return l;
}
ll solve(int N, int M, int W, vint mcc,
vint xx, vint yy, vint st, vint ed, vint lc,
vint le, vint ri){
n = N;
m = M;
w = W;
for(int i = 0; i < n; ++ i){
mc[i] = mcc[i];
lp[i] = 1;
rp[i] = 0;
que[i].push_back(0);
qcr[i].push_back(0);
}
for(int i : st){
pl[++tp] = i;
}
for(int i : ed){
pl[++tp] = i;
}
for(int i : le){
pl[++tp] = i;
}
for(int i : ri){
pl[++tp] = i;
}
sort(pl + 1, pl + tp + 1);
tp = unique(pl + 1, pl + tp + 1) - pl - 1;
for(int i = 0; i < m; ++ i){
st[i] = lower_bound(pl + 1, pl + tp + 1, st[i]) - pl;
ed[i] = lower_bound(pl + 1, pl + tp + 1, ed[i]) - pl;
tra[st[i]].push_back(make_pair(-i-1, xx[i]));
tra[ed[i]].push_back(make_pair(i+1, yy[i]));
}
for(int i = 0; i < w; ++ i){
le[i] = lower_bound(pl + 1, pl + tp + 1, le[i]) - pl;
ri[i] = lower_bound(pl + 1, pl + tp + 1, ri[i]) - pl;
eat[ri[i]].push_back(le[i]);
}
for(int i = 1; i <= tp; ++ i){
T.rt[i] = T.rt[i-1];
for(int j : eat[i]){
T.add(T.rt[i], 1, tp, j);
}
}
f[0][0] = 0;
que[0].push_back(0);
qcr[0].push_back(0);
que[0][++rp[0]] = 0;
for(int i = 1; i <= tp; ++ i){
sort(tra[i].begin(), tra[i].end());
for(auto j : tra[i]){
int id = j.first, pos = j.second;
if(id < 0){
id = -id - 1;
ll val = 1e18;
while(lp[pos] < rp[pos] && qcr[pos][lp[pos]] <= i){
++ lp[pos];
}
if(lp[pos] > rp[pos]){
continue;
}
int p = que[pos][lp[pos]];
val = f[pos][p] + mc[pos] * T.ask(p, i);
if(f[pos].find(i) != f[pos].end()){
f[pos][i] = min(f[pos][i], val);
} else {
f[pos][i] = val;
}
} else {
-- id;
if(f[xx[id]].find(st[id]) == f[xx[id]].end()){
continue;
}
ll val = f[xx[id]][st[id]] + lc[id];
if(f[pos].find(i) != f[pos].end()){
f[pos][i] = min(f[pos][i], val);
} else {
f[pos][i] = val;
}
while(lp[pos] < rp[pos] &&
qcr[pos][rp[pos]-1] >=
crs(pos, que[pos][rp[pos]], i)){
-- rp[pos];
}
que[pos].push_back(0);
qcr[pos].push_back(0);
que[pos][++rp[pos]] = i;
if(lp[pos] < rp[pos]){
qcr[pos][rp[pos]-1] = crs(pos, que[pos][rp[pos]-1], que[pos][rp[pos]]);
}
}
}
}
ll val = 1e18;
for(auto i : f[n-1]){
val = min(val, i.second + 1ll * mc[n-1] * T.ask(i.first, tp+1));
}
if(val > 1e17){
return -1;
}
return val;
}
posted @   KiharaTouma  阅读(12)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起