20220623
- rk 24/48, 15+20+50=85
- max: 100, 80, 100, 100+20+100=220
寄
代价是相加形式,可以拆到每条边上计算。对于一棵子树,可能是外部的关键点到子树中中转站,也可能是子树中关键点到外部中转站,但不会同时出现这两种情况。设 \(f[u,i]\) 为子树 \(u\) 中有 \(i\) 个关键点与子树外中转站匹配的最小代价(已经统计上了这些点到 \(u\) 的代价和),\(g[u,i]\) 为子树中距 \(u\) 最近的中转站为 \(i\) 的最小代价
\(f\) 的转移就是树上背包,\(g\) 可以对每个当前子树中的 \(j\) 枚举前面的未匹配点数,对前面子树的 \(j\) 枚举当前的未匹配点数
每一对关键点与关键点、关键点与树点会在 lca 处枚举一次,时间复杂度 \(O(n^{2})\)
code
const int N = 3e3+5;
const LL infl = 0x3f3f3f3f3f3f3f3f;
int n,m,c,siz[N];
LL dis[N],f[N][N],g[N][N],h[N];
Vi st[N];
vector<Pii> e[N];
#define v ei.fi
#define w ei.se
void dfs(int u,int fa) {
f[u][0] = g[u][u] = c, f[u][siz[u]] = 0, st[u].pb(u);
for(auto &ei : e[u]) if( v != fa ) {
dis[v] = dis[u]+w, dfs(v,u);
copy(f[u],f[u]+siz[u]+1,h), mem(f[u],0x3f,siz[u]);
for(int i : st[u]) {
LL res = infl;
For(j,0,siz[v]) ckmin(res,g[u][i]+f[v][j]+(dis[i]-dis[u]+w)*j);
g[u][i] = res, ckmin(f[u][0],res);
}
for(int i : st[v]) {
LL res = infl;
For(j,0,siz[u]) ckmin(res,g[v][i]+h[j]+(dis[i]-dis[u])*j);
g[u][i] = res, ckmin(f[u][0],res);
}
For(i,0,siz[u]) For(j,0,siz[v]) ckmin(f[u][i+j],h[i]+f[v][j]+(LL)w*j);
siz[u] += siz[v], st[u].insert(st[u].end(),all(st[v]));
}
// cerr<<u<<":\n";
// For(i,0,siz[u]) cerr<<f[u][i]<<' '; cerr<<endl;
// for(int i : st[u]) cerr<<i<<' '<<g[u][i]<<endl;
}
signed main() { freopen("post.in","r",stdin); freopen("post.out","w",stdout);
memset(f,0x3f,sizeof f), memset(g,0x3f,sizeof g);
io>>n>>m>>c; Rep(i,1,n, x,y,z) io>>x>>y>>z, e[x].pb(y,z), e[y].pb(x,z);
For(i,1,m) ++siz[read()];
dfs(1,0);
io<<f[1][0];
return 0;
}
摆
润
赛时做法:先写出 \(O(n^{2})\) 暴力:
// x 表示进位。省略了记忆化
mint slv(int l,int r,int x) {
if( l == r ) return a[l]+x==1 ? 1 : 6;
int i = r-l + max(x,*max_element(a+l,a+r));
return (i+1)*Pow(2,i) + slv(l+1,r,a[l]) + slv(l+1,r,x);
}
把 \(r\) 调整到左侧第一个为 \(1\) 的位置。考虑拆开与 \(x\) 取 \(\max\)。找到 \(r\) 左侧第一个为 \(1\) 的位置 \(p\),那么 \(l\le p\) 时 *max_element(a+l,a+r) = 1
。\(l>p\) 时剩余的数位一定形如 \(000\cdots001\),可以 \(O(n)\) 计算。可以把代码改成 \(l>p\) 时返回 \(f[x,r-p+1]\)(\(f\) 表示预处理出的上述子问题的答案)
把贡献拆成 (i+1)*Pow(2,i)
和 slv(l+1,r,a[l]) + slv(l+1,r,x)
,前者是等差乘等比数列求和,后者本质是要计算 \(f[0/1]\) 的贡献系数之和,\(i\) 位置对系数的贡献是 \(2^{i-l}\),对 \(0,1\) 分别求和即可
上述信息在带修的情况下可以用线段树轻松维护,时间复杂度 \(O(n\log n)\)
code
const int N = 1e5+5;
const mint iv2 = mint(2).pow();
int n,m;
mint pw[N],ipw[N],spw[N];
#define ls (u<<1)
#define rs (u<<1|1)
#define mid (l+r>>1)
#define root int u=1,int l=1,int r=n
#define lson ls,l,mid
#define rson rs,mid+1,r
struct {
mint res0,res1;
struct { int len,cov,cnt; mint sum,s[2]; bool rev; } t[N*4];
void up(int u) {
t[u].cnt = t[ls].cnt + t[rs].cnt,
t[u].s[0] = t[ls].s[0] + t[rs].s[0], t[u].s[1] = t[ls].s[1] + t[rs].s[1];
}
void dcov(int u,bool x) {
t[u].cnt = x*t[u].len, t[u].s[x] = t[u].sum, t[u].s[!x] = 0,
t[u].cov = x, t[u].rev = 0;
}
void drev(int u) {
t[u].cnt = t[u].len-t[u].cnt, swap(t[u].s[0],t[u].s[1]);
if( ~t[u].cov ) t[u].cov ^= 1;
else t[u].rev ^= 1;
}
void down(int u) {
if( ~t[u].cov ) dcov(ls,t[u].cov), dcov(rs,t[u].cov), t[u].cov = -1;
else if( t[u].rev ) drev(ls), drev(rs), t[u].rev = 0;
}
void bld(root) {
t[u].len = r-l+1, t[u].sum = spw[r]-spw[l-1], t[u].cov = -1;
if( l == r ) return dcov(u,read<char>()-'0');
bld(lson), bld(rson), up(u);
}
void cov(int ql,int qr,bool x,root) {
if( qr < l || r < ql ) return;
if( ql <= l && r <= qr ) return dcov(u,x);
down(u), cov(ql,qr,x,lson), cov(ql,qr,x,rson), up(u);
}
void rev(int ql,int qr,root) {
if( qr < l || r < ql ) return;
if( ql <= l && r <= qr ) return drev(u);
down(u), rev(ql,qr,lson), rev(ql,qr,rson), up(u);
}
int bs(int qr,root) {
if( qr < l || !t[u].cnt ) return 0;
if( l == r ) return t[u].cnt ? l : 0;
down(u);
if( int res = bs(qr,rson) ) return res;
return bs(qr,lson);
}
void qry(int ql,int qr,root) {
if( qr < l || r < ql ) return;
if( ql <= l && r <= qr ) return res0 += t[u].s[0], res1 += t[u].s[1], void();
down(u), qry(ql,qr,lson), qry(ql,qr,rson);
}
} seg;
auto w=[](int x) { return (x+1)*pw[x]; };
mint slv(bool x,int r) {
static bool vis[2][N]; static mint f[2][N];
if( r == 1 ) return x ? 6 : 1;
if( vis[x][r] ) return f[x][r];
return vis[x][r] = 1, f[x][r] = w(r-1+x) + slv(x,r-1) + slv(0,r-1);
}
auto sid=[](int l,int r) { return iv2*(l+r)*(r-l+1); };
mint qry(int l,int r) {
if( l > (r=seg.bs(r)) ) return 0;
int ll = seg.bs(r-1);
if( l > ll ) return slv(0,r-l+1);
seg.res0 = seg.res1 = 0, seg.qry(l,ll);
return pw[r-l+1]*sid(r-ll+2,r-l+2) + slv(0,r-ll) +
ipw[l]*(seg.res0*slv(0,r-ll)+seg.res1*slv(1,r-ll));
}
signed main() {
#ifdef FS
freopen("run7.in","r",stdin); freopen("a.out","w",stdout);
#else
freopen("run.in","r",stdin); freopen("run.out","w",stdout);
#endif
ios::sync_with_stdio(0);cout.tie(0);
pw[0] = ipw[0] = spw[0] = 1;
Rep(i,1,N) pw[i] = pw[i-1] * 2, ipw[i] = ipw[i-1] * iv2, spw[i] = spw[i-1]+pw[i];
io>>n>>m, seg.bld();
while( m-- ) {
int op,l,r; io>>op>>l>>r;
if( op == 1 ) seg.rev(l,r);
else if( op == 2 ) seg.cov(l,r,0);
else if( op == 3 ) seg.cov(l,r,1);
else cout<<qry(l,r).x<<endl;
}
return 0;
}
std 做法更泛用。记 \(ans[i] = solve(1,n)\)。考虑从右往左推,仍然以 \(p\) 位置分开计算,需要维护 \(ans[i],ans[i+1]\)(其中 \(i\) 是当前位置到 \(r\) 形成的二进制数,\(i+1\) 是上一位进位后的数)。
根据上面的暴力不难递推到 \(ans[2i],ans[2i+1]\),这个过程可以用矩乘表示。以 \(>p\) 的位置为例:
\(<p\) 的位置类似,对 \(0/1\) 分别转移即可。使用线段树维护区间矩阵乘积,时间复杂度 \(O(4^{3}n\log n)\),可能需要卡常