前言

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

里面大概都是些 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. 面试突击54:MySQL 常用引擎有哪些?

    MySQL 有很多存储引擎(也叫数据引擎),所谓的存储引擎是指用于存储.处理和保护数据的核心服务.也就是存储引擎是数据库的底层软件组织.在 MySQL 中可以使用"show engines& ...

  2. Tmux常用命令总结

    会话 # 创建会话 tmux new -s work -s是session # 查看tmux进程 ps aux | grep tmux # 连接会话 tmux attach -t work # 会话分 ...

  3. Docker容器配置远程登录

    Docker容器配置远程登录 前言 docker 的网络模式主要有三种,bridge.host.none: pridge是docker安装后自动创建的虚拟网卡,创建容器时默认使用此模式. host是指 ...

  4. markdown常用到的语法

    一.标题 后加文字,几个#代表几级标题,最高为6 ,标准语法一般在#后跟个空格再写文字. 二.分割线 三个或者三个以上的 - 或者 * 三.图片 格式: ![A](B "C") A ...

  5. 使用Karmada实现Helm应用的跨集群部署

    摘要:借助Karmada原生API的支持能力,Karmada可以借助Flux轻松实现Helm应用的跨集群部署. 本文分享自华为云社区< 使用Karmada实现Helm应用的跨集群部署[云原生开源 ...

  6. 一文看完vue3的变化之处

    在通读了vue的官网文档后,我记录下了如下这些相对于2.x的变化之处. 1.创建应用实例的变化 之前一般是这样: let app = new Vue({ // ...一些选项 template: '' ...

  7. 一种让运行在CentOS下的.NET CORE的Web项目简单方便易部署的自动更新方案

    一.项目运行环境 项目采用的是.NET5开发的Web系统,独立部署在省内异地多台CentOS服务器上,它们运行在甲方专网环境中(不接触互联网),甲方进行业务运作时(一段时间内)会要求异地服务器开机上线 ...

  8. Timer和ScheduledThreadPoolExecutor的区别

    Timer 基于单线程.系统时间实现的延时.定期任务执行类.具体可以看下面红色标注的代码. public class Timer { /** * The timer task queue. This ...

  9. NC207028 第k小数

    NC207028 第k小数 题目 题目描述 给你一个长度为 \(n\) 的序列,求序列中第 \(k\) 小数的多少. 输入描述 多组输入,第一行读入一个整数 \(T\) 表示有 \(T\) 组数据. ...

  10. NC53681 「土」巨石滚滚

    NC53681 「土」巨石滚滚 题目 题目描述 帕秋莉掌握了一种土属性魔法 她使用这种魔法建造了一个大型的土球,并让其一路向下去冲撞障碍 土球有一个稳定性 \(x\) ,如果 \(x < 0\) ...