【做题记录】HZOJ 多校-dp
A. Multitest Generator
考虑一个长为 \(m(m\ge 2)\) 的序列 \(b\),我们显然可以令 \(b_1=1,b_2=m-2\) 来使它变成 multitest。于是我们只需要判断能否使用 \(0\) 次或 \(1\) 次操作使其变成 multitest。
首先考虑 \(0\) 次,也就是它本身就是个 multitest。设 \(f_i\) 表示 \(i\sim n\) 这段后缀中有多少个 test,若不合法则 \(f_i=0\),可以 DP 出来。于是 \(i\) 的答案为 \(0\) 的充要条件即为 \(a_i=f_{i+1}\)。
考虑 \(1\) 次。首先如果 \(f_{i+1}\ne0\),那么我们可以直接修改 \(a_i\) 来满足要求。而如果 \(f_{i+1}=0\),我们则希望通过一次修改将 \(i+1\sim n\) 这段后缀变成 \(a_i\) 个 test。考虑如果能变成 \(x\) 个 test,则一定可以变成 \(x-1\) 个 test,于是设 \(g_i\) 表示通过一次修改能使 \(i\sim n\) 最多变成多少个 test,则有:
也就是分别考虑是否修改 \(a_i\)。于是若 \(g_{i+1}\ge a_i\) 那么 \(i\) 的答案为 \(1\),否则答案为 \(2\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=3e5+5;
int T,n,a[maxn],f[maxn],hp[maxn],g[maxn];
il void solve(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
f[n+1]=hp[n+1]=g[n+1]=0;
for(int i=n;i;i--){
f[i]=i+a[i]==n?1:i+a[i]>n||!f[i+a[i]+1]?0:f[i+a[i]+1]+1;
hp[i]=max(hp[i+1],f[i]);
g[i]=max(i+a[i]>n?0:g[i+a[i]+1]+1,hp[i+1]+1);
}
for(int i=1;i<n;i++){
cout<<(a[i]==f[i+1]?0:f[i+1]||g[i+1]>=a[i]?1:2)<<' ';
}
cout<<'\n';
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>T;
while(T--){
solve();
}
return 0;
}
}
int main(){return asbt::main();}
B. Bitwise Slides
记 \(s_i\) 为前缀异或和数组。则对于每一时刻,都有三个数的异或和为 \(s_i\)。又因为有两个相等,所以必然有一个值为 \(s_i\)。
设 \(dp_{i,j}\) 表示进行完第 \(i\) 次操作后那两个相等的数值为 \(j\) 的方案数。于是有转移:
-
\(j=s_{i-1}\),\(dp_{i,j}\gets 3dp_{i-1,j}\)。
-
\(j=s_i\),\(\begin{cases}dp_{i,j}\gets dp_{i-1,j}\\dp_{i,s_{i-1}}\gets2dp_{i,j}\end{cases}\)。
-
\(else\),\(dp_{i,j}\gets dp_{i-1,j}\)。
发现每次 DP 值改变的只有 \(dp_{i,s_{i-1}}\)。用 map 维护即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pii pair<int,int>
#define fir first
#define sec second
using namespace std;
namespace asbt{
const int maxn=2e5+5,mod=1e9+7;
il int pls(int x,int y){
return x+y<mod?x+y:x+y-mod;
}
il void add(int &x,int y){
x=pls(x,y);
}
il int mns(int x,int y){
return x<y?x-y+mod:x-y;
}
il void sub(int &x,int y){
x=mns(x,y);
}
int T,n,a[maxn];
map<int,int> dp;
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>T;
while(T--){
cin>>n;
dp.clear();
dp[0]=1;
for(int i=1,x;i<=n;i++){
cin>>x;
a[i]=a[i-1]^x;
dp[a[i-1]]=(dp[a[i-1]]*3ll+dp[a[i]]*2ll)%mod;
}
int ans=0;
for(pii i:dp){
add(ans,i.sec);
}
cout<<ans<<'\n';
}
return 0;
}
}
int main(){return asbt::main();}
C. Cows and Cool Sequences
考虑题目约束的实质,设那一段连续数字的开头为 \(t\),于是有 \(ty+\frac{y(y-1)}{2}=x\),即 \(\frac{2x}{y}-y=2t-1\),也就是要求 \(\frac{2x}{y}-y\) 为奇数。
考虑将每个数 \(x\) 的所有因子 \(2\) 都拆出来,也就是 \(x=2^{v(x)}\times p(x)\),其中 \(p(x)\) 为 \(x\) 的最大的奇约数。于是我们的要求即为 \(2^{v(x)-v(y)+1}\times\frac{p(x)}{p(y)}-y\) 为奇数。首先有 \(p(y)|p(x)\),然后对 \(y\) 的奇偶性进行分类讨论:
-
\(y\) 为奇数,则要求 \(2^{v(x)-v(y)+1}\times\frac{p(x)}{p(y)}\) 为偶数,即 \(v(x)-v(y)+1>0\),也就是 \(v(x)>-1\),即 \(v(x)\) 任取。
-
\(y\) 为偶数,则要求 \(2^{v(x)-v(y)+1}\times\frac{p(x)}{p(y)}\) 为奇数,即 \(v(x)-v(y)+1=0\),即 \(v(y)=v(x)+1\)。
于是我们可以得出 \((x,y)\) 合法的充要结论:\(p(y)|p(x)\land[v(y)=0\lor v(y)=v(x)+1]\)。考虑 DP,设 \(f_i\) 表示考虑到 \(i\) 且没有修改 \(i\),\(1\sim i\) 合法的最小修改次数。于是有转移:
答案即为 \(\min_{i=1}^{n}\{f_i+n-i\}\)。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=5e3+5,inf=1e9;
int n,a[maxn],b[maxn],f[maxn];
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
while(a[i]%2==0){
a[i]>>=1,b[i]++;
}
}
f[1]=0;
for(int i=2;i<=n;i++){
f[i]=i-1;
for(int j=1;j<i;j++){
if(a[j]%a[i]==0&&(b[i]==b[j]+i-j||b[i]<=i-j-1)){
f[i]=min(f[i],f[j]+i-j-1);
}
}
}
int ans=inf;
for(int i=1;i<=n;i++){
ans=min(ans,f[i]+n-i);
}
cout<<ans;
return 0;
}
}
signed main(){return asbt::main();}
D. Reality Show
首先考虑如果已知选了哪些选手,怎么算利润。考虑从下往上合并,每次将下面一层的合并到上面一层。然而题目要求的顺序是单调不增的,很难这样处理,考虑反过来,按照编号从大到小出处理一个单调不减的序列即可。于是可以 DP,设 \(f_{i,j,k}\) 表示考虑到了第 \(i\) 个人,此时第 \(j\) 层有 \(k\) 个人且第 \(j\) 层上面没有人的最大利润。于是有转移:
- 加入第 \(i\) 个人:
- 合并第 \(j\) 层:
时空复杂度都是 \(O(n^3)\)。先将第一维滚掉,然后又是喜闻乐见的有效状态数优化。考虑第二种转移,因为每一层的人数最多是上一层的一半,所以有 \(k\le\frac{n}{2^{j-l_i}}\)。于是有效的状态数是 \(O(n^2)\) 的,时间复杂度也就是 \(O(n^2)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=5e3+5;
int n,m,a[maxn],b[maxn],c[maxn],f[maxn][maxn];
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
cin>>b[i];
}
for(int i=1;i<=n+m;i++){
cin>>c[i];
}
memset(f,-0x3f,sizeof(f));
for(int i=1;i<=n+m;i++){
f[i][0]=0;
}
for(int i=n;i;i--){
for(int j=n;j;j--){
f[a[i]][j]=max(f[a[i]][j],f[a[i]][j-1]-b[i]+c[a[i]]);
}
for(int j=a[i];j<n+m;j++){
for(int k=0;k<=n>>(j-a[i]);k++){
f[j+1][k>>1]=max(f[j+1][k>>1],f[j][k]+(k>>1)*c[j+1]);
}
}
}
cout<<f[n+m][0];
return 0;
}
}
int main(){return asbt::main();}
E. Random Task
假设 \([x+1,2x]\) 中满足条件的有 \(a\) 个,那么在 \([x+2,2x+2]\) 中,\(2x+2\) 和 \(x+1\) 二进制下 \(1\) 的个数是相同的,\([x+2,2x]\) 这一部分是相同的,又多了一个 \(2x+1\),因此满足条件的一定 \(\ge a\)。于是可以二分,将 \([mid+1,2mid]\) 中满足条件的数量数位 DP 出来即可。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
int m,kk,dig[67],f[67][67];
il int dfs(int pos,int sum,bool limit){
if(!limit&&~f[pos][sum]){
return f[pos][sum];
}
if(!pos){
return sum==kk;
}
if(limit){
if(dig[pos]){
return dfs(pos-1,sum+1,1)+dfs(pos-1,sum,0);
}else{
return dfs(pos-1,sum,1);
}
}else{
return f[pos][sum]=dfs(pos-1,sum+1,0)+dfs(pos-1,sum,0);
}
}
il int solve(int x){
int cnt=0;
while(x){
dig[++cnt]=x&1,x>>=1;
}
return dfs(cnt,0,1);
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>m>>kk;
memset(f,-1,sizeof(f));
int l=1,r=1e18;
while(l<r){
int mid=(l+r)>>1;
if(solve(mid<<1)-solve(mid)>=m){
r=mid;
}else{
l=mid+1;
}
}
cout<<l;
return 0;
}
}
signed main(){return asbt::main();}
F. [CERC2017] Donut Drone
从左往右走的过程显然具有结合律,带修改,考虑线段树。对于线段树上的一个区间,维护它最左边一列的值和从左边的第 \(i\) 行会走到右边的哪一行即可,单次合并 \(O(r)\)。考虑询问,先走到第 \(c\) 列,然后用快速幂计算若干个整段,最后再将后面一点加上即可。时间复杂度 \(O(mr(\log\frac{k}{c}+\log c))\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
const int maxn=2e3+5;
int n,m,q,a[maxn][maxn];
struct node{
int f[maxn],g[maxn];
node(){}
node(int x){
for(int i=1;i<=n;i++){
f[i]=i,g[i]=a[i][x];
}
}
il node operator+(const node &x)const{
node res;
for(int i=1;i<=n;i++){
res.g[i]=g[i];
int t=max({x.g[f[i]],x.g[f[i]==n?1:f[i]+1],x.g[f[i]==1?n:f[i]-1]});
if(t==x.g[f[i]]){
res.f[i]=x.f[f[i]];
}else if(t==x.g[f[i]==n?1:f[i]+1]){
res.f[i]=x.f[f[i]==n?1:f[i]+1];
}else{
res.f[i]=x.f[f[i]==1?n:f[i]-1];
}
}
return res;
}
}tr[maxn<<2];
il void pushup(int id){
tr[id]=tr[lid]+tr[rid];
}
il void build(int id,int l,int r){
if(l==r){
tr[id]=l;
return ;
}
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
pushup(id);
}
il void upd(int id,int l,int r,int p){
if(l==r){
tr[id]=p;
return ;
}
int mid=(l+r)>>1;
if(p<=mid){
upd(lid,l,mid,p);
}else{
upd(rid,mid+1,r,p);
}
pushup(id);
}
il node query(int id,int L,int R,int l,int r){
if(L>=l&&R<=r){
return tr[id];
}
int mid=(L+R)>>1;
if(r<=mid){
return query(lid,L,mid,l,r);
}else if(l>mid){
return query(rid,mid+1,R,l,r);
}else{
return query(lid,L,mid,l,r)+query(rid,mid+1,R,l,r);
}
}
il void qpow(node x,int y,node &res){
while(y){
if(y&1){
res=res+x;
}
y>>=1,x=x+x;
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
}
build(1,1,m);
int xx=1,yy=1;
cin>>q;
while(q--){
string opt;
cin>>opt;
if(opt[0]=='m'){
int k;
cin>>k;
if(k<=m-xx){
node res=query(1,1,m,xx,xx+k);
xx+=k,yy=res.f[yy];
}else{
node res=query(1,1,m,xx,m);
k-=m-xx;
qpow(tr[1],k/m,res);
k%=m;
if(k){
res=res+query(1,1,m,1,k);
}
xx=k?k:m,yy=res.f[yy];
}
cout<<yy<<' '<<xx<<'\n';
}else{
int x,y,k;
cin>>x>>y>>k;
a[x][y]=k;
upd(1,1,m,y);
}
}
return 0;
}
}
int main(){return asbt::main();}
G. [COCI2020-2021#3] Selotejp
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=(1<<10)+5,inf=1e9;
int n,m,f[maxn][17][maxn];
string s[maxn];
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>s[i];
s[i]=" "+s[i];
}
memset(f,0x3f,sizeof(f));
f[0][m][0]=0;
for(int x=1;x<=n;x++){
for(int y=1;y<=m;y++){
int nx=y==1?x-1:x,ny=y==1?m:y-1;
for(int S=0;S<1<<m;S++){
if(s[x][y]=='.'){
if(S>>(y-1)&1){
continue;
}
f[x][y][S]=min(f[nx][ny][S],f[nx][ny][S|1<<(y-1)]);
}else{
if(S>>(y-1)&1){
f[x][y][S]=min(f[nx][ny][S],f[nx][ny][S^1<<(y-1)]+1);
}else{
f[x][y][S]=min(f[nx][ny][S],f[nx][ny][S|1<<(y-1)])+(y==1||s[x][y-1]=='.'||(S>>(y-2)&1));
}
}
}
}
}
int ans=inf;
for(int S=0;S<1<<m;S++){
ans=min(ans,f[n][m][S]);
}
cout<<ans;
return 0;
}
}
int main(){return asbt::main();}
H. [BalticOI 2020 Day2] 村庄
在 DP 题单中做出 Ad-hoc 是一种怎样的体验呢
最小值比较正常,考虑一个菊花,必然要使所有叶子轮换,根去其中一个叶子,答案为 \(leaf\times2\)。考虑一棵树,对于每个点将传上来的儿子按照菊花的做法去做,如果没有儿子传上来就将自己传给父亲。注意要特判根没有父亲,加入任何一个儿子即可。
而最大值就不属于人类范畴了。首先考虑最大的答案是什么,不妨拆贡献,对于每条边,设其两侧的子树大小为 \(a\) 和 \(b\),则最多能有 \(2\min(a,b)\) 的贡献。考虑如何构造。考虑以重心为根,此时每个子树的大小都 \(<\frac{n}{2}\),如果我们让每个点都进入另一棵子树必然能满足条件,于是我们将每个点指向 \(dfn\) 比它大 \(\frac{n}{2}\) 的点即可。然而实际上并不需要以重心为根求 \(dfn\),因为显然以任何一点为根这样做都能满足以重心为根后每个点都指向另一个子树。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int n;
vector<int> e[maxn];
namespace p1{
int ans,p[maxn];
il bool dfs(int u,int fa){
int x=u,son=0;
for(int v:e[u]){
if(v==fa){
continue;
}
son=v;
if(dfs(v,u)){
p[x]=v,x=v,ans+=2;
}
}
if(x==u){
if(!fa){
ans+=2;
p[u]=p[son],p[son]=u;
}
return 1;
}else{
p[x]=u;
return 0;
}
}
il void work(){
dfs(1,0);
}
}
namespace p2{
int sz[maxn],ans,p[maxn],dfn[maxn],cnt,stk[maxn];
il void dfs(int u,int fa){
sz[u]=1,dfn[u]=cnt,stk[cnt++]=u;
for(int v:e[u]){
if(v==fa){
continue;
}
dfs(v,u);
sz[u]+=sz[v],ans+=min(sz[v],n-sz[v])<<1;
}
}
il void work(){
dfs(1,0);
for(int i=1;i<=n;i++){
p[i]=stk[(dfn[i]+n/2)%n];
}
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
e[u].pb(v),e[v].pb(u);
}
p1::work(),p2::work();
cout<<p1::ans<<' '<<p2::ans<<'\n';
for(int i=1;i<=n;i++){
cout<<p1::p[i]<<' ';
}
cout<<'\n';
for(int i=1;i<=n;i++){
cout<<p2::p[i]<<' ';
}
return 0;
}
}
signed main(){return asbt::main();}
I. [arc066_b]Xor Sum
设 \(f_{i,j}\) 表示考虑到第 \(i\) 位,目前的 \(v=j\) 的方案数。因为 \(v\ge u\),所以我们只用保证 \(v\le n\) 即可。考虑哪些 \(j'\) 可以转移到 \(j\):
-
填两个 \(0\),\(2j'=j\)。
-
填一个 \(0\) 和一个 \(1\),\(j'+(j'+1)=j\)。
-
填两个 \(1\),\(2(j'+1)=j\)。
这些转移全都是 \(f_{i,j}\gets f_{i-1,j'}\)。因为三个 \(j'\) 中必然有两个是相同的,而另一个的差只有 \(1\),所以总的状态数是 \(O(\log n)\) 的。第一维可以直接滚掉,再随便用个什么 STL 维护一下 DP 数组,时间复杂度是 polylog 的。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int mod=1e9+7;
il int pls(int x,int y){
return x+y<mod?x+y:x+y-mod;
}
il void add(int &x,int y){
x=pls(x,y);
}
il int mns(int x,int y){
return x<y?x-y+mod:x-y;
}
il void sub(int &x,int y){
x=mns(x,y);
}
ll n;
map<ll,int> dp;
il int dfs(ll x){
if(dp.count(x)){
return dp[x];
}
if(x==0){
return 1;
}
if(x==1){
return 2;
}
return dp[x]=pls(pls(dfs(x>>1),dfs((x-1)>>1)),dfs((x-2)>>1));
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
cout<<dfs(n);
return 0;
}
}
int main(){return asbt::main();}
J. [abc279_g]At Most 2 Colors
设 \(f_i\) 表示 \(1\sim i\) 合法染色的方案数。有转移:
- 前 \(k-1\) 个染的是同一种颜色,那么第 \(i\) 个格子可以染任何一种颜色。显然「前 \(k-1\) 个染的是同一种颜色」的方案数就等于考虑到 \(i-k+1\) 的方案数。
- 前 \(k-1\) 个染了两种颜色,那么第 \(i\) 个格子只能从其中选一种。考虑计算「前 \(k-1\) 个染了两种颜色」的方案数,\(f_{i-1}\) 表示 「前 \(k-1\) 个染了一到两种颜色」的方案数,\(f_{i-k+1}\) 表示「前 \(k-1\) 个染了一种颜色」的方案数,于是这个方案数就是 \(f_{i-1}-f_{i-k+1}\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e6+5,mod=998244353;
int n,m,kk,f[maxn];
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>kk>>m;
f[1]=m;
for(int i=2;i<=n;i++){
f[i]=(f[i-1]*2ll+f[max(i-kk+1,1)]*1ll*(m-2))%mod;
}
cout<<f[n];
return 0;
}
}
int main(){return asbt::main();}

浙公网安备 33010602011771号