1001 Link with Bracket Sequence II
区间dp题。求形成合法括号序列的方案数。
每次看到括号序列的题就毫无办法,今天特意系统学了一下。同样是括号的背景,可能做法很不同,有考察栈的、有变成-1和1求前缀和的,有dfs搜索的,也有dp的。观察这题的数据量,应该很明显dp了。(附上大佬名言:求方案数的题,还给了很大的模数的,要么是dp要么是数学的排列组合找规律。
为什么要用区间dp呢?它跟普通dp有什么区别?一般的DP主要是特征是一次往往只操作一个数值或者存储可以不连续的物品的状态(比如背包问题中,每一个点存储的状态中,拿到的物品的编号并不要求一定连续)。而区间DP,则是要求DP数组中每个点的状态映射到原本的数据集中都会与附近的元素有所关联,其中,连续区间的长度、起始点的位置、子区间分割点的位置都会有所变化。
标程代码:
#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int mod = 1000000007;
const int MN=500;
int n, m;
int a[MN + 5];
int f[MN + 5][MN + 5], g[MN + 5][MN + 5];
void solve()
{
cin>>n>>m;
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++)
f[i][j]=g[i][j]=0; //初始化,用memset如果数组太大会很浪费时间
for (int i = 1; i <= n; i++) cin>>a[i];
if (n & 1) //如果是奇数,肯定没有合法的方案
{
cout<<"0"<<'\n';
return;
}
for (int i = 0; i <= n; i++) g[i + 1][i] = 1; //DP边界
for (int len = 2; len <= n; len += 2)
{
for (int l = 1; l + len - 1 <= n; l++)
{
int r = l + len - 1;
if (a[l] >= 0 && a[r] <= 0)
{
int e;
if (a[l] == 0 && a[r] == 0) e = m;
else if (a[l] == 0 || a[r] == 0) e = 1;
else if (a[l] + a[r] == 0) e = 1;
else e = 0;
f[l][r] = (ll)g[l + 1][r - 1] * e % mod;
}
for (int nl = l; nl <= r; nl += 2)
{
g[l][r] = (g[l][r] + (ll)g[l][nl - 1] * f[nl][r]) % mod;
}
}
}
cout<<g[1][n]<<'\n';
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T;
cin>>T;
while (T--)
solve();
return 0;
}
几个思考:
①区间长度从小到大枚举,且每次加2,我们要先得到小区间的值来更新大区间,所以要从长度小的区间开始循环。
②最下面的for循环是在枚举断点,就像一个计数问题一样,区间A方案数乘上区间B方案数。
③为什么要用两个数组?去掉g数组可以吗?
这个问题真的困扰了我很久(我是真的好笨好菜啊呜呜呜),后来输出DP数组观察了一下。
我们能发现,到了后期,结果增加得非常快,并且答案远远大于预期的正确答案。说明,一定有重复的地方。为什么重复了呢?怎么样才能不重复计数呢?这个问题真的困扰了我好久,一直想不通,这里引用一下carry老师博客里的解释,真的非常清晰明确。
指路-> 2022"杭电杯"中超联赛·第四场 - CarryNotKarry
(这个题真的理解了一整天....还翻出以前学的区间dp题目重新理解重新复习,这个题和区间dp板子题的区别主要在于,要想到直接分区间会算重复,要找出如何去重的方法。呜呜呜我为什么这么菜呜呜呜
1002 Link with Running
——jmy
一开始没有注意到 ei可以等于 00,直接当成板子题交了一发,然后 Wa 了。
然后发现 dis[], fit[]
数组,pair
第一位没开 long long
,又 Wa 了几发。
紧接着看 Clarification 发现了问题的所在,dis
相等时也入一次队,果不其然反复入队 tle 了。又写了个 spfa,果然照样寄。然后心态有点崩,想下班了。
赛后补题发现只要加个 if(vis[u] >= n) continue;
就莽过去了,虽然一点都不严谨……
Djkstra每个点入队都会更新其他点一次,所以当更新了n次的时候,其实最短路就跑完了。还在入队说明有环了。
代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<utility>
#include<vector>
#include<queue>
#include<tuple>
#define mt make_tuple
// #define int long long
typedef long long LL;
#define pii pair<LL, int>
using namespace std;
const int maxn = 1e5 + 10;
const LL INF = 1e18;
LL dis[maxn], fit[maxn];
int vis[maxn];
vector<tuple<int, int, int> > e[maxn];
inline int read() {
int x = 0, f = 1;
char ch = getchar();
for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;
for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
return f * x;
}
signed main() {
int t = read();
while(t--) {
int n = read(), m = read();
for(int i = 1; i <= n; i++) {
dis[i] = INF;
fit[i] = 0;
vis[i] = 0;
e[i].clear();
}
for(int i = 1; i <= m; i++) {
int a = read(), b = read(), d = read(), p = read();
e[a].push_back(mt(b, d, p));
// e[b].push_back(mt(a, d, p));
}
priority_queue<pii, vector<pii >, greater<pii > > q;
dis[1] = 0; fit[1] = 0;
q.push(pii{0, 1});
while(!q.empty()) {
int u = q.top().second; q.pop();
if(vis[u] >= n) continue;
vis[u]++;
for(auto i : e[u]) {
int v = get<0>(i), w = get<1>(i), val = get<2>(i);
if(dis[v] > dis[u] + w) {
dis[v] = dis[u] + w;
fit[v] = fit[u] + val;
q.push({dis[v], v});
}
else if(dis[v] == dis[u] + w && fit[v] < fit[u] + val) {
fit[v] = fit[u] + val;
q.push({dis[v], v});
}
}
}
printf("%lld %lld\n", dis[n], fit[n]);
}
return 0;
}
1004 Link with Equilateral Triangle
输出No即可 (是一个把整队心态搞炸的签到题,谢谢出题人
1006 BIT Subway
阅读理解,就大概是初高中应用题的样子吧?
1007 Climb Stairs
赛中和队友一起在想怎么贪心(呜呜呜我好菜
这题贪心(要加优化)和线段树维护都能做
赛中完全没想到用线段树维护......感觉自己会的线段树就是最基本的区间和区间修改这种操作,一涉及到维护其他东西就傻了....这题代码应该会自己写一遍(放个凳子以后来补