前言

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

里面大概都是些 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. 【js奇妙说】如何跟非计算机从业者解释,为什么浮点数计算0.1+0.2不等于0.3?

    壹 ❀ 引 0.1+0.2不等于0.3,即便你不知道原理,但也应该听闻过这个问题,包括博主本人也曾在面试中被问到过此问题.很遗憾,当时只知道一句精度丢失,但是什么原因造成的精度丢失却不太清楚.而我在查 ...

  2. SyntaxError: Non-UTF-8 code starting with '\xef' in file(已解决)

    错误原因: python代码中出现了中文字符 解决方案: 在python代码文件的第一行(必须是第一行)添加如下代码(随编码不同自行修改): #coding=utf-8

  3. 亿信BI——EXCEL组件使用流程

    功能模块: 用户点击"excel输入"模块进行excel文件导入操作,excel导入页面的顶部导航栏包括基本属性.文件设置.格式设置和字段列表四部分. 基础属性: 在基本属性模块部 ...

  4. Zookeeper安装学习(二)

    学习内容:Zookeeper集群安装(Zookeeper版本:Zookeeper3.5.7:注:master,s1,s2都需要部署) 解压安装: (1)在主机 master 解压 Zookeeper ...

  5. 2020级cpp机考模拟题A卷-#题解2

    这部分的题目都有一定难度,有兴趣的同学可以钻研一下. 特此感谢来自BDT20030  tql的支持. 2:素数的和-2 题意: 计算不大于m的素数之和.(多么容易理解的题目啊,对吧) 题解(有点复杂的 ...

  6. Navicat破解激活流程

    ​​ ​ Navicat Navicat Premium 是一套数据库开发工具,让你从单一应用程序中同时连接 MySQL.MariaDB.MongoDB.SQL Server.Oracle.Postg ...

  7. 负载均衡之DR实验

    实验环境 本实验搭建在虚拟机中.一台服务器作为DR两台作为RS,还有一台为后续内容会用到的备用机. 实验环境示意图: 1. 修改网络层VIP 修改DR,添加VIP 修改前: 修改后: 修改RS,修改A ...

  8. OI中组合数学公式和定理90%歼灭

    组合数学 基础概念 加法和乘法原理 加法原理 同一步下的不同选择,可以通过累加得到方案数. 乘法原理 整个流程的方案数可以由每一步的方案数相乘得到. 有了加法原理和乘法原理,就可以解决一些没有选择导致 ...

  9. React中使用react-player 播放视频或直播

    业务中需要播放视频,寻来寻去,找到了react-player 初次点击,甚是眼熟,思来想去,竟是钉钉同款 如果使用react框架 先安装 npm install --save video-react ...

  10. 使用git提交和拉取gitee的代码

    使用git提交和拉取gitee的代码 1. 安装Git(自行摸索) 2. 在gitee新建仓库 名称和路径自己写 这两个二选一足矣 默认分支master就行 复制这个链接,待会要用 3. 新建项目目录 ...