2023 6月 dp做题记录
6月 dp做题记录
P5664 [CSP-S2019] Emiya 家今天的饭#
分析条件,我们要选出来的菜的集合需要满足的限制,集合不为空和烹饪方法互不相同都好处理,这样保证每种烹饪方法是独立不受影响的,并且至多选一种,所以每种烹饪方法
在第三种限制里,集合中每种食材的使用次数不超过
考虑用动态规划,我们在考虑不符合第三种限制是,同时也要满足前两种限制,这样保证求出来的方案一定在总方案数中。最朴素的,设状态
分为不选第
但我们再思考一下,在一种不合法的方案中不合法的食材有且仅有一种,因为假设有两种,一定超过总的选菜数量,即
放在状态里面就是不关心
每次枚举一个
#include<bits/stdc++.h>
using namespace std;
int n,m,a[120][2020];
long long s[120],dp[120][220],ans=1;
const int mod=998244353;
int main(){
cin >> n >> m;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
cin >> a[i][j];
s[i] += a[i][j];
s[i] %= mod;
}
}
for(int i = 1; i <= n; i++){
ans *= (s[i] + 1);
ans %= mod;
}
ans = (ans - 1 + mod) % mod;
for(int k = 1; k <= m; k++){
memset(dp, 0, sizeof(dp));
dp[0][100] = 1;
for(int i = 1; i <= n; i++){
long long now = s[i] - a[i][k];
for(int j = 0; j <= n + 100; j++){
dp[i][j] = dp[i - 1][j];
if(j) dp[i][j] += dp[i - 1][j - 1] * a[i][k];
dp[i][j] += dp[i - 1][j + 1] * now;
dp[i][j] %= mod;
}
}
for(int i = 101; i <= n + 100; i++){
ans -= dp[n][i];
ans = (ans + mod) % mod;
}
}
cout << ans << endl;
return 0;
}
P8867 [NOIP2022] 建造军营#
计算合法的建造军营和看守道路方案数,合法即为去掉一条没人看守的边后军营之间依然连通,因为是一条,所以容易发现在图中,强连通分量的边被割去一条是一定不会影响军营连通的,即强连通分量的边想看守就看守,不作为决定性因素。只有割边与方案的合法性有关。
所以我们考虑缩点,在无向图缩点后,原图会变成一个树,这点方便我们做树形 dp。
每个强连通分量内的方案数是可以预处理的,处理出点数为
考虑题目,经过上面分析,题目简化成,在一颗树上选出若干个点,选出的点在去掉一条边后依然连通的方案数。意思即选出的点之间相连的唯一路径上的边一定要看守,其他随意。问题缩小到子树上,限制只在子树里有军营,由于枚举子树时,唯一不同的就是根节点,它是我们区分不同方案的关键,所以我们限制当前的子树的根节点一定为相连路径上的一点,这点方便转移,因为这样子树中的军营就可以通过根节点相连。
在限制下,考虑
乘
答案即为:
要理解不漏也很容易,我们将每个节点作为中转点,实际上所有的选点方案都一定至少会在一棵子树上被统计。
在这题中,我们经过了缩点,将题目转化为树形 dp,统计方案数,在树上选点可以依照题意给出可以转移的状态,并且为了统计方案,可以给状态一些隐藏的限制,一是便于转移,二是可以使状态的意义更加明晰,特指某一种情况下的方案,使得小的方案之间没有并集,便于统计答案。最后的复杂度为
#include<bits/stdc++.h>
using namespace std;
int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c - '0');
c = getchar();
}
return x * f;
}
const int mod = 1000000007;
int n, m, cnt, cnt2, top, idx, tot;
int h[500010], h2[500010];
long long g[500010], sz[500010], sum1[500010];
int low[500010], dfn[500010], bel[500010], ins[500010], st[500010];
long long dp[500010][2], ans;
struct node{
int to, nxt;
}e[2000010];
struct node2{
int to, nxt;
}e2[2000010];
void add(int u, int v){
e[++cnt].to = v;
e[cnt].nxt = h[u];
h[u] = cnt;
}
void add2(int u, int v){
e2[++cnt2].to = v;
e2[cnt2].nxt = h2[u];
h2[u] = cnt2;
}
void tarjan(int u, int fa){
dfn[u] = low[u] = ++tot;
st[++top] = u;
ins[u] = 1;
for(int i = h[u]; i; i = e[i].nxt){
int v = e[i].to;
if(!ins[v]){
tarjan(v, u);
low[u] = min(low[u], low[v]);
}
else if(v != fa){
low[u] = min(low[u], dfn[v]);
}
}
if(low[u] == dfn[u]){
++idx;
int v;
do{
v = st[top--];
bel[v] = idx;
sum1[idx]++;
ins[v] = 0;
}while(v != u);
}
}
long long ksm(long long a, long long b){
long long ans = 1;
while(b){
if(b & 1) ans = (ans * a) % mod;
a = (a * a) % mod;
b >>= 1;
}
return ans;
}
void init(int u, int fa){
sz[u] = g[u];
for(int i = h2[u]; i; i = e2[i].nxt){
int v = e2[i].to;
if(v == fa) continue;
init(v, u);
sz[u] += sz[v] + 1;
}
}
void dfs(int u, int fa){
dp[u][0] = ksm(2, g[u]) % mod, dp[u][1] = (ksm(2, sum1[u] + g[u]) - dp[u][0] + mod) % mod;
for(int i = h2[u]; i; i = e2[i].nxt){
int v = e2[i].to;
if(v == fa) continue;
dfs(v, u);
dp[u][1] = (dp[u][1] * (2 * dp[v][0] % mod + dp[v][1]) % mod + dp[u][0] * dp[v][1] % mod) % mod;
dp[u][0] = dp[u][0] * (2 * dp[v][0] % mod) % mod;
}
if(u == 1) ans += dp[u][1], ans %= mod;
else ans += (dp[u][1] * ksm(2, sz[1] - sz[u] - 1) % mod) % mod, ans %= mod;
}
int main(){
n = read(), m = read();
for(int i = 1; i <= m; i++){
int u = read(), v = read();
add(u, v), add(v, u);
}
tarjan(1, 0);
for(int i = 1; i <= n; i++){
for(int j = h[i]; j; j = e[j].nxt){
int v = e[j].to;
if(bel[i] == bel[v]) g[bel[i]]++;
else add2(bel[i], bel[v]);
}
}
for(int i = 1; i <= idx; i++) g[i] /= 2;
init(1, 0);
dfs(1, 0);
cout << ans << endl;
return 0;
}
[ARC115E] LEQ and NEQ#
如果不考虑第二个条件的话,那么答案显然是
容斥的基本条件,我们要找到一个共性,也就是能够容斥的性质。这一题中,容斥的对象就是不符合第二个条件的两项。所以我们可以设
处理
因为我们转移需要一整段的
统计答案也就变成
发现
这样转移是
关于找到左边第一个小于
#include <bits/stdc++.h>
using namespace std;
long long read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c - '0');
c = getchar();
}
return x * f;
}
long long n, ans, mod = 998244353;
long long a[500010], s[500010][2], dp[500010][2];
long long st[500010], top;
int main(){
n = read();
for(int i = 1; i <= n; i++){
a[i] = read();
}
dp[0][0] = s[0][0] = 1;
for(int i = 1; i <= n; i++){
while(top > 0 && a[st[top]] >= a[i]) top--;
if(!top){
for(int j = 0; j <= 1; j++) dp[i][j] = 1ll * (dp[i][j] + s[i - 1][j ^ 1] * a[i]) % mod;
}
else{
for(int j = 0; j <= 1; j++) dp[i][j] = 1ll * (dp[st[top]][j] + (s[i - 1][j ^ 1] - s[st[top] - 1][j ^ 1] + mod) * a[i]) % mod;
}
s[i][0] = (s[i - 1][0] + dp[i][0]) % mod;
s[i][1] = (s[i - 1][1] + dp[i][1]) % mod;
st[++top] = i;
}
if(n % 2 == 1) ans = (dp[n][1] - dp[n][0] + mod) % mod;
else ans = (dp[n][0] - dp[n][1] + mod) % mod;
cout << ans;
return 0;
}
P3800 Power收集#
这题的状态很明确,因为完全可以把每一个网格看成状态,网格之间相互转移,所以设状态
典型的单调队列形式,我们需要的只有一段区间中的最大值,并且区间移动是连续的。瓶颈在于我们枚举
其他还有可以优化的地方,比如由于一层的转移只与上一层有关,所以可以滚掉
P3594 [POI2015] WIL#
这题中,可以一次将任意长度小于等于
对于区间和,我们可以用前缀和
可以容易想到,“任意长度小于等于
考虑优化,对于一个左端点,我们一定是找它满足条件的最远右端点;同样,对于一个右端点,我们一定是找它满足条件的最远左端点。这个性质可以用上双指针,只需要枚举
现在的瓶颈是,因为我们判断一个区间能否满足条件,一定要找到它的最大修改区间才能一次做出决定,所以能否优化掉寻找最大修改区间的时间呢?可以用到单调队列,每枚举一个
如果此时的
由于一个数最多进出队列一次,并且
#include <bits/stdc++.h>
using namespace std;
int read(){
int x = 0, f = 1;
char c = getchar();
while(c < '0' || c > '9'){
if(c == '-') f = -1;
c = getchar();
}
while(c >= '0' && c <= '9'){
x = (x << 1) + (x << 3) + (c - '0');
c = getchar();
}
return x * f;
}
int n, m, k, t, ans;
int dp[4010][4010], a[4010][4010], q[4010];
int main(){
n = read(), m = read(), k = read(), t = read();
for(int i = 1; i <= k; i++){
int x = read(), y = read(), v = read();
a[x][y] = v;
}
for(int i = 1; i <= n; i++){
int head = 1, tail = 0;
for(int j = 1; j <= m; j++){
while(head <= tail && dp[i - 1][q[tail]] <= dp[i - 1][j]) tail--;
while(head <= tail && q[head] + t < j) head++;
q[++tail] = j;
dp[i][j] = max(dp[i][j], dp[i - 1][q[head]] + a[i][j]);
}
head = 1, tail = 0;
for(int j = m; j >= 1; j--){
while(head <= tail && dp[i - 1][q[tail]] <= dp[i - 1][j]) tail--;
while(head <= tail && q[head] - t > j) head++;
q[++tail] = j;
dp[i][j] = max(dp[i][j], dp[i - 1][q[head]] + a[i][j]);
}
}
for(int i = 1; i <= m; i++) ans = max(ans, dp[n][i]);
cout << ans << endl;
return 0;
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek智能编程
· 精选4款基于.NET开源、功能强大的通讯调试工具
· [翻译] 为什么 Tracebit 用 C# 开发
· 腾讯ima接入deepseek-r1,借用别人脑子用用成真了~
· DeepSeek崛起:程序员“饭碗”被抢,还是职业进化新起点?