海亮寄 7.14
前言
业精于勤荒于嬉,行成于思毁于随
正文(加餐)
动态规划(一)选讲,这是要 \(DAY^{-1}\) 的节奏
浅贴一个课件
关键词:区间 DP、“最后一次删除”、分类讨论、由数据范围得状压
再浅贴一下题单
T1
题意
给定长度为 \(n\) 的序列 \(a\),每次可以从头或尾选一个数放到序列 \(b\) (选后删除),求 \(\sum_{i=1}^{n} ({b_i}^{b_{i+1}} \bmod 998244353)\) 的最大值
题解
热身题,直接 \(f_{l,r}\) 起手,依照题意扩展区间即可
需要注意特判 \(0^0\) 的情况!
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e3+5,MOD=998244353;
int n,a[N],f[N][N][2];
inline void chkmx(int &x,int y){x=max(x,y);return;}
inline int qpow(int a,int b){
if(a==0&&b==0)return 0;
int res=1;
while(b){
if(b&1)res=res*a%MOD;
a=a*a%MOD;b>>=1;
}
return res;
}
inline void solve(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int len=2;len<=n;len++){
for(int l=1;l+len-1<=n;l++){
int r=l+len-1;
chkmx(f[l][r][0],max(f[l+1][r][0]+qpow(a[l],a[l+1])%MOD,f[l+1][r][1]+qpow(a[l],a[r])%MOD));
chkmx(f[l][r][1],max(f[l][r-1][0]+qpow(a[r],a[l])%MOD,f[l][r-1][1]+qpow(a[r],a[r-1])%MOD));
}
}
cout<<max(f[1][n][0],f[1][n][1])<<'\n';
return;
}
inline void clr(){
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
f[i][j][0]=f[i][j][1]=0;
return;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
int T;cin>>T;while(T--)solve(),clr();
return 0;
}
T2
初见端倪
题意
给定字符串 \(s\) 和数组 \(v\),若 \(v_i \ge 0\) 则可以删掉长度为 \(i\) 的回文子串,并获得 \(v_i\) 的收益。删掉子串后前后两部分拼接,可以多次操作,求最大收益
题解
(谁懂早上看题的时候被 T2 薄纱的破碎感)
首先一个显然的观测是对于 \(-1\) 的情况直接赋值为 \(- \infty\)
然而区间合并的操作并不好用朴素的区间 DP 维护
老师讲解了一个技巧,即用 \(g_{l,r,x}\) 辅助 \(f_{l,r}\) 转移
其中 \(f_{l,r}\) 表示区间的答案,\(g_{l,r,x}\) 是辅助数组,并且其含义往往是如下形式:
对于区间 \([l,r]\),考虑最后一次删除前,剩余部分 xxx 呈现若干可以用 \(x\) 来记录的性质的方案数(或者最值)
在这道题目中,定义 \(g_{l,r,x}\) 表示将区间 \([l,r]\) 删除至仅剩一个长度为 \(x\) 的回文子串的最大收益
显然有
所以,问题转化为对 \(g\) 数组进行转移
平凡的转移有:
自然有不凡的转移(也是状态中设置回文子串长度的原因),形如:
表示一个回文串的扩展,正确性显然是对的
剩下就是代码实现了
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=155,INF=0x3f3f3f3f3f3f3f3f;
int n,v[N],f[N][N],g[N][N][N],dp[N];char s[N];
inline void chkmx(int &x,int y){x=max(x,y);return;}
signed main(){
// freopen("in.txt","r",stdin);
// freopen("out.txt","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
int x;cin>>x;
v[i]=((x<0)?-INF:x);
}
cin>>(s+1);
memset(f,-0x3f,sizeof(f));memset(g,-0x3f,sizeof(g));
for(int i=1;i<=n;i++){
g[i][i][1]=0;g[i][i][0]=v[1];
f[i][i]=v[1];
}
for(int i=1;i<=n-1;i++){
g[i][i+1][0]=max(v[1]*2,((s[i]==s[i+1])?v[2]:-INF));
g[i][i+1][1]=v[1];
g[i][i+1][2]=((s[i]==s[i+1])?0:-INF);
f[i][i+1]=g[i][i+1][0];
}
for(int len=3;len<=n;len++){
for(int l=1;l+len-1<=n;l++){
int r=l+len-1;
for(int x=1;x<=n;x++){
if(s[l]==s[r])g[l][r][x]=g[l+1][r-1][x-2];
for(int k=l;k<=r-1;k++)
chkmx(g[l][r][x],max(g[l][k][x]+f[k+1][r],f[l][k]+g[k+1][r][x]));
chkmx(f[l][r],g[l][r][x]+v[x]);
}
g[l][r][0]=f[l][r];
}
}
dp[0]=0;
for(int i=1;i<=n;i++){
dp[i]=dp[i-1];
for(int j=1;j<=i;j++)chkmx(dp[i],dp[j-1]+f[j][i]);
}
cout<<dp[n]<<'\n';
return 0;
}
T3
渐入佳境
题意
给定一个长度为 \(n\) 的序列,每次操作可以取走一段区间,取走这段区间后左右两边会合并
设第 \(i\) 次操作取走区间的最大值为 \(mx_i\),最小值为 \(mn_i\),把整个序列取光用了 \(k\) 次操作
总代价为 \(a \times k + b \times \sum_{i=1}^{k} ({mx_i} - {mn_i})^2\)
求最小代价
题解
和 T2 差不多,依旧是 \(f_{l,r}\) 表示答案,维护一个辅助数组 \(g_{l,r,x,y}\) 表示区间 \([l,r]\) 删除至只剩余值域在 \([y,x]\) 的部分
转移比较显然,平凡转移好做,不凡转移形如向右扩展一个,刷表即可
代码
可以参考代码实现,感觉比较清晰!
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define lwbd lower_bound
using namespace std;
const int N=55;
int n,A,B,a[N],b[N],f[N][N],g[N][N][N][N];
inline void chkmn(int &x,int y){x=min(x,y);return;}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>A>>B;
for(int i=1;i<=n;i++)cin>>a[i],b[i]=a[i];
sort(b+1,b+n+1);
int T=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;i++)a[i]=lwbd(b+1,b+T+1,a[i])-b;
memset(f,0x3f,sizeof(f)),memset(g,0x3f,sizeof(g));
for(int i=1;i<=n;i++)g[i][i][a[i]][a[i]]=0,f[i][i]=A;
for(int len=1;len<=n;len++){
for(int l=1;l+len-1<=n;l++){
int r=l+len-1;
for(int x=1;x<=T;x++){
for(int y=x;y<=T;y++){
chkmn(g[l][r][min(x,a[r])][max(y,a[r])],g[l][r-1][x][y]);
for(int k=l;k<=r-1;k++)
chkmn(g[l][r][x][y],min(f[l][k]+g[k+1][r][x][y],g[l][k][x][y]+f[k+1][r]));
}
}
for(int x=1;x<=T;x++)
for(int y=x;y<=T;y++)
chkmn(f[l][r],g[l][r][x][y]+A+B*(b[y]-b[x])*(b[y]-b[x]));
}
}
cout<<f[1][n]<<'\n';
return 0;
}
T4
尾杀开始(高效进阶乱入?)
题意
\(n\) 个带颜色方格排成一列,每种颜色用一个整数表示,相同颜色的方块可连成一个区域(如果两个相邻方块颜色相同,则这两个方块可连成一个区域),区域数量一共 \(m\) 个
每次操作可以任选一个区域消去。假设这个区域包含的方块数为 \(x\),那么这次操作可以得到 \(x^2\) 的分数。某个区域的方块消去之后,其右边的所有区域就会向左移动与左边的区域拼接在一起
求最高得分
题解
一模一样的套路,然而如果只是粗暴的定义 \(g_{l,r,x,y}\) 表示区间 \([l,r]\) 内剩余颜色块为 \(y\) 且长度为 \(x\) 的最大权值的话,时间复杂度上会很吃力
重要观测:可以钦定 \(y=a_r\),发现不会对答案造成影响,省去了极多的冗余转移!
平凡转移不多赘述,不凡转移如下:
代码
代码是简短的……
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=55,K=1e3+5;
int n,m,a[N],b[N],f[N][N],g[N][N][K];
inline void chkmx(int &x,int y){x=max(x,y);return;}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>m;
for(int i=1;i<=m;i++)cin>>a[i];
for(int i=1;i<=m;i++)cin>>b[i],n+=b[i];
memset(f,-0x3f,sizeof(f));memset(g,-0x3f,sizeof(g));
for(int i=1;i<=m;i++){
f[i][i]=b[i]*b[i];
for(int j=0;j<=b[i];j++)g[i][i][j]=(b[i]-j)*(b[i]-j);
}
for(int len=2;len<=m;len++){
for(int l=1;l+len-1<=m;l++){
int r=l+len-1;
for(int x=0;x<=n;x++){
if(a[l]==a[r]&&x>=b[l])chkmx(g[l][r][x],g[l+1][r][x-b[l]]);
for(int k=l;k<=r-1;k++)chkmx(g[l][r][x],f[l][k]+g[k+1][r][x]);
}
for(int k=l;k<=r-1;k++)chkmx(f[l][r],f[l][k]+f[k+1][r]);
for(int x=0;x<=n;x++)chkmx(f[l][r],g[l][r][x]+x*x);
}
}
cout<<f[1][m]<<'\n';
return 0;
}
T5
原题,甚至在北中还是自己讲的
题意
给定大小为 \(n\) 的树,要求在树上选 \(k\) 个点染成白色,其余点染成黑色,问黑点两两之间的
距离加上白点两两之间的距离的和最大是多少
题解
速切,过!
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e3+5;
int n,k,head[N],tot;
struct Edge{int to,nxt,val;}e[N<<1];
int sz[N],f[N][N];
inline void add(int u,int v,int w){
e[++tot]={v,head[u],w};head[u]=tot;
return;
}
inline void dfs(int u,int fa){
sz[u]=1;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to,w=e[i].val;
if(v==fa)continue;
dfs(v,u);sz[u]+=sz[v];
for(int k1=k;k1>=0;k1--){
for(int k2=max(k1-sz[u]+sz[v],0ll);k2<=min(sz[v],k1);k2++){
int val=k2*(k-k2)*w+(sz[v]-k2)*(n-k-sz[v]+k2)*w;
f[u][k1]=max(f[u][k1],f[u][k1-k2]+f[v][k2]+val);
}
}
}
return;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>k;
for(int i=1;i<=n-1;i++){
int u,v,w;cin>>u>>v>>w;
add(u,v,w),add(v,u,w);
}
dfs(1,0);
cout<<f[1][k]<<endl;
return 0;
}
T6
(不会算 \(f_{u,2}\) 是不是菜完了……)
题意
给定大小为 \(n\) 的树,若在某个点放一个士兵,则这个点及其相邻的点都会被控制,现在要控制整棵树,求最少放置的士兵个数以及对应的方案数
题解
记 \(f_{u,1/2/3}\) 分别表示 \(u\) 子树内已经合法,且 \(u\) 结点被 自我 / 儿子 / 父亲 控制,再记一个 \(g_{u,1/2/3}\) 分别对应上述限制条件下的方案数
\(f_{u,1/3}\) 直接朴素树形 DP 转移,而 \(f_{u,2}\) 是枚举子树前缀,合并子树进行转移
具体地,转移方程如下:
方案数直接考察最小值来源然后更新即可
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define vi vector<int>
#define pb push_back
using namespace std;
const int N=5e5+10,MOD=1032992941ll;
int n,f[N][4],g[N][4];vi G[N];
inline void ADD(int &x,int y){x=(x+y)%MOD;return;}
inline void MUL(int &x,int y){x=(x*y)%MOD;return;}
inline void dfs(int u,int fa){
f[u][1]=1ll;f[u][2]=N;f[u][3]=0ll;
g[u][1]=g[u][2]=g[u][3]=1ll;
int fv,gv;
for(int v:G[u]){
if(v==fa)continue;
dfs(v,u);
fv=min({f[v][1],f[v][2],f[v][3]}),gv=0ll;
if(f[v][1]==fv)gv+=g[v][1];
if(f[v][2]==fv)gv+=g[v][2];
if(f[v][3]==fv)gv+=g[v][3];
f[u][1]+=fv,MUL(g[u][1],gv);
fv=min({f[u][2]+f[v][1],f[u][2]+f[v][2],f[u][3]+f[v][1]}),gv=0ll;
if(f[u][2]+f[v][1]==fv)gv+=(g[u][2]*g[v][1]%MOD);
if(f[u][2]+f[v][2]==fv)gv+=(g[u][2]*g[v][2]%MOD);
if(f[u][3]+f[v][1]==fv)gv+=(g[u][3]*g[v][1]%MOD);
f[u][2]=fv,g[u][2]=gv%MOD;
f[u][3]+=f[v][2],MUL(g[u][3],g[v][2]);
}
return;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n-1;i++){
int u,v;cin>>u>>v;
G[u].pb(v),G[v].pb(u);
}
dfs(1,-1);
if(f[1][1]<f[1][2])cout<<f[1][1]<<'\n'<<g[1][1]<<'\n';
else if(f[1][1]>f[1][2])cout<<f[1][2]<<'\n'<<g[1][2]<<'\n';
else cout<<f[1][1]<<'\n'<<(g[1][1]+g[1][2])%MOD<<'\n';
return 0;
}
花絮:这份代码比较神秘,云落疑似因为调用大量的取模运算使得代码出现了精度误差。不过,以上代码是经过修正的,可以 AC!
T7
也是做上 IOI 的题目了
题意
给定 \(n\) 个点 \(m\) 条边的无向连通图,你需要删掉一些边,使得此图没有长度为偶数的简单环。删掉第 \(i\) 条边有 \(c_i\) 的花费,有些边又是不能删的,不能删的边形成图的一棵生成树。求最小花费
题解
先做一步转化,删除的求最小等价于保留求最大
一个显然的事实是,非树边在生成树上连接的两点,若距离为奇数,则一定不会被保留
换言之,一条边被保留的必要条件是其树上两点距离为偶数
然而还有奇环合并的问题(两个简单奇环如果有交,则一定会合成一个偶环)
所以问题转化为在树上进行一些链剖分,要求选中的链不存在交集,然后尽可能保留更大的权值
对一条链,决策其是否保留一定是在 LCA 处!
树形 DP 是显然的!然而树边的保留情况并不好维护
注意到结点度数是 \(\le 10\) 的,这明示我们使用状压
因此,状态设计是记 \(f_{u,S}\) 表示对于子树 \(u\),不考虑 \(S\) 集合中子树的最大保留权值
转移方程是一坨
前面三项和最后一项是好理解的,中间的西格玛表示 \(u \to lca \to v\) 的路径上分叉出去给出的贡献!
剩下的就是实现了,时间复杂度 \(O(m \times 2^{deg} + m \times n)\)
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define vi vector<int>
#define pb push_back
using namespace std;
const int N=1e3+5,M=5e3+5,K=10;
int n,m;vi G[N];int fa[N][K],dep[N];
vi vec[N];int id[N],rk[K],f[N][1<<K];
struct Edge{int u,v,w;}E[M];
inline void dfs(int u,int fath){
fa[u][0]=fath;dep[u]=dep[fath]+1;
for(int i=1;i<=9;i++)fa[u][i]=fa[fa[u][i-1]][i-1];
for(int v:G[u]){
if(v==fath)continue;
dfs(v,u);
}
return;
}
inline int LCA(int x,int y){
if(dep[x]<dep[y])swap(x,y);
for(int i=9;i>=0;i--)
if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
if(x==y)return x;
for(int i=9;i>=0;i--)
if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
return fa[x][0];
}
inline void solve(int u,int fath){
for(int v:G[u]){
if(v==fath)continue;
solve(v,u);
}
int t=0;
for(int v:G[u]){
if(v==fath)continue;
id[v]=(1<<t),rk[t++]=v;
}
for(int i=0;i<(1<<t);i++)
for(int j=0;j<t;j++)
if(!(i>>j&1))f[u][i]+=f[rk[j]][0];
for(int i:vec[u]){
int x=E[i].u,y=E[i].v,w=E[i].w;
if(x!=u){
w+=f[x][0];
while(fa[x][0]!=u){
w+=f[fa[x][0]][id[x]];
x=fa[x][0];
}
}
if(y!=u){
w+=f[y][0];
while(fa[y][0]!=u){
w+=f[fa[y][0]][id[y]];
y=fa[y][0];
}
}
for(int st=0;st<(1<<t);st++)
if((!(st&id[x]))&&(!(st&id[y])))
f[u][st]=max(f[u][st],f[u][st|id[x]|id[y]]+w);
}
return;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
int tol=0,all=0;
for(int i=1;i<=m;i++){
int u,v,w;cin>>u>>v>>w;
if(w==0)G[u].pb(v),G[v].pb(u);
else E[++tol]={u,v,w},all+=w;
}
dfs(1,0);
for(int i=1;i<=tol;i++){
int u=E[i].u,v=E[i].v,lca=LCA(u,v);
if((dep[u]+dep[v]-2*dep[lca])&1)continue;
vec[lca].pb(i);
}
solve(1,0);
cout<<all-f[1][0]<<'\n';
return 0;
}
T8
水题
题意
\(n\) 个点 \(m\) 条边的有向图,保证边是从编号小的连到编号大的。一条合法路径指从一个入度 \(0\) 的点走到 \(n\) 的路径,求被合法路径经过次数最多边对应的经过次数
题解
正反拓扑序维护 \(f\) 数组即可(DAG 上 DP)
什么?你问 \(f\) 数组是什么?\(f_i\) 表示到达 \(i\) 结点的路径条数
所以有——
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define vi vector<int>
#define pb push_back
using namespace std;
const int N=5e3+5,M=5e4+5;
int n,m,U[M],V[M],in1[N],in2[N],f[N],g[N];vi G1[N],G2[N];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>U[i]>>V[i];in1[V[i]]++,in2[U[i]]++;
G1[U[i]].pb(V[i]),G2[V[i]].pb(U[i]);
}
for(int i=1;i<=n;i++)
if(!in1[i])f[i]=1;
for(int u=1;u<=n;u++)for(int v:G1[u])f[v]+=f[u];
for(int i=1;i<=n;i++)
if(!in2[i])g[i]=1;
for(int u=n;u>=1;u--)for(int v:G2[u])g[v]+=g[u];
int ans=0;
for(int i=1;i<=m;i++)ans=max(ans,f[U[i]]*g[V[i]]);
cout<<ans<<'\n';
return 0;
}
T9
水题 again
题意
给定 \(n\) 个点 \(m\) 条边的有向无环图,边有边权,每个点上有一个物品,重量为 \(w_i\),价值为 \(v_i\)
乐乐要背着包从 \(1\) 号点出发到达 \(n\) 号点,当他背包中的物品重量为 \(a\),行走 \(b\) 的距离时,花
费的体力为 \(a \times b\),背包容量有上界 \(W\)
乐乐希望到达 \(n\) 时,背包中的物品价值最大,同时花费的体力最小,求出对应答案
题解
DAG 上跑背包,需要 BFS 先判可达性,然后拓扑排序 DP 即可
(怎么感觉后面的题都偏简单呢?)
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
#define fi first
#define se second
#define mkp make_pair
#define vi vector<int>
#define pb push_back
using namespace std;
const int N=1e3+5,M=2e4+5,INF=2e9;
int n,m,W,wei[N],val[N],U[M],V[M],head[N],tot;
struct Edge{int to,nxt,val;}e[M];
int in[N];bool vis[N];
pii f[N][N];
inline pii mxp(pii x,pii y){
if(x.fi!=y.fi)return x.fi>y.fi?x:y;
return x.se<y.se?x:y;
}
inline void chkmx(pii &x,pii y){x=mxp(x,y);return;}
inline void add(int u,int v,int w){
e[++tot]={v,head[u],w};head[u]=tot;
return;
}
inline void bfs(){
queue<int> q;q.push(1);
while(!q.empty()){
int u=q.front();q.pop();
if(vis[u])continue;
vis[u]=true;
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to;
if(vis[v])continue;
q.push(v);
}
}
return;
}
inline void topo(){
f[1][0]=mkp(0,0);
queue<int> q;q.push(1);
while(!q.empty()){
int u=q.front();q.pop();
if(!vis[u])continue;
for(int i=W;i>=0;i--)
if(i+wei[u]<=W)chkmx(f[u][i+wei[u]],mkp(f[u][i].fi+val[u],f[u][i].se));
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to,w=e[i].val;
if(!vis[v])continue;
for(int j=0;j<=W;j++){
int inc=f[u][j].fi,outc=f[u][j].se;
chkmx(f[v][j],mkp(inc,outc+j*w));
}
in[v]--;if(in[v]==0)q.push(v);
}
}
return;
}
signed main(){
freopen("travel.in","r",stdin);
freopen("travel.out","w",stdout);
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>m>>W;
for(int i=1;i<=n;i++)cin>>wei[i]>>val[i];
for(int i=1,w;i<=m;i++){
cin>>U[i]>>V[i]>>w;
add(U[i],V[i],w);in[V[i]]++;
}
bfs();
for(int i=1;i<=m;i++)
if(!vis[U[i]])in[V[i]]--;
// for(int i=1;i<=n;i++)cerr<<vis[i]<<' '<<in[i]<<endl;
for(int i=1;i<N;i++)
for(int j=0;j<N;j++)
f[i][j]=mkp(0,INF);
topo();
// for(int i=1;i<=n;i++){
// for(int j=0;j<=W;j++)cerr<<f[i][j].fi<<' ';
// cerr<<endl;
// }
pii ans=mkp(0,INF);
for(int i=0;i<=W;i++)chkmx(ans,f[n][i]);
cout<<ans.fi<<' '<<ans.se<<'\n';
return 0;
}
T10
一开始读错题,硬控自己 5min
题意
给出两个数 \(a,b\),求区间 \([a,b]\) 中满足各位数字之和能整除原数的数的个数
题解
数位 DP 的板子呐,维护数字之和,余数
外层先整一个 \(O(162)\) 的数字之和枚举,dfs 返回答案的时候简单 check 即可
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=20,K=165;
int n,m,a[N],S,f[N][K][K];
inline int dfs(int len,int sum,int rem,int lim){
if(sum+9*len<S)return 0;
if(!lim&&~f[len][sum][rem])return f[len][sum][rem];
if(!len)return sum==S&&rem==0;
int mx=lim?a[len]:9,res=0;
for(int i=0;i<=mx&&i+sum<=S;i++)
res+=dfs(len-1,sum+i,(rem*10+i)%S,lim&(i==a[len]));
return lim?res:f[len][sum][rem]=res;
}
inline int cal(int x){
int len=0;
while(x)a[++len]=x%10,x/=10;
int ans=0;
for(S=1;S<=len*9;S++){
memset(f,-1,sizeof(f));
ans+=dfs(len,0,0,1);
}
return ans;
}
signed main(){
int l,r;cin>>l>>r;cout<<cal(r)-cal(l-1)<<'\n';
return 0;
}
T11
题意
多次询问,第一行给出询问次数 \(T\)
每次询问给定 L,R,K,问区间 \([L,R]\) 内有多少数字满足,各数位的 LIS 长度恰好为 \(K\)
题解
首先要会二分求 LIS,即记 \(f_i\) 表示长度为 \(i\) 的 LIS 最靠前的最末下标位置
容易发现在 \(f\) 数组是严格单调递增的,并且 \(f\) 数组大小和值域范围规模都不大,所以直接将 \(f\) 数组中出现过的数状态压缩并放在 DP 数组中
然后我们分析试填数字 \(i\) 之后对状态的贡献
显然地,如果有更高位的 \(i\),直接换掉即可;否则,把 \(i\) 加到状态里(可以参考代码实现)
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=20,K=11;
int k,a[N],len,f[N][1<<K][K];
inline int nxt(int x,int S){
for(int i=x;i<=9;i++)
if(S&(1<<i))return (S^(1<<i))|(1<<x);
return S|(1<<x);
}
inline int ppc(int x){
int res=0;
while(x){res+=(x&1);x>>=1;}
return res;
}
inline int dfs(int pos,int st,bool lim,bool zero){
if(!pos)return ppc(st)==k;
if(lim&&~f[pos][st][k])return f[pos][st][k];
int mx=lim?9:a[pos],res=0;
for(int i=0;i<=mx;i++)
res+=dfs(pos-1,zero&&(i==0)?0:nxt(i,st),lim||i<mx,zero&&(i==0));
return lim?f[pos][st][k]=res:res;
}
inline int cal(int x){
memset(a,0,sizeof(a));len=0;
while(x){a[++len]=x%10;x/=10;}
return dfs(len,0,0,1);
}
inline void solve(){
int l,r;cin>>l>>r>>k;
cout<<cal(r)-cal(l-1)<<'\n';
return;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
memset(f,-1,sizeof(f));
int T;cin>>T;while(T--)solve();
return 0;
}
T12
这种神秘东西很 AT 了
《论 \(y \bmod x = x \oplus y\)》
题意
找出有多少整数对 \((x,y)\) 满足 \(L \le x \le y \le R ,\ y \pmod x = y \oplus x\),答案对 \(10^9+7\) 取模
题解
实际上模运算和位运算八竿子打不着,所以需要找一个中间商,叫做 \(y-x\)
注意到
前面的限制意味着 \(x \le y < 2x\)
后面的限制意味着 \(x\) 是 \(y\) 的子集
由于不能前缀差分维护,所以一次数位 DP 扫两个数,维护 \(x1,x2\) 表示是否卡到了上下界
剩下的就是随便搞搞就行的事咯!
代码
点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=65,MOD=1e9+7;
int l,r,f[N][2][2][2];
inline void ADD(int &x,int y){x=(x+y)%MOD;return;}
inline int dfs(int pos,bool x1,bool x2,bool w){
if(pos==-1)return 1;
if(~f[pos][x1][x2][w])return f[pos][x1][x2][w];
int L=x1?(l>>pos&1):0,R=x2?(r>>pos&1):1;
int res=0;
for(int i=L;i<=1;i++){
for(int j=i;j<=R;j++){
if(w&&i!=j)continue;
ADD(res,dfs(pos-1,x1&&(i==L),x2&&(j==R),w&&(j==0)));
}
}
return f[pos][x1][x2][w]=res;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>l>>r;
memset(f,-1,sizeof(f));
cout<<dfs(63,1,1,1)%MOD<<'\n';
return 0;
}
后记
世界孤立我任它奚落
完结撒花!

浙公网安备 33010602011771号