前言

感觉都初一升初二了,再做这个题是不是有点太菜了啊……

里面大概都是些 DP 板子题(确信,题目质量还挺高的,不过不涉及太难的优化(实际上只有最后一题是斜率优化)。

不管了,还是写个 blog 来总结一下吧~

T Permutation

题目链接

题目大意:给你一个长度为 \(n-1\) 的只有 < 或者 > 两种字符的字符串 \(s\),分别代表 \(p_i<p_{i+1}\) 或 \(p_i>p_{i+1}\),求有多少个 \(1\) 到 \(n\) 的排列 \(p\) 满足这个要求。

数据范围:\(n\leq 3000\)

这个数据范围就明摆着的 \(O(n^2)\) DP?

一开始容易想到一个错误的状态 \(f(i,j)\) 表示第 \(i\) 个数为 \(j\) 的情况数。

这个状态错误的原因就是我们不应该关心 \(p_i\) 的值,而是应该关心 \(p_i\) 在前 \(i\) 个数中的排名。(高妙啊!)

这样就不难设计出 DP 状态 \(f(i,j)\) 表示第 \(i\) 个数在 \(1\) 到 \(i\) 中排名为 \(j\) 的情况。

转移:

  • 若 \(s_{i-1}\) 为 <,则 \(f(i,j)\leftarrow \sum_{k=1}^{j-1}f(i-1,k)\)。
  • 否则 \(f(i,j)\leftarrow \sum_{k=j}^nf(i-1,k)\)

这个东西大力不行,用前缀和优化一下即可。

时间复杂度 \(O(N^2)\)。

代码:

#include<bits/stdc++.h>
#define rep(i,n) for(int i=1;i<=n;i++)
#define REP(i,n) for(int i=0;i<n;i++)
#define FOR(i,x,y) for(int i=x;i<=y;i++)
#define sz(x) (int)(x.size())
#define LL long long
#define pii pair<int, int>
#define pLL pair<LL, LL>
using namespace std;
template<typename T>inline void chmax(T &a,T b){a = max(a, b);}
template<typename T>inline void chmin(T &a,T b){a = min(a, b);}
const int maxn=3005,mod=1e9+7;
int n,dp[maxn][maxn],presum[maxn][maxn];
string s;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>s;s=' '+s;
dp[1][1]=1;
for(int i=1;i<=n;i++)presum[1][i]=1;
for(int i=2;i<=n;i++){
if(s[i-1]=='<'){
for(int j=1;j<=i;j++)dp[i][j]=presum[i-1][j-1];
}
else{
for(int j=1;j<=i;j++)dp[i][j]=(presum[i-1][n]-presum[i-1][j-1]+mod)%mod;
}
for(int j=1;j<=n;j++)presum[i][j]=(presum[i][j-1]+dp[i][j])%mod;
}
cout<<presum[n][n]<<endl;
return 0;
}

U Grouping

题目链接

题意:给你 \(n\) 个物品需要分组,你可以将它们分成一些组合,每组内部每一对 \((i,j)\) 都会产生一个贡献 \(a_{i,j}\)(可能为负数),问你最大可能产生的总贡献。

数据范围:\(n\leq 16\)

裸状压 DP,没啥技术含量,差评。

一看这个数据范围就知道肯定是状压 DP。

然后你知道这个以后就容易设计出 DP 状态:设 \(f(i)\) 表示选择状态为 \(i\) 时的最大总贡献。

考虑转移,转移就是你一个 \(i\) 有两种选择:

  • 不划分(直接成为一个集合,即 \(i\) 内部两两直接产生贡献)

  • 划分成多个集合(划分后每个集合分别计算)

    但你考虑这个情况并不需要真的划分成多个集合,只要划分成 2 个然后这 2 个在递归继续划分。

划分成 2 个的过程可以通过用二进制枚举子集来实现。

总时间复杂度 \(O(2^N)\)。

代码:

#include <bits/stdc++.h>
#define sz(x) (int)(x.size())
using namespace std;
const int mod=1e9+7,Base=233,INF=0x3f3f3f3f;
template<typename T>inline void chmax(T &a, T b){a=max(a,b);}
template<typename T>inline void chmin(T &a, T b){a=min(a,b);}
inline void trans(int &a,int b){a+=b;if(a>mod)a-=mod;}
int n;
long long a[20][20],dp[1<<16];
long long dfs(int mask)
{
if(dp[mask]!=-1)
return dp[mask];
long long &ret=dp[mask];
ret=0;
for(int i=0;i<n;i++)
if((mask>>i)&1)
for(int j=i+1;j<n;j++)
if((mask>>j)&1)
ret+=a[i][j];
for(int i=mask&(mask-1);i>0;i=(i-1)&mask)
chmax(ret,dfs(i)+dfs(mask^i));
return ret;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cin>>n;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
cin>>a[i][j];
memset(dp,-1,sizeof(dp));
cout<<dfs((1<<n)-1)<<"\n";
return 0;
}

V Subtree

题目链接

题目大意:有一棵 \(n\) 个节点的数,你要给它黑白染色,但是限定染黑的节点必须连在一起(即形成一个连通块),对于每一个 \(i=1...n\),问如果强制将 \(i\) 节点染黑的染色方案数。(注意:模数不保证为质数)

数据范围:\(n\leq 10^5\)

感觉 \(O(N)\) 的 DP 且要对于每个 \(i\) 都求的要么就是一次全算出来,要么是换根 DP。

这题一开始想的是拓扑排序的一个做法,但是不太会。后来看了 ft 巨佬题解才贺出来(划掉)。

不过这道题不用换根 DP 也能解释?(不懂

你需要分两类,即 \(u\) 子树里(不含 \(u\))和除了 \(u\) 的子树(含 \(u\))。

对这两类分别使用 \(f(u),g(u)\) 来表示强制 \(u\) 染色的代价。

首先考虑 \(f(u)\) 的转移,这是简单的:

\[f(u)=\prod_{v\in son(u)}(f(v)+1)
\]

这个式子就表示每个子树都可以染或不染,染就 \(f(v)\) 种,不染就一车空白 1 种。

但是 \(g(u)\) 的转移就需要从 \(f(u)\) 进行倒推了(貌似和某模拟赛我想的思路一样):

\[g(u)=g(p)\cdot\prod_{v\in son(p)}(f(v)+1)+1
\]

这个转移分成 2 类:

  • 父亲及祖先(\(g(p)\))

  • 兄弟的子树(\(\prod_{v\in son(p)}(f(v)+1)\))

剩下的 \(1\) 就是啥都不染的代价。

这样就可以得到一个 \(O(N^2)\) 的做法了。

Some Optimization:

考虑到 \(O(N^2)\) 并不能通过此题(至少我不能,所以需要优化转移。

考虑到复杂度瓶颈为枚举兄弟节点,容易想到一个优化就是 \(\prod_{v\in son(p)}(f(v)+1)=\frac{f(p)}{f(u)+1}\),但是不能直接用乘法逆元搞。

仔细想想维护前缀和后缀积就行,这样时间复杂度 \(O(N)\)。

代码:

#include <bits/stdc++.h>
#define sz(x) (int)(x.size())
using namespace std;
const int Base=233,INF=0x3f3f3f3f;
template<typename T>inline void chmax(T &a, T b){a=max(a,b);}
template<typename T>inline void chmin(T &a, T b){a=min(a,b);}
const int maxn=1e5+5;
int n,mod,F[maxn],G[maxn];
vector<int> g[maxn],pre[maxn],suf[maxn];
inline void trans(int &a,int b){a+=b;if(a>mod)a-=mod;}
void dfs1(int x,int p)
{
pre[x].resize(sz(g[x]),1);
suf[x].resize(sz(g[x]),1);
F[x]=1;
if(sz(g[x])==1&&g[x][0]==p)
return;
for(int i=0;i<sz(g[x]);i++)
{
int to=g[x][i];
if(to==p)
continue;
dfs1(to,x);
F[x]=1LL*F[x]*(F[to]+1)%mod;
}
for(int i=1;i<sz(g[x]);i++)
{
pre[x][i]=pre[x][i-1];
if(g[x][i-1]==p)
continue;
pre[x][i]=1LL*pre[x][i]*(F[g[x][i-1]]+1)%mod;
}
for(int i=sz(g[x])-2;i>=0;i--)
{
suf[x][i]=suf[x][i+1];
if(g[x][i+1]==p)
continue;
suf[x][i]=1LL*suf[x][i]*(F[g[x][i+1]]+1)%mod;
}
}
void dfs2(int x,int p,int id)
{
G[x]=1;
if(p)
{
int tr=G[p];
tr=1LL*tr*pre[p][id]%mod;
tr=1LL*tr*suf[p][id]%mod;
trans(G[x],tr);
}
for(int i=0;i<sz(g[x]);i++)
{
int to=g[x][i];
if(to==p)
continue;
dfs2(to,x,i);
}
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cin>>n>>mod;
int u,v;
for(int i=1;i<n;i++)
{
cin>>u>>v;
g[u].push_back(v);
g[v].push_back(u);
}
dfs1(1,0);
dfs2(1,0,0);
for(int i=1;i<=n;i++)
cout<<1LL*F[i]*G[i]%mod<<"\n";
return 0;
}

W Intervals

题目链接

题目大意:你要构造一个长度为 \(n\) 的 01 串,它的价值由 \(m\) 条规则组成,每条规则如下:

  • 若在区间 \([l_i,r_i]\) 中有 1,则会获得 \(a_i\) 的价值(注意:\(a_i\) 可能为负数)。

求可能的最大价值。

数据范围:\(n\leq 10^5\)

我的评价是:高妙啊!tql!

容易想到的是按 \(r_i\) 排序,对于每个 \(r_i\) 分别计算到 \(l_i\) 的贡献。

我当时的做法是设 \(f(i)\) 表示到第 \(i\) 个字符并且必须选 \(i\) 的最大价值。然后大概推了推式子就弃掉了……

实际上这道题的思路是先设计出 \(O(N^2)\) 的状态再优化,优化就比较套路了。

具体来说,可以设 \(f(i,j)\) 表示到第 \(i\) 个字符并且上一个选的字符是 \(j\)(\(j=i\) 表示选了第 \(i\) 个字符)的最大价值。

转移比较容易:

\[\begin{cases}
f(i,i)=\min_{j<i}f(i-1,j)\\
f(i,j)=f(i-1,j)+\sum_{r_k=i,l_k\leq j}a_k
\end{cases}
\]

这个东西用线段树搞一下时间空间直接 ok 了。

#include <bits/stdc++.h>
#define sz(x) (int)(x.size())
using namespace std;
const int mod=1e9+7,Base=233,inf=0x3f3f3f3f;
const long long INF=0x3f3f3f3f3f3f3f3f;
template<typename T>inline void chmax(T &a, T b){a=max(a,b);}
template<typename T>inline void chmin(T &a, T b){a=min(a,b);}
inline void trans(int &a,int b){a+=b;if(a>mod)a-=mod;}
const int maxn=2e5+5;
int n,m;
struct node
{
int l,r;
long long val,tag;
};
struct segmentree
{
node tree[maxn<<2];
void pushup(int x)
{
tree[x].val=max(tree[x<<1].val,tree[x<<1|1].val);
}
void pushdown(int x)
{
if(!tree[x].tag)
return;
tree[x<<1].tag+=tree[x].tag;
tree[x<<1|1].tag+=tree[x].tag;
tree[x<<1].val+=tree[x].tag;
tree[x<<1|1].val+=tree[x].tag;
tree[x].tag=0;
}
void build(int x,int l,int r)
{
tree[x].l=l;
tree[x].r=r;
tree[x].val=-INF;
tree[x].tag=0;
if(l==r)
return;
int mid=(l+r)>>1;
build(x<<1,l,mid);
build(x<<1|1,mid+1,r);
}
void update1(int x,int p,long long v)
{
if(tree[x].l==tree[x].r)
{
tree[x].val=v;
return;
}
int mid=(tree[x].l+tree[x].r)>>1;
if(p<=mid)
update1(x<<1,p,v);
else
update1(x<<1|1,p,v);
pushup(x);
}
void update2(int x,int l,int r,long long v)
{
if(tree[x].l>=l&&tree[x].r<=r)
{
tree[x].val+=v;
tree[x].tag+=v;
return;
}
pushdown(x);
int mid=(tree[x].l+tree[x].r)>>1;
if(l<=mid)
update2(x<<1,l,r,v);
if(r>mid)
update2(x<<1|1,l,r,v);
pushup(x);
}
}tr;
struct rule
{
int l,r;
long long val;
bool operator < (const rule &x) const
{
return r<x.r;
}
}a[maxn];
int main()
{
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
cin>>n>>m;
for(int i=1;i<=m;i++)
cin>>a[i].l>>a[i].r>>a[i].val;
sort(a+1,a+m+1);
tr.build(1,1,n);
int p=1;
for(int i=1;i<=n;i++)
{
tr.update1(1,i,max(0LL,tr.tree[1].val));
while(a[p].r==i)
{
tr.update2(1,a[p].l,i,a[p].val);
p++;
}
}
cout<<max(0LL,tr.tree[1].val)<<"\n";
return 0;
}

AtCoder Educational DP Contest 总结的更多相关文章

  1. Atcoder Educational DP Contest

    前面简单一点的题直接过吧. A 暴力DP B 怎么还是暴力DP C 还是暴力DP D 直接背包 E 这个背包不太一样了,这里有一个技巧,就是因为价值很小,所以直接对价值背包,求出来达到某一个权值最小的 ...

  2. Atcoder Educational DP Contest 题解

    A - Frog 1/B - Frog 2 入门... #include<cstdio> #define abs(a) ((a)>=0?(a):(-(a))) #define min ...

  3. Atcoder Educational DP Contest I - Coins (概率DP)

    题意:有\(n\)枚硬币,每枚硬币抛完后向上的概率为\(p[i]\),现在求抛完后向上的硬币个数大于向下的概率. 题解:我们用二维的\(dp[i][j]\)来表示状态,\(i\)表示当前抛的是第\(i ...

  4. Sth about Educational DP Contest

    Contest Website : atcoder.jp/contests/dp \[\begin{array}{c|C|c|c} TaskNum & TaskName & Statu ...

  5. 【DP】Educational DP Contest

    这份 dp 题单的最后几题好难 orz. 前面的题比较简单,所以我会选取一些题来讲,其它的直接看代码理解吧 qwq. 传送门: https://atcoder.jp/contests/dp 全部 AC ...

  6. Educational DP Contest H - Grid 1 (DP)

    题意:有一个\(n\)X\(m\)的图,"#"表示障碍物,"."表示道路,只能向右或向下走,问从左上角走到右下角的方案数. 题解:这题可以用bfs来搞,但dp更 ...

  7. Educational DP Contest G - Longest Path (dp,拓扑排序)

    题意:给你一张DAG,求图中的最长路径. 题解:用拓扑排序一个点一个点的拿掉,然后dp记录步数即可. 代码: int n,m; int a,b; vector<int> v[N]; int ...

  8. Educational DP Contest F - LCS (LCS输出路径)

    题意:有两个字符串,求他们的最长公共子序列并输出. 题解:首先跑个LCS记录一下dp数组,然后根据dp数组来反着还原路径,只有当两个位置的字符相同时才输出. 代码: char s[N],t[N]; i ...

  9. Educational DP Contest E - Knapsack 2 (01背包进阶版)

    题意:有\(n\)个物品,第\(i\)个物品价值\(v_{i}\),体积为\(w_{i}\),你有容量为\(W\)的背包,求能放物品的最大价值. 题解:经典01背包,但是物品的最大体积给到了\(10^ ...

随机推荐

  1. Fast-Rcnn学习笔记

    Fast-Rcnn学习笔记 paper code Fast-RCNN总览 step1:图片先放进卷积层 step2:再卷积层的特征图谱上回映射出对应的感兴趣区域 step3:集过一层ROI Pooli ...

  2. vue3常见问题及解决方案(四)父组件切换行,然后子组件切换tab,子组件内的数据不刷新

    问题描述 父组件切换行,然后子组件切换tab,子组件内的数据不刷新. 例如父组件为订单,子组件为订单相关商品和相关客户,商品和客户使用tab选项卡组织. 当tab显示商品页时,切换订单,商品页内容跟着 ...

  3. 树形dp基础

    今天来给大家讲一下数形dp基础 树形dp常与树上问题(lca.直径.重心)结合起来 而这里只讲最最基础的树上dp 1.选课 题目描述 在大学里每个学生,为了达到一定的学分,必须从很多课程里选择一些课程 ...

  4. String、StringBuilder、StringBuffer——JavaSE基础

    String.StringBuilder.StringBuffer String不可变 StringBuilder与StringBuffer均可变 StringBuilder线程不安全,效率高,常用 ...

  5. 当JAVA注解、AOP、SpEL相遇,更多可能变为了现实

    常规情况下,我们可以通过业务定制化的注解,借助AOP机制来实现某些通用的处理策略.比如定义个@Permission注解,可以用于标识在具体的方法上,然后用来指定某个方法必须要指定角色的人才能够访问调用 ...

  6. 如何获取GC(垃圾回收器)的STW(暂停)时间?

    前言 在现代的容器化和微服务应用中,因为分布式的环境和错综复杂的调用关系,APM(Application Performance Monitoring 应用性能监控)显得尤为重要,它通过采集应用程序各 ...

  7. js 表面学习 - 认识结构

    JavaScript 语句由以下构成: 值.运算符.表达式.关键词和注释. 这条语句告诉浏览器在 id="demo" 的 HTML 元素中输出 "Hello Kitty. ...

  8. 业务可视化-让你的流程图"Run"起来

    前言 最近在研究业务可视化的问题,在日常的工作中,流程图和代码往往是分开管理的. 一个被维护多次的系统,到最后流程图和代码是否匹配这个都很难说. 于是一直有一个想法,让程序直接读流程图,根据流程图的配 ...

  9. 面试官:Redis 过期删除策略和内存淘汰策略有什么区别?

    作者:小林coding 计算机八股文网站:https://xiaolincoding.com 大家好,我是小林. Redis 的「内存淘汰策略」和「过期删除策略」,很多小伙伴容易混淆,这两个机制虽然都 ...

  10. Python 用configparser读写ini文件

    一.configparser 简介Python用于读写ini文件的一个官方标准库.具体详见官网链接 二.configparser 部分方法介绍 方法 描述 read(filenames) filesn ...