7.5 模拟赛
7.5 模拟赛
DP专题
A 涂色paint
明显的区间DP
转移方程:
\[f[i][j]=\begin{cases}
max(f[i+1][j],f[i][j-1])\;(c[i]==c[j])\\
max(f[i][k]+f[k+1][j])\;(c[i]!=c[j])
\end{cases}
\]
码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#include<deque>
#include<set>
#include<stack>
#include<bitset>
#include<cstring>
#define ll long long
using namespace std;
const int INF=0x3f3f3f3f,N=100010;
inline int read(){
int x=0,y=1;char c=getchar();
while (c<'0'||c>'9') {if (c=='-') y=-1;c=getchar();}
while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*y;
}
int n,w[N],s[N];
int t,h,q[N];
int f[N],g[N];
int main(){
n=read();
for(int i=n;i>=1;i--) w[i]=read();
for(int i=1;i<=n;i++) s[i]=s[i-1]+w[i];
h=0,t=0;
for(int i=1;i<=n;i++){
while(h<t&&s[q[h+1]]+g[q[h+1]]<=s[i]) ++h;
f[i]=f[q[h]]+1;
g[i]=s[i]-s[q[h]];
while(h<t&&s[q[t]]+g[q[t]]>=s[i]+g[i]) --t;
q[++t]=i;
}
printf("%d\n",f[n]);
return 0;
}
B 粉刷匠
预处理+分组背包
预处理出每个木板涂k次最多能涂对多少块,线性DP
用分组背包,每个木板为一组,每个木板涂多少次是1个单位
选就完了
码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#include<deque>
#include<set>
#include<stack>
#include<bitset>
#include<cstring>
#define ll long long
using namespace std;
const int INF=0x3f3f3f3f;
int n,m,t,mapp[55][2510];
int g[55][2510][55],f[2][2510];
inline int read(){
int x=0,y=1;char c=getchar();
while (c<'0'||c>'9') {if (c=='-') y=-1;c=getchar();}
while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*y;
}
int main(){
n=read(),m=read(),t=read();
for(int i=1;i<=n;i++) for(int j=1;j<=m;j++){
mapp[i][0]=0;
char a;cin>>a;
mapp[i][j]=mapp[i][j-1]+(a=='1');
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=1;k<=m;k++)
for(int q=j-1;q<k;q++)
g[i][j][k]=max(g[i][j][k],g[i][j-1][q]+max(mapp[i][k]-mapp[i][q],k-q-mapp[i][k]+mapp[i][q]));//涂0还是涂1
for(int i=1;i<=n;i++){
for(int j=1;j<=t;j++){
for(int k=0;k<=min(m,j);k++){
int op=(i%2)^1;
f[i&1][j]=max(f[i&1][j],f[op][j-k]+g[i][k][m]);
}
}
}
int ans=0;
for(int i=1;i<=t;i++) ans=max(ans,f[n&1][i]);
printf("%d\n",ans);
return 0;
}
C 花神的数论题
数位DP板子无压力
甚至不用记录前导零和原数
码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#include<deque>
#include<set>
#include<stack>
#include<bitset>
#include<cstring>
#define ll long long
using namespace std;
const ll INF=0x3f3f3f3f,Mod=10000007;
ll n,len,ans=1,a[55];
ll f[55][55];
inline ll read(){
ll x=0,y=1;char c=getchar();
while (c<'0'||c>'9') {if (c=='-') y=-1;c=getchar();}
while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*y;
}
ll dfs(int pos,int sum1,int lim){
if(pos>len){
if(sum1!=0) return sum1%Mod;
return 1;
}
if(!lim&&f[pos][sum1]!=-1) return f[pos][sum1]%Mod;
ll res=1;
int ret=lim?a[len-pos+1]:1;
for(int i=0;i<=ret;i++){
(res*=dfs(pos+1,sum1+(i==1),(lim&&i==ret)))%=Mod;
}
if(!lim) f[pos][sum1]=res;
return res;
}
ll part(ll x){
len=0;
while(x){a[++len]=x&1,x>>=1;}
memset(f,-1,sizeof f);
ll res=dfs(1,0,1);
return res;
}
int main(){
n=read();
printf("%lld\n",part(n));
return 0;
}
D 合法序列
注意到该题的k很小,最大也只能到4,所以我们可以枚举这k位,而这k位对整个序列合法性判断上的影响也只有16位,也可以直接枚举,最大枚举量为\(2^{2^4}\),即131072
我们每枚举到一个\(2^{2^k}\)的状态,就判断它是不是合法的,如果它不合法,那么就舍去这种情况,如果合法,就继续转移
转移方程方面,我们设f[i][S]为从第i位开始k位是状态S,我们在枚举出来的\(2^{2^k}\)的状态必然合法,那么我们把最后一个长度为k的串在f数组中标记为1
接下来就把这个枚举的序列不断向后移去转移
转移有点难
我们需要先把枚举的状态转移成上一个状态,即取该状态的后k-1位,然后左移一位,就是上一个状态,最后一位取1时函数值与最后一位取0时函数值之和就是该状态的函数值
用到了a或上b再异或上b就是把b是1的位在a中都设成0
码:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<string>
#include<cmath>
#include<vector>
#include<map>
#include<queue>
#include<deque>
#include<set>
#include<stack>
#include<bitset>
#include<cstring>
#define ll long long
using namespace std;
const int INF=0x3f3f3f3f,N=510,Mod=998244353;
int n,k,res=0;
int g[N],f[N][N];
inline int read(){
int x=0,y=1;char c=getchar();
while (c<'0'||c>'9') {if (c=='-') y=-1;c=getchar();}
while (c>='0'&&c<='9') x=x*10+c-'0',c=getchar();
return x*y;
}
int main(){
n=read(),k=read();
for(int i=0;i<(1<<k);i++){
for(int j=0;j<k;j++){
g[i]|=((i>>j)&1)*(1<<(k-j-1));
}
}//编号和状态位置相反
for(int i=0;i<(1<<(1<<k));i++){
int flg=1;
for(int j=0;j<=(1<<k)-k;j++){
int op=((1<<k)-1)&(i>>j);//取该状态的每个k位
op=g[op];
if(i&(1<<op)) continue;//合法吗
flg=0;
break;
}
if(!flg)continue;
memset(f,0,sizeof(f));
f[(1<<k)-1][i>>((1<<k)-k)]=1;
for(int j=(1<<k);j<n;j++){//从16开始往后dp
for(int l=0;l<(1<<k);l++){//枚举状态
if((i&(1<<g[l]))==0) continue;
int op=((l|(1<<(k-1)))^(1<<(k-1)))<<1;
//cout<<j<<" "<<l<<" "<<op<<endl;
f[j][l]=(f[j-1][op|1]+f[j-1][op])%Mod;
}
}
for(int j=0;j<(1<<k);j++) res=(res+f[n-1][j])%Mod;
}
printf("%d\n",res);
return 0;
}