CF82D Two out of Three
d p i , j dp_{i,j} dpi,j 表示第 i i i轮, j j j未选得到的最大值
p r e i , j , ( 0 / 1 / 2 ) pre_{i,j,(0/1/2)} prei,j,(0/1/2) 中 :
p r e i , j , 0 / 1 pre_{i,j,0/1} prei,j,0/1表示在 d p i , j dp_{i,j} dpi,j满足最大值时,这一轮选择的数的下标
p r e i , j , 2 pre_{i,j,2} prei,j,2表示 d p i , j dp_{i,j} dpi,j满足最大值时,前一轮剩下的数的下标
第 i i i轮从三个下标选数: j , 2 ∗ i , 2 ∗ i + 1 j,2*i,2*i+1 j,2∗i,2∗i+1 方程很好转移,pre记录决策
如果 n n n为奇数,只需要枚举第 ⌊ \lfloor ⌊ n 2 \frac{n}{2} 2n ⌋ \rfloor ⌋轮最后没选的数 加上即可
如果 n n n为偶数,只需要在最后随便加一个数,最后答案就是第 ⌊ \lfloor ⌊ n 2 \frac{n}{2} 2n ⌋ \rfloor ⌋轮
没选第 n + 1 n+1 n+1个数
// LUOGU_RID: 106060092
#include
#define ll long long
using namespace std;
int pre[1005][1005][3];
int n;
void prin(int tol,int bad){
if (!tol){
return;
}
prin(tol-1,pre[tol][bad][2]);
cout<<pre[tol][bad][0]<<" "<<pre[tol][bad][1]<<"\n";
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n;
vector<int> a;
a.push_back(0);
for (int i=1;i<=n;i++){
int x;
cin>>x;
a.push_back(x);
}
if (n==1){
cout<<a[1]<<"\n"<<1;
return 0;
}else if (n==2){
cout<<max(a[1],a[2])<<"\n"<<"1 2";
return 0;
}
if(n%2==0){
a.push_back(1e8);
}
vector<vector<int>> dp(n/2+5,vector<int>(n+5,1e9+1));
dp[1][1]=max(a[2],a[3]);pre[1][1][0]=2;pre[1][1][1]=3;pre[1][1][2]=1;
dp[1][2]=max(a[1],a[3]);pre[1][2][0]=1;pre[1][2][1]=3;pre[1][2][2]=2;
dp[1][3]=max(a[1],a[2]);pre[1][3][0]=1;pre[1][3][1]=2;pre[1][3][2]=3;
for (int i=2;i<=n/2;i++){
for (int j=1;j<2*i;j++){
// cout<
if(dp[i][j]>dp[i-1][j]+max(a[2*i],a[2*i+1])){
dp[i][j]=dp[i-1][j]+max(a[2*i],a[2*i+1]);
pre[i][j][0]=2*i;
pre[i][j][1]=2*i+1;
pre[i][j][2]=j;
}
if (dp[i][2*i]>dp[i-1][j]+max(a[2*i+1],a[j])){
dp[i][2*i]=dp[i-1][j]+max(a[2*i+1],a[j]);
pre[i][2*i][0]=j;
pre[i][2*i][1]=2*i+1;
pre[i][2*i][2]=j;
}
if (dp[i][2*i+1]>dp[i-1][j]+max(a[2*i],a[j])){
dp[i][2*i+1]=dp[i-1][j]+max(a[2*i],a[j]);
pre[i][2*i+1][0]=j;
pre[i][2*i+1][1]=2*i;
pre[i][2*i+1][2]=j;
}
}
}
if (n%2==0){
cout<<dp[n/2][n+1]<<"\n";
prin(n/2,n+1);
}else{
int pos=1;
for (int i=2;i<=n;i++){
if (dp[n/2][pos]+a[pos]>dp[n/2][i]+a[i]){
pos=i;
}
}
cout<<dp[n/2][pos]+a[pos]<<"\n";
prin(n/2,pos);
cout<<pos;
}
return 0;
}
CF847E Packmen
二分答案+贪心, c h e c k check check方法就是先把左边没跳完的跳完,再尽可能往右跳
#include
#define ll long long
using namespace std;
int main(){
// ios::sync_with_stdio(false);
// cin.tie(0);
// cout.tie(0);
int n;
cin>>n;
vector<char> s(n+1);
vector<int> pos,now;
for (int i=1;i<=n;i++){
cin>>s[i];
if (s[i]=='P') pos.push_back(i);
if (s[i]=='*') now.push_back(i);
}
int l=0,r=2e5,ans;
function<bool(int)> check=[&](int x){
int poss=0,noww=0;
while (poss<pos.size()){
int need=0,time=x;
// cout<
if (pos[poss]>now[noww]){
need=pos[poss]-now[noww];
// cout<
}
if (need>time){
return 0;
}
int maxn=max(time-need*2,(time-need)/2);
while (now[noww]<=pos[poss]+maxn&&noww<now.size()){
noww++;
}
if (noww==now.size()){
return 1;
}
poss++;
}
return 0;
};
// cout<<"kkk\n";
while (l<=r){
int mid=(l+r)/2;
if (check(mid)){
ans=mid;
r=mid-1;
}else{
l=mid+1;
}
}
cout<<ans<<"\n";
return 0;
}
CF855C Helga Hufflepuff’s Cup
树上染色 d p i , j , ( 0 / 1 / 2 ) dp_{i,j,(0/1/2)} dpi,j,(0/1/2)表示以 i i i为根的子树,选取了 j j j个特殊颜色的点, i i i节点颜色为 0:小于特殊颜色 k k k,1:等于特殊颜色 k k k,2:大于特殊颜色 k k k 的方案数
对于一个以 u u u节点为根的子树,考虑对其一个一个加孩子,因为满足乘法原理,所以加入一个新的孩子,就让之前累乘完的 u u u节点状态来进行转移
d p u , j + k , 0 dp_{u,j+k,0} dpu,j+k,0+= d p u , j , 0 ∗ ( d p v , k , 0 + d p v , k , 1 + d p v , k , 2 ) dp_{u,j,0}*(dp_{v,k,0}+dp_{v,k,1}+dp_{v,k,2}) dpu,j,0∗(dpv,k,0+dpv,k,1+dpv,k,2)
d p u , j + k , 1 dp_{u,j+k,1} dpu,j+k,1+= d p u , j , 1 ∗ d p v , k , 0 dp_{u,j,1}*dp_{v,k,0} dpu,j,1∗dpv,k,0
d p u , j + k , 2 dp_{u,j+k,2} dpu,j+k,2+= d p u , j , 2 ∗ ( d p v , k , 0 + d p v , k , 2 ) dp_{u,j,2}*(dp_{v,k,0}+dp_{v,k,2}) dpu,j,2∗(dpv,k,0+dpv,k,2)
但是在转移到 d p u , j + k , ( 0 / 1 / 2 ) dp_{u,j+k,(0/1/2)} dpu,j+k,(0/1/2)时,等号右边有关 d p u , j , ( 0 / 1 / 2 ) dp_{u,j,(0/1/2)} dpu,j,(0/1/2)的值会改变,所以我们先用一个辅助数组 g j + k , ( 0 / 1 / 2 ) g_{j+k,(0/1/2)} gj+k,(0/1/2)来储存转移后的状态
再加入一个孩子节点的转移完成后,再将 f f f的值赋给 d p dp dp
// LUOGU_RID: 106079875
#include
#define ll long long
using namespace std;
const ll mod=1e9+7;
int n,m,K,x;
int siz[100005];
ll f[100005][12][3];
ll g[12][3];
vector<vector<int>> e(100005);
void dfs(int u,int fa){
siz[u]=1;
f[u][0][0]=K-1;
f[u][1][1]=1;
f[u][0][2]=m-K;
for (auto v:e[u]){
if (v==fa) continue;
dfs(v,u);
memset(g,0,sizeof(g));
for (int j=0;j<=min(siz[u],x);j++){
for (int k=0;k<=min(siz[v],x)&&k+j<=x;k++){
g[j+k][1]=(g[j+k][1]+f[u][j][1]*f[v][k][0]%mod)%mod;
g[j+k][0]=(g[j+k][0]+(f[v][k][1]+f[v][k][0]+f[v][k][2])%mod*f[u][j][0]%mod)%mod;
g[j+k][2]=(g[j+k][2]+(f[v][k][0]+f[v][k][2])%mod*f[u][j][2]%mod)%mod;
}
}
siz[u]+=siz[v];
for (int j=0;j<=min(siz[u],x);j++){
f[u][j][1]=g[j][1];
f[u][j][2]=g[j][2];
f[u][j][0]=g[j][0];
}
}
}
int main(){
cin>>n>>m;
for (int i=1;i<n;i++){
int a,b;
cin>>a>>b;
e[a].push_back(b);
e[b].push_back(a);
}
cin>>K>>x;
dfs(1,-1);
ll ans=0;
for (int j=0;j<=x;j++){
for (int k=0;k<=2;k++){
// cout<
ans=(ans+f[1][j][k])%mod;
}
}
cout<<ans;
return 0;
}
CF222E Decoding Genome
转移很简单,由题意合法转移即可,但 n n n很大,由于转移是一个线性的,所以考虑构造成矩阵,用矩阵乘法 快速幂加速来做
对于构造的 m ∗ m m*m m∗m矩阵只需让 a c h a r 2 , c h a r 1 = 0 a_{char2,char1}=0 achar2,char1=0 即可
// LUOGU_RID: 106099368
#include
#define ll long long
using namespace std;
const ll mod=1e9+7;
ll n,a[55][55],ans[55],s[55][55],temp[55][55];
int m;
void oper1(){
memset(temp,0,sizeof(temp));
for (int i=1;i<=m;i++){
for (int j=1;j<=m;j++){
for (int k=1;k<=m;k++){
temp[i][j]=(temp[i][j]+a[i][k]*s[k][j]%mod)%mod;
}
}
}
for (int i=1;i<=m;i++){
for (int j=1;j<=m;j++){
a[i][j]=temp[i][j];
}
}
}
void oper2(){
memset(temp,0,sizeof(temp));
for (int i=1;i<=m;i++){
for (int j=1;j<=m;j++){
for (int k=1;k<=m;k++){
temp[i][j]=(temp[i][j]+s[i][k]*s[k][j]%mod)%mod;
}
}
}
for (int i=1;i<=m;i++){
for (int j=1;j<=m;j++){
s[i][j]=temp[i][j];
}
}
}
void quickm(){
ll temp=n;
for (int i=1;i<=m;i++){
a[i][i]=1;
}
while (temp){
if (temp&1){
oper1();
}
oper2();
temp>>=1;
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int q;
cin>>n>>m>>q;
for (int i=1;i<=m;i++){
for (int j=1;j<=m;j++){
s[i][j]=1;
}
}
while (q--){
char x,y;
cin>>x>>y;
int xx,yy;
if (x>='a'&&x<='z') xx=x-'a'+1;
else xx=x-'A'+27;
if (y<='Z'&&y>='A') yy=y-'A'+27;
else yy=y-'a'+1;
s[yy][xx]=0;
}
for (int i=1;i<=m;i++){
ans[i]=1;
}
n--;
quickm();
memset(temp,0,sizeof(temp));
for (int j=1;j<=m;j++){
for (int k=1;k<=m;k++){
temp[1][j]=(temp[1][j]+ans[k]*a[k][j]%mod)%mod;
}
}
ll sum=0;
for (int i=1;i<=m;i++){
sum=(sum+temp[1][i])%mod;
}
cout<<sum<<"\n";
return 0;
}
CF533B Work Group
简单的树形dp方案统计
d p i , ( 0 / 1 ) dp_{i,(0/1)} dpi,(0/1) 表示以 i i i为根的子树,在没选 u u u时,选择的子树个数为0偶/1奇
偶由奇加奇 偶加偶转移 奇由奇加偶 偶加奇转移即可
// LUOGU_RID: 106109430
#include
#define ll long long
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n;
cin>>n;
vector<vector<int>> e(n+1);
vector<ll> a(n+1);
for (int i=1;i<=n;i++){
int b;
cin>>b>>a[i];
if (b>0) e[b].push_back(i);
}
vector<vector<ll>> dp(n+1,vector<ll>(2));
function<void(int)> dfs=[&](int u){
dp[u][1]=-1e18;
for (auto v:e[u]){
dfs(v);
ll x=dp[u][0],y=dp[u][1];
dp[u][0]=max(dp[v][0]+x,dp[v][1]+y);
dp[u][1]=max(dp[v][1]+x,dp[v][0]+y);
}
dp[u][1]=max(dp[u][1],dp[u][0]+a[u]);
};
dfs(1);
cout<<max(dp[1][0],dp[1][1]);
return 0;
}
CF1201D Treasure Hunting
离谱题,贪心一层到下一层,要想最快把下一层的宝藏捡完,最终一定会停在最左/最右的宝藏处。
所以每层只需设两个状态, d p i , ( 0 / 1 ) dp_{i,(0/1)} dpi,(0/1) 第 i i i层,0最后在左端点,1最后在右端点的最短总时间花费
贪心 假设当前宝藏全捡完,所在的最左/最右端点的所在列不是安全的列
那么就往左/右找到最靠近的安全列向上走到下一层,贪心易证(如果当前列就是安全列,那么也可以合并在这种情况中,详见代码)
找最靠近的安全列用二分来查找
那么转移的情况就是:
最后还需要特殊处理空行的情况,空行应该在上一行的基础上总时间花费+1
我的思路是提前储存第 i i i行前:第一个非空行是 b e f o r e before before 那么 d p i , ( 0 / 1 ) dp_{i,(0/1)} dpi,(0/1) 就由 d p b e f o r e , ( 0 / 1 ) dp_{before,(0/1)} dpbefore,(0/1) 转移
可以用函数来简化形式类似的转移方程,方便调试。
// LUOGU_RID: 106145466
#include
#define ll long long
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int n,m,K,q;
cin>>n>>m>>K>>q;
vector<ll> l(n+1,m+1),r(n+1);
while (K--){
ll x,y;
cin>>x>>y;
l[x]=min(l[x],y);
r[x]=max(r[x],y);
}
while (r[n]==0&&n){
n--;
}
vector<ll> use;
for (int i=0;i<q;i++){
int x;
cin>>x;
use.push_back(x);
}
sort(use.begin(),use.end());
int before=1;
vector<vector<ll>> dp(n+1,vector<ll>(2,1e18));
if (r[1]){
dp[1][0]=r[1]-1+(r[1]-l[1]);
dp[1][1]=r[1]-1;
}else{
dp[1][0]=dp[1][1]=use[0]-1;
l[1]=r[1]=use[0];
}
for (int i=2;i<=n;i++){
//从上一行左端点转移
int pos1=lower_bound(use.begin(),use.end(),l[before])-use.begin();
if (use[pos1]!=l[before]&&pos1) pos1--;
int pos2=lower_bound(use.begin(),use.end(),l[before])-use.begin();
if (use[pos2]<l[before]) pos2=use.size()-1;
ll x=llabs(l[before]-use[pos1]),y=llabs(use[pos2]-l[before]);
if (!r[i]){
}else{
if (use[pos1]<=l[i]){
dp[i][0]=min(dp[i][0],dp[before][0]+x+(r[i]-use[pos1])+(r[i]-l[i])+i-before);
dp[i][1]=min(dp[i][1],dp[before][0]+x+(r[i]-use[pos1])+i-before);
}else if (use[pos1]>=r[i]){
dp[i][0]=min(dp[i][0],dp[before][0]+x+(use[pos1]-l[i])+i-before);
dp[i][1]=min(dp[i][1],dp[before][0]+x+(use[pos1]-l[i])+(r[i]-l[i])+i-before);
}else{
dp[i][0]=min(dp[i][0],dp[before][0]+x+(r[i]-use[pos1])+(r[i]-l[i])+i-before);
dp[i][1]=min(dp[i][1],dp[before][0]+x+(use[pos1]-l[i])+(r[i]-l[i])+i-before);
}
if (use[pos2]<=l[i]){
dp[i][0]=min(dp[i][0],dp[before][0]+y+(r[i]-use[pos2])+(r[i]-l[i])+i-before);
dp[i][1]=min(dp[i][1],dp[before][0]+y+(r[i]-use[pos2])+i-before);
}else if (use[pos2]>=r[i]){
dp[i][0]=min(dp[i][0],dp[before][0]+y+(use[pos2]-l[i])+i-before);
dp[i][1]=min(dp[i][1],dp[before][0]+y+(use[pos2]-l[i])+(r[i]-l[i])+i-before);
}else{
dp[i][0]=min(dp[i][0],dp[before][0]+y+(r[i]-use[pos2])+(r[i]-l[i])+i-before);
dp[i][1]=min(dp[i][1],dp[before][0]+y+(use[pos2]-l[i])+(r[i]-l[i])+i-before);
}
}
//右端点
pos1=lower_bound(use.begin(),use.end(),r[before])-use.begin();
if (use[pos1]!=r[before]&&pos1)pos1--;
pos2=lower_bound(use.begin(),use.end(),r[before])-use.begin();
if (use[pos2]<r[before]) pos2=use.size()-1;
x=llabs(r[before]-use[pos1]);
y=llabs(use[pos2]-r[before]);
// if (n>=8&&i==6)cout<
if (!r[i]){
}else{
if (use[pos1]<=l[i]){
dp[i][0]=min(dp[i][0],dp[before][1]+x+(r[i]-use[pos1])+(r[i]-l[i])+i-before);
dp[i][1]=min(dp[i][1],dp[before][1]+x+(r[i]-use[pos1])+i-before);
}else if (use[pos1]>=r[i]){
dp[i][0]=min(dp[i][0],dp[before][1]+x+(use[pos1]-l[i])+i-before);
dp[i][1]=min(dp[i][1],dp[before][1]+x+(use[pos1]-l[i])+(r[i]-l[i])+i-before);
}else{
dp[i][0]=min(dp[i][0],dp[before][1]+x+(r[i]-use[pos1])+(r[i]-l[i])+i-before);
dp[i][1]=min(dp[i][1],dp[before][1]+x+(use[pos1]-l[i])+(r[i]-l[i])+i-before);
}
if (use[pos2]<=l[i]){
dp[i][0]=min(dp[i][0],dp[before][1]+y+(r[i]-use[pos2])+(r[i]-l[i])+i-before);
dp[i][1]=min(dp[i][1],dp[before][1]+y+(r[i]-use[pos2])+i-before);
}else if (use[pos2]>=r[i]){
dp[i][0]=min(dp[i][0],dp[before][1]+y+(use[pos2]-l[i])+i-before);
dp[i][1]=min(dp[i][1],dp[before][1]+y+(use[pos2]-l[i])+(r[i]-l[i])+i-before);
}else{
dp[i][0]=min(dp[i][0],dp[before][1]+y+(r[i]-use[pos2])+(r[i]-l[i])+i-before);
dp[i][1]=min(dp[i][1],dp[before][1]+y+(use[pos2]-l[i])+(r[i]-l[i])+i-before);
}
before=i;
}
}
// if (n>=8)cout<
cout<<min(dp[n][0],dp[n][1]);
return 0;
}
总结 :3.27总计做了4道dp
一道二分答案加贪心(这个贪心方法跟周天vp的一场2020ICPC青岛站的E很像E. Plants vs. Zombies )
两道树形dp结合计数原理,其中CF533B Work Group较常规,CF855C Helga Hufflepuff’s Cup这一道学会了多用一个数组辅助转移(防止当前状态下dp的值发生变化)
一道很板子的矩阵加速优化dpCF222E Decoding Genome
还有一道CF1201D Treasure Hunting 很多细节,需要大胆贪心,严谨处理实现的dp题(有数据结构那味了) ,最大收获就是对于很特殊的情况不要偷懒,分类然后不要偷懒,重新用不同的方法实现,不要偷懒,不要偷懒,不要偷懒