AtCoder Educational DP Contest 总结
前言
感觉都初一升初二了,再做这个题是不是有点太菜了啊……
里面大概都是些 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(v)\) 种,不染就一车空白 1 种。
但是 \(g(u)\) 的转移就需要从 \(f(u)\) 进行倒推了(貌似和某模拟赛我想的思路一样):
\]
这个转移分成 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\) 个字符)的最大价值。
转移比较容易:
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 总结的更多相关文章
- Atcoder Educational DP Contest
前面简单一点的题直接过吧. A 暴力DP B 怎么还是暴力DP C 还是暴力DP D 直接背包 E 这个背包不太一样了,这里有一个技巧,就是因为价值很小,所以直接对价值背包,求出来达到某一个权值最小的 ...
- Atcoder Educational DP Contest 题解
A - Frog 1/B - Frog 2 入门... #include<cstdio> #define abs(a) ((a)>=0?(a):(-(a))) #define min ...
- Atcoder Educational DP Contest I - Coins (概率DP)
题意:有\(n\)枚硬币,每枚硬币抛完后向上的概率为\(p[i]\),现在求抛完后向上的硬币个数大于向下的概率. 题解:我们用二维的\(dp[i][j]\)来表示状态,\(i\)表示当前抛的是第\(i ...
- Sth about Educational DP Contest
Contest Website : atcoder.jp/contests/dp \[\begin{array}{c|C|c|c} TaskNum & TaskName & Statu ...
- 【DP】Educational DP Contest
这份 dp 题单的最后几题好难 orz. 前面的题比较简单,所以我会选取一些题来讲,其它的直接看代码理解吧 qwq. 传送门: https://atcoder.jp/contests/dp 全部 AC ...
- Educational DP Contest H - Grid 1 (DP)
题意:有一个\(n\)X\(m\)的图,"#"表示障碍物,"."表示道路,只能向右或向下走,问从左上角走到右下角的方案数. 题解:这题可以用bfs来搞,但dp更 ...
- Educational DP Contest G - Longest Path (dp,拓扑排序)
题意:给你一张DAG,求图中的最长路径. 题解:用拓扑排序一个点一个点的拿掉,然后dp记录步数即可. 代码: int n,m; int a,b; vector<int> v[N]; int ...
- Educational DP Contest F - LCS (LCS输出路径)
题意:有两个字符串,求他们的最长公共子序列并输出. 题解:首先跑个LCS记录一下dp数组,然后根据dp数组来反着还原路径,只有当两个位置的字符相同时才输出. 代码: char s[N],t[N]; i ...
- Educational DP Contest E - Knapsack 2 (01背包进阶版)
题意:有\(n\)个物品,第\(i\)个物品价值\(v_{i}\),体积为\(w_{i}\),你有容量为\(W\)的背包,求能放物品的最大价值. 题解:经典01背包,但是物品的最大体积给到了\(10^ ...
随机推荐
- DeepPrivacy: A Generative Adversarial Network for Face Anonymization阅读笔记
DeepPrivacy: A Generative Adversarial Network for Face Anonymization ISVC 2019 https://arxiv.org/pdf ...
- Net6 Xunit 集成测试
对于单元测试.集成测试大部分开发的朋友都懒得去写,因为这要耗费精力去设计去开发,做完项目模块直接postman 调用测试(当然这是一个选择,开发也中经常用到),但是如果测试需要多样化数据,各种场景模拟 ...
- 深入解读SQL的聚集函数
摘要:本文从基本聚集操作入手,介绍常用的SQL语法,以及一些扩展的聚集功能,同时会讲到在GaussDB(DWS)里聚集相关的一些优化思路. 本文分享自华为云社区<GaussDB(DWS) SQL ...
- 安装Python到Linux(Pyenv)
pyenv是一个多Python版本的托管工具,我们可以使用它安装Python和随意的切换系统环境中默认使用的Python版本. 运行环境 系统版本:CentOS Linux release 7.6.1 ...
- 直接将A类库复制到vs中的B类库,但是解决方案菜单中不显示
1.将要复制的文件夹复制粘贴到你要用的vs项目中 2.右键 添加 现有项目 选中xxxxx.csproj文件 点击 打开 就可以了
- django框架6
内容概要 神奇的双下划线查询 外键字段的创建 外键字段操作 多表查询 基于对象的跨表查询 基于双下划线的跨表查询 双下线查询扩展 如何查看SQL语句 内容详情 神奇的双下划线查询 1.查询年龄大于20 ...
- DirectX11 With Windows SDK--19(Dev) 编译Assimp并加载模型、新的Effects框架
前言 注意:这一章进行了重写,对应教程Dev分支第19章的项目,在更新完后面的项目后会替换掉原来第19章的教程 在前面的章节中我们一直使用的是由代码生成的几何模型,但现在我们希望能够导入模型设计师生成 ...
- rpc的正确打开方式|读懂Go原生net/rpc包
前言 最近在阅读字节跳动开源RPC框架Kitex的源码,分析了如何借助命令行,由一个IDL文件,生成client和server的脚手架代码,也分析了Kitex的日志组件klog.当然Kitex还有许多 ...
- 开发工具-MySQL下载地址
更新记录 2022年6月10日 完善标题. 商业版下载 商业版下载地址 https://edelivery.oracle.com/ 使用Oracle账号登录即可下载. 官方下载 https://dev ...
- 基于Kubernetes v1.24.0的集群搭建(三)
1 使用kubeadm部署Kubernetes 如无特殊说明,以下操作可以在所有节点上进行. 1.1 首先我们需要配置一下阿里源 cat <<EOF > /etc/yum.repos ...