首先有这样的转移:
\[\begin{cases}f(n,0)=f(n-1,1) \\\displaystyle f(n,1)=\lvert a_n-a_{n-1}\rvert+\min\{f(n-1,0),f(n-1,1)\}\end{cases} \]其中 \(0/1\) 表示 \(n\) 否/是与 \(n-1\) 建立关系。但这题需要我们进行区间合并,这个 \(\mathtt{dp}\) 提醒我们从两个方向来思考:维护 \(\mathtt{dp}\) 转移方式(矩阵);改写一个可以区间合并的 \(\mathtt{dp}\)。
这里只讲区间合并的 \(\mathtt{dp}\)。类似地,我们在区间 \([l,r]\) 上维护四个变量:\(l,r\) 两个端点都建立关系的最小值;\(l,r\) 两个端点中,\(l\) 或 \(r\) 可以不 建立关系的最小值;\(l,r\) 均 可以不 建立关系的最小值。这四个变量是一层一层想到的。另外,需要注意 "可以不" 并不是 "不",这导致转移时可以不考虑某些情况,因为已经被包含在限制较小的变量中了。
具体就用一棵平衡树维护,还需要维护区间 \(\max,\min\) 来表示 \(\lvert a_n-a_{n-1}\rvert\)。最开始建树的时候要先排序。
#include <cstdio> #define print(x,y) write(x),putchar(y) template <class T> inline T read(const T sample) { T x=0; char s; bool f=0; while((s=getchar())>'9' or s<'0') f|=(s=='-'); while(s>='0' and s<='9') x=(x<<1)+(x<<3)+(s^48), s=getchar(); return f?-x:x; } template <class T> inline void write(const T x) { if(x<0) { putchar('-'); write(-x); return; } if(x>9) write(x/10); putchar(x%10^48); } #include <cstdlib> #include <iostream> #include <algorithm> using namespace std; typedef long long ll; const int maxn=2e5+5; int n,q,idx,rt; struct node { int mn,mx,siz,ls,rs,key,val; ll ans,bl,br,ba; void Put() { printf("VALUE %d %d %d\n",mn,mx,val); printf("%lld %lld %lld %lld\n",ba,bl,br,ans); } } t[maxn]; int NewNode(int x) { t[++idx].siz=1; t[idx].ls=t[idx].rs=0; t[idx].key=rand(); t[idx].siz=1; t[idx].val=t[idx].mn=t[idx].mx=x; t[idx].ba=t[idx].bl=t[idx].br=0; t[idx].ans=1e15; return idx; } void Update(const node &a,const node &b,node &o) { node t=o; int inc=b.mn-a.mx; t.ba=min(a.bl+b.br,a.ba+b.ba+inc); t.bl=min(a.bl+b.ans,a.ba+b.bl+inc); t.br=min(a.ans+b.br,a.br+b.ba+inc); t.ans=min(a.ans+b.ans,a.br+b.bl+inc); o=t; } void pushUp(int o) { if(!o) return; int l=t[o].ls,r=t[o].rs; t[o].siz=t[l].siz+t[r].siz+1; t[o].mn=t[o].mx=t[o].val; t[o].ba=t[o].bl=t[o].br=0; t[o].ans=1e15; if(l) Update(t[l],t[o],t[o]),t[o].mn=t[l].mn; if(r) Update(t[o],t[r],t[o]),t[o].mx=t[r].mx; } void split(int o,int k,int &x,int &y) { if(!o) x=y=0; else { if(t[o].val<=k) x=o, split(t[o].rs,k,t[o].rs,y); else y=o, split(t[o].ls,k,x,t[o].ls); pushUp(o); } } int merge(int x,int y) { if(!x or !y) return x|y; if(t[x].key<t[y].key) { t[x].rs=merge(t[x].rs,y); pushUp(x); return x; } else { t[y].ls=merge(x,t[y].ls); pushUp(y); return y; } } int Val[maxn]; int main() { srand(20210907); int a,b,c; for(int T=read(9);T;--T) { n=read(9),q=read(9); rt=idx=0; for(int i=1;i<=n;++i) Val[i]=read(9); sort(Val+1,Val+n+1); for(int i=1;i<=n;++i) rt=merge(rt,NewNode(Val[i])); while(q--) { int x,y; x=read(9),y=read(9); if(x==1) { split(rt,y-1,a,b); rt=merge(merge(a,NewNode(y)),b); } else { split(rt,y-1,a,b); split(b,y,b,c); b=merge(t[b].ls,t[b].rs); rt=merge(merge(a,b),c); } print(t[rt].ans>1e14?0:t[rt].ans,'\n'); } } return 0; }
首先将小矮人按照 \(a_i+b_i\) 从小到大排序。为什么捏?不妨令 \(H-(a_i+b_i)\) 是第 \(i\) 个小矮人的逃生高度,对于相邻小矮人 \(i,j\),如果我们期望 两个小矮人都逃出去,那么放逃生高度越大的小矮人越优。
但问题是有可能小矮人不逃出去更优,可以令 \(dp_i\) 为逃出 \(i\) 个小矮人后可以垒出的最大高度,这是一个 \(\mathcal O(n^2)\) 的 \(\mathtt{dp}\)。
更优的做法是反悔贪心。先依次使可以逃出的小矮人逃出,并用优先队列记录它的 \(a\),到某个无法逃出的小矮人 \(i\) 时,我们考虑这样操作:找到队列中最大的 \(a\),如果踢回这个小矮人能使 \(i\) 逃出,且 \(\max a>a_i\),我们就把这个小矮人 \(j\) 换成 \(i\)。
首先 \(j\) 到 \(i\) 这段的小矮人肯定是不受影响的,如果 \(j\) 能够使之间的小矮人逃出就早换了。对于之后的小矮人,垒出的高度变高了,解也就变优了。这是 \(\mathcal O(n\log n)\) 的。
#include <cstdio> #define print(x,y) write(x),putchar(y) template <class T> inline T read(const T sample) { T x=0; char s; bool f=0; while((s=getchar())>'9' or s<'0') f|=(s=='-'); while(s>='0' and s<='9') x=(x<<1)+(x<<3)+(s^48), s=getchar(); return f?-x:x; } template <class T> inline void write(const T x) { if(x<0) { putchar('-'); write(-x); return; } if(x>9) write(x/10); putchar(x%10^48); } #include <queue> #include <algorithm> using namespace std; const int maxn=2005; int n; struct node { int a,b; bool operator < (const node &t) const { return a+b<t.a+t.b; } } s[maxn]; priority_queue <int> q; int main() { n=read(9); for(int i=1;i<=n;++i) s[i]=(node){read(9),read(9)}; sort(s+1,s+n+1); int h=read(9); long long sum=0; int ans=0; for(int i=1;i<=n;++i) sum+=s[i].a; for(int i=1;i<=n;++i) if(sum+s[i].b>=h) { sum-=s[i].a,++ans; q.push(s[i].a); } else { if(!q.empty() and sum+q.top()+s[i].b>=h and q.top()>s[i].a) { sum+=q.top()-s[i].a; q.pop(); q.push(s[i].a); } } print(ans,'\n'); return 0; }
给你多棵内向基环树,对于每个点,求出有多少个点走 \(\le k\) 步能走到这个点。
感觉自己的脑子里根本不存在差分这个东西,好几次想用树链剖分硬艹了…
先找出环,\(\rm dfs\) 每个环上点的子树,用一个栈动态维护这个节点到根的节点,这样就可以找到走 \(k+1\) 才能走到的那个祖先。
然后考虑点对环的贡献。考虑破环为链,随便选一个点作为 \(rt_0\)(链的起点)。如果能贡献整个环,就将 \(rt_0\) 的差分数组加一,反之计算出它在环上贡献的一段区间 \([l,r]\),需要注意有 \(l>r\) 的情况,这时需要将 \(rt_0\) 的差分数组加一。
#include <cstdio> #define print(x,y) write(x),putchar(y) template <class T> inline T read(const T sample) { T x=0; char s; bool f=0; while((s=getchar())>'9' or s<'0') f|=(s=='-'); while(s>='0' and s<='9') x=(x<<1)+(x<<3)+(s^48), s=getchar(); return f?-x:x; } template <class T> inline void write(const T x) { if(x<0) { putchar('-'); write(-x); return; } if(x>9) write(x/10); putchar(x%10^48); } #include <vector> using namespace std; const int maxn=5e5+5; int n,k,f[maxn],ans[maxn]; int vis[maxn],stk[maxn],tp; int dep[maxn],siz; vector <int> e[maxn],rt; void dfs(int u,int root) { if(!vis[u]) vis[u]=1; stk[++tp]=u; if(tp>k+1) if(stk[tp-k-1]^rt[root]) --ans[stk[tp-k-1]]; for(auto v:e[u]) { if(vis[v]==2) continue; dep[v]=dep[u]+1; dfs(v,root); if(u^rt[root]) ans[u]+=ans[v]; } --tp; if(dep[u]-1<=k) { if(dep[u]-1+siz>k+1) { ++ans[rt[root]]; --ans[rt[(root+k-dep[u]+2+siz)%siz]]; if((root+k-dep[u]+2+siz)%siz<=root) ++ans[rt[0]]; } else ++ans[rt[0]]; } if(rt[root]^u) ++ans[u]; } void work(int x) { rt.clear(); while(vis[x]^2) { if(vis[x]) rt.push_back(x); ++vis[x]; x=f[x]; } siz=rt.size(); for(int i=0;i<rt.size();++i) dep[rt[i]]=1,dfs(rt[i],i); for(int i=1;i<rt.size();++i) ans[rt[i]]+=ans[rt[i-1]]; } int main() { n=read(9),k=read(9); for(int i=1;i<=n;++i) { f[i]=read(9); e[f[i]].push_back(i); } for(int i=1;i<=n;++i) if(!vis[i]) work(i); for(int i=1;i<=n;++i) print(ans[i],'\n'); return 0; }
有两个长度为 \(K\) 的数列 \(a,b\),满足 \(\sum a_i=N,\sum b_i=M\)。
定义权值 \(P\) 为
\[\prod \min\{a_i,b_i\} \]你需要求出所有可能数列组合的权值之和。取模 \(998244353\)。
多组数据,\(T\le 100,N,M,K\le 5\cdot 10^5\)。
定义 \(dp_{i,j,k}\) 为在第 \(i\) 位,数列 \(a\) 数字和为 \(j\),数列 \(b\) 数字和为 \(k\) 的权值之和。那么有:
\[dp_{i,j,k}=dp_{i,j,k}+dp_{i-1,j-x,k-y}\cdot \min\{x,y\} \]这是 \(\mathcal O(n^4k)\) 的。具体优化可以枚举 \(\min\{x,y\}\),后两维的前缀和即可,应该还要再容斥一下,就有 \(\mathcal O(n^3k)\) 了。
我们可以思考权值的组合意义 —— 定义 \(\{c\}\) 满足 \(\forall i,c_i\le \min\{a_i,b_i\}\),那么对于固定的 \(\{a\},\{b\}\),所有合法的 \(\{c\}\) 的个数即为权值!
转化一下,对于每种 \(\{c\}\),计算 \(\{a\},\{b\}\) 的种类数。
枚举 \(s=\sum c_i\),为了保证条件成立,可以先令 \(c_i=a_i=b_i\),再将 \(N-s\),\(M-s\) 填入 \(K\) 个位置(插板法),就有:
\[\text{Ans}=\sum_{s=0}^{\min\{N-K,M-K\}}\binom{s+K-1}{K-1}\cdot \binom{N-s-1}{K-1}\cdot \binom{M-s-1}{K-1} \]注意初始时先将 \(1\) 填入每一个位置,所以后面两个组合数的上面本来还要加个 \(K\),就抵消掉了。