省选集训 26 - DP 优化
[ABC214G] Three Permutations
正难则反,令 \(f_i\) 表示钦定 \(i\) 个条件不满足的方案数,则 \(ans=\sum_{i=0}^n(-1)^if_i(n-i)!\)。
将 \(p_i\) 和 \(q_i\) 连双向边,则问题转化为将每条边标号为两端点标号之一且不重复。
由于每个点的度数均为 \(2\),所以图一定构成了若干个环。
记已处理完的答案为 \(f\),已处理完的大小为 \(s\),当前处理环的大小为 \(c\),新的 \(f\) 为 \(g\)。
当 \(c=1\) 时显然有 \(g_i=f_{i-1}+f_i\)。
当 \(c\neq 1\) 时枚举 \(c\) 中选 \(i\) 条边,\(s\) 中选 \(j\) 条边,有 \(g_{i+j}\leftarrow h_{c,i}\times f_j\)。
\(h_{c,i}\) 表示大小为 \(c\) 的环选 \(i\) 条边的方案数,考虑如何计算。
将 \((u,v)\) 边拆成 \((u,w)\) 和 \((w,v)\),则选 \((u,w)\) 表示标 \(u\),选 \((w,v)\) 表示标 \(v\)。
问题转化为在大小为 \(2c\) 的环上选 \(i\) 条不相邻边的方案数,考虑钦定其中一条边是否选。
则 \(h_{c,i}=w_{2c-1,i}+w_{2c-3,i-1}\),\(w_{i,j}\) 表示 \(i\) 条边的链选 \(j\) 条不相邻边的方案数。
根据组合意义有 \(w_{i,j}=w_{i-1,j}+w_{i-2,j-1}\),初值 \(w_{1,0}=w_{2,0}=1\),\(w_{2,1}=2\)。
然后 DP 解决即可,复杂度证明:任意两点间贡献只会被统计一次,与树形 DP 类似。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define add(x,y) (x)=((x)+(y))%mod
const int N=6005,mod=1000000007;
vector<int> v[N];bool vis[N];
int n,num,now,ans,p[N],q[N],f[N],g[N],fac[N],w[N][N];
void dfs(int x){
vis[x]=1,num++;
for(auto y:v[x]) if(!vis[y]) dfs(y);
}
signed main(){
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
cin>>n,fac[0]=f[0]=w[1][1]=1,w[2][1]=2;
for(int i=1;i<N;i++) w[i][0]=1,fac[i]=fac[i-1]*i%mod;
for(int i=3;i<N;i++)
for(int j=1;j<=i;j++)
w[i][j]=(w[i-1][j]+w[i-2][j-1])%mod;
for(int i=1;i<=n;i++) cin>>p[i];
for(int i=1;i<=n;i++) cin>>q[i];
for(int i=1;i<=n;i++) v[p[i]].push_back(q[i]);
for(int i=1;i<=n;i++) v[q[i]].push_back(p[i]);
for(int i=1;i<=n;i++){
if(vis[i]) continue;
num=0,dfs(i),fill(g,g+num+now+1,0);
if(num==1){
for(int j=++now;j>=1;j--) add(f[j],f[j-1]);
continue;
}
for(int i=0;i<=num;i++){
int tmp=(w[num*2-1][i]+w[num*2-3][i-1])%mod;
for(int j=0;j<=now;j++) add(g[i+j],tmp*f[j]);
}
now+=num,copy(g,g+now+1,f);
}
for(int i=0,op=1;i<=n;i++,op=mod-op) add(ans,f[i]*op%mod*fac[n-i]);
cout<<ans<<"\n";
}
[USACO19FEB] Mowing Mischief P
首先有路径上的权值为相邻两点组成的矩形面积和。
然后发现这个玩意满足决策单调性,虽然单调性和一般题相比是反的。
按在二维 LIS 上的最大位置分层,这样每一层都是 \(x\) 单增 \(y\) 单减的。
然后发现每一个点都只能被前一层的一个区间转移到,线段树维护后再决策单调性即可。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define N 1000005
#define int long long
#define INF 0x3f3f3f3f3f3f3f3f
vector<int> tmp,lis[N];
int n,m,k,ans=INF,dp[N];
struct Node{int x,y;}a[N];
bool cmp(Node a,Node b){
if(a.x==b.x) return a.y<b.y;
return a.x<b.x;
}
struct BIT{
int tr[N];
void update(int x,int y){
while(x<=k) tr[x]=max(tr[x],y),x+=x&-x;
}
int query(int x,int res=0){
while(x) res=max(res,tr[x]),x-=x&-x;
return res;
}
}tr;
struct Segment_tree{
vector<int> pos,tr[N];
int n,cnt,root,ls[N],rs[N];
void build(int l,int r,int &p){
p=++cnt,ls[cnt]=rs[cnt]=0,tr[cnt].clear();
if(l==r) return;
int mid=(l+r)>>1;
build(l,mid,ls[p]),build(mid+1,r,rs[p]);
}
void init(vector<int> v){
pos=v,n=v.size(),root=cnt=0,build(0,n-1,root);
}
void update(int x,int l,int r,int p){
if(a[x].x>=a[pos[r]].x&&a[x].y>=a[pos[l]].y)
return tr[p].push_back(x);
if(a[x].x<=a[pos[l]].x||a[x].y<=a[pos[r]].y) return;
int mid=(l+r)>>1;
update(x,l,mid,ls[p]),update(x,mid+1,r,rs[p]);
}
void update(int x){update(x,0,n-1,root);}
int calc(int i,int j){
return dp[j]+(a[i].x-a[j].x)*(a[i].y-a[j].y);
}
void solve(int l,int r,int pl,int pr){
if(l>r) return;
int res=INF,pmid=pl,mid=(l+r)>>1,now=tmp[mid];
for(int i=pl;i<=pr;i++){
int tmp=calc(now,pos[i]);
if(tmp<res) res=tmp,pmid=i;
}
dp[now]=min(dp[now],res);
solve(l,mid-1,pmid,pr),solve(mid+1,r,pl,pmid);
}
void work(int l,int r,int p){
if(!tr[p].empty()) tmp=tr[p],solve(0,tmp.size()-1,l,r);
if(l==r) return;int mid=(l+r)>>1;
work(l,mid,ls[p]),work(mid+1,r,rs[p]);
}
void work(){return work(0,n-1,root);};
}SGT;
signed main(){
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++) scanf("%lld%lld",&a[i].x,&a[i].y);
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++){
int res=tr.query(a[i].y)+1;
tr.update(a[i].y,res),lis[res].push_back(i),m=max(m,res);
}
for(auto x:lis[1]) dp[x]=a[x].x*a[x].y;
for(int i=2;i<=m;i++){
SGT.init(lis[i-1]);
for(auto x:lis[i]) dp[x]=INF,SGT.update(x);
SGT.work();
}
for(auto x:lis[m]) ans=min(ans,dp[x]+(k-a[x].x)*(k-a[x].y));
printf("%lld\n",ans);
}
[USACO22OPEN] 262144 Revisited P
用的 @ANIG 的做法,但是 ta 讲得不是很清楚,所以来扩写一下题解。
令 \(f_{l,r}\) 表示区间 \([l,r]\) 的答案,直接区间 DP 是容易的,用决策单调性可以做到 \(O(n^2)\)。
但是区间 DP 的下限复杂度显然为 \(O(n^2)\) 没有前途,考虑拆分贡献。
令 \(g_{i,k}\) 表示使得 \(f_{l,i}\leq k\) 的最小 \(l\),当 \(a_i>k\) 时令 \(g_{i,k}=i+1\)。
这样 \(ans=\sum_{k=0}^{upV}\sum_{i-1}^n(g_{i,k}-1)\),\(upV\) 为答案值域,上界为 \(V+\lceil\log n\rceil\)。
为什么上界是这个?考虑极端情况 \(a_i\) 全部 \(=V\) 时,最优策略是分治下去然后每次合并两个分治区间,分治的层数至多为 \(\lceil\log n\rceil\),每层答案增加 \(1\),所以上界 \(V+\lceil\log n\rceil\)。
从小到大枚举 \(k\),当 \(k=a_i\) 时有 \(g_{i,k}=i\)。
当 \(k\neq a_i\) 时有 \(g_{i,k}=g_{g_{i,k-1}-1,k-1}\),即在当前段前面找一段最长的 \(\leq k-1\) 的合并起来。
在线段树里面维护 \(s_i=g_{i,k}-1\) 的值,这样过后 \(k\neq a_i\) 时相当于让 \(s_i\) 变成 \(s_{s_i}\),当 \(s_i\neq s_{s_i}\) 时有意义,考虑维护哪些 \(i\) 满足 \(s_i\neq s_{s_i}\),把 \(s_i\neq s_{s_i}\) 的 \(s_i\) 用 vector 记录下来,由于 \(s\) 序列单调,要用的时候可以直接线段树二分找出来是哪段区间。
修改 \(s_i\leftarrow s_{s_i}\) 时直接线段树区间覆盖,再看新的 \(s_i\) 要不要扔到 vector 里去,\(a_i=k\) 的地方修改过后因为 \(s_i\) 从 \(i\) 变为了 \(i-1\),所以原本 \(s_j=i\) 的地方后续都要跟着 \(s_i\) 一起往前跳,\(i\) 和 \(i-1\) 都应该扔进 vector 里去。
模拟实现上述过程,在每个 \(k\) 累加线段树维护出的答案,就能解决本题。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define N 1200005
#define int long long
#define all(v) v.begin(),v.end()
int n,m,ans,a[N];vector<int> dif,v[N];
struct Segment_tree{
int tr[N],mx[N],len[N],tag[N];
void build(int l=0,int r=n+1,int p=1){
len[p]=r-l+1,tag[p]=-1;
if(l==r) return tr[p]=mx[p]=l,void();
int mid=(l+r)>>1,ls=p<<1,rs=p<<1|1;
build(l,mid,ls),build(mid+1,r,rs);
tr[p]=tr[ls]+tr[rs],mx[p]=max(mx[ls],mx[rs]);
}
void pushdown(int &p,int &ls,int &rs){
if(tag[p]==-1) return;
work(ls,tag[p]),work(rs,tag[p]),tag[p]=-1;
}
int query(int x,int l=0,int r=n+1,int p=1){
if(l==r) return tr[p];
int mid=(l+r)>>1,ls=p<<1,rs=p<<1|1;pushdown(p,ls,rs);
return x<=mid?query(x,l,mid,ls):query(x,mid+1,r,rs);
}
int bound(int x,int l=0,int r=n+1,int p=1){
if(l==r) return l;
int mid=(l+r)>>1,ls=p<<1,rs=p<<1|1;pushdown(p,ls,rs);
return mx[ls]>=x?bound(x,l,mid,ls):bound(x,mid+1,r,rs);
}
void cover(int sl,int sr,int x,int l=0,int r=n+1,int p=1){
if(sl<=l&&r<=sr) return work(p,x);
int mid=(l+r)>>1,ls=p<<1,rs=p<<1|1;pushdown(p,ls,rs);
if(sl<=mid) cover(sl,sr,x,l,mid,ls);
if(sr>mid) cover(sl,sr,x,mid+1,r,rs);
tr[p]=tr[ls]+tr[rs],mx[p]=max(mx[ls],mx[rs]);
}
void work(int &p,int &x){tr[p]=x*len[p],mx[p]=tag[p]=x;}
}SGT;
signed main(){
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
cin>>n,SGT.build();
for(int i=1;i<=n;i++) cin>>a[i],m=max(m,a[i]+30);
for(int i=1;i<=n;i++) v[a[i]].push_back(i),ans+=i;
for(int i=1;i<=m;i++,ans+=SGT.tr[1]){
vector<int> tmp;
vector<tuple<int,int,int>> cov;
sort(all(dif)),dif.resize(unique(all(dif))-dif.begin());
for(auto it:dif){
int l=SGT.bound(it),r=SGT.bound(it+1)-1,x=SGT.query(it);
cov.emplace_back(l,r,x),tmp.push_back(x);
}
for(auto [l,r,x]:cov) SGT.cover(l,r,x);
dif.clear();
for(auto x:tmp) if(SGT.query(x)!=x) dif.push_back(x);
for(auto x:v[i]){
if(SGT.query(x)<x) continue;
SGT.cover(x,x,x-1),dif.push_back(x);
if(SGT.query(x-1)!=x-1) dif.push_back(x-1);
}
}
cout<<ans-(n+1)*m<<"\n";
}
[AGC017F] Zigzag
参考题解:https://www.luogu.com.cn/article/gqwnwmoe
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define lowbit(x) ((x)&-(x))
#define add(x,y) (x)=((x)+(y))%mod
const int N=20,mod=1000000007;
int n,m,k,u,ans,pre=0,now=1,s[N][N],dp[2][1<<(N-1)];
int main(){
ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr);
cin>>n>>m>>k,u=(1<<(n-1)),memset(s,-1,sizeof s);
for(int i=0,a,b,c;i<k;i++) cin>>a>>b>>c,s[a-1][b-1]=c;
for(int i=0;i<u;i++){
int &fl=(dp[0][i]=1);
for(int j=0;j<n-1;j++) fl&=(s[0][j]<0||s[0][j]==(i>>j&1));
}
for(int i=1;i<m;i++) for(int j=0;j<n-1;j++){
for(int k=0;k<u;k++){
if(s[i][j]!=0&&!(k>>j&1)){
int p=lowbit(k>>j)<<j;
add(dp[now][(k^p)|(1<<j)],dp[pre][k]);
}
if(s[i][j]!=1&&!(k>>j&1)) add(dp[now][k],dp[pre][k]);
if(s[i][j]!=0&&(k>>j&1)) add(dp[now][k],dp[pre][k]);
}
fill(dp[pre],dp[pre]+u,0),swap(now,pre);
}
for(int i=0;i<u;i++) add(ans,dp[pre][i]);return cout<<ans<<"\n",0;
}

浙公网安备 33010602011771号