1001题
Link with Bracket Sequence II
题目大意
给定一个长度为
n
n
n 的方括号数组,其中
0
0
0 表示该元素缺失,你有
m
m
m 种类型的括号可以将其替换。如果
a
i
>
0
a_i > 0
ai>0 则代表此元素为
a
i
a_i
ai 类型的左括号,如果
a
i
<
0
a_i < 0
ai<0 则代表此元素为
a
i
a_i
ai 类型的右括号。如果两个括号匹配则认为它有效。(特殊地,长度为
0
0
0 也认为是有效的括号序列。)
例如:若
A
A
A 为有效的括号序列,
X
=
−
Y
X = -Y
X=−Y,则
X
A
Y
XAY
XAY 也为一个有效的括号序列;若
A
A
A、
B
B
B 均为有效的括号序列,则
A
B
AB
AB 也为有效的括号序列。现需要输出你有多少种方案使得这个括号序列有效(答案对
1
e
9
+
7
1e9+7
1e9+7 取模)
思路
先对长度
n
n
n 进行考虑,因为括号序列必定是两两匹配,故
n
n
n 一定为偶数,奇数情况的
n
n
n 无方案,答案为
0
0
0 .
当
n
n
n 为偶数时,开始考虑不同括号之间的关系:
- 对于类似于 A B AB AB 这种括号序列,若 B B B 是我们正在考虑的, A A A 已经确定,则方案数 A B AB AB应当是方案数 A × A\times A× 方案数 B B B 。
- 对于类似于
X
A
Y
XAY
XAY 这种括号序列,因为
A
A
A 的方案数已知,故考虑
X
X
X 和
Y
Y
Y 匹配得到的方案数。
- 如果 X Y XY XY 的和为 0 0 0 或有一方为 0 0 0 ,则方案数为 1 1 1 .
- 若两者均为 0 0 0 ,则方案数为 m m m 。其他情况则不匹配,方案数为 0 0 0.
基于以上两种思路,如果我们用 a n s ans ans 数组代表最终结果, d p dp dp 数组表示目前正在考虑的互相匹配的括号序列的方案数的话。我们就可以得出:
- 对于第一种情况: a n s [ i ] [ j ] ans[i][j] ans[i][j] = ( a n s [ i ] [ j ] + a n s [ i ] [ k ] ) (ans[i][j] + ans[i][k]) (ans[i][j]+ans[i][k]) × \times × f [ k + 1 ] [ j ] f[k+1][j] f[k+1][j];
- 对于第二种情况: f [ i ] [ j ] f[i][j] f[i][j] = a n s [ i + 1 ] [ j − 1 ] ans[i+1][j-1] ans[i+1][j−1] × \times ×倍数;
其中 i i i, j j j 表示当前序列的左端点和右端点。之后只需要按长度遍历每一种情况即可。
代码
#include<iostream>
#include<string.h>
using namespace std;
#define ll long long
const int mod = 1e9+7;
const int N = 600;
int dp[N][N],ans[N][N];
void solve(){
int n,m;
cin>>n>>m;
memset(dp,0,sizeof dp);
memset(ans,0,sizeof ans);
int a[N];
for(int i = 1;i <= n;i ++)cin>>a[i];
if(n & 1){//奇数长度的括号序列不匹配
cout<<0<<endl;
return;
}
for(int i = 0;i <= n;i ++)ans[i+1][i] = 1;
for(int len = 2;len <= n;len += 2){//奇数长度的括号序列一定不匹配,故len+=2
for(int l = 1;l + len - 1 <= n;l++){
int r = l + len - 1;
if(a[l] >= 0 && a[r] <= 0){
int b = 0;
if(a[l] == 0 && a[r] == 0)b = m;//两端都是0,说明有m个匹配的方法
else if(a[l] == 0 || a[r] == 0)b = 1;//有一端确定,说明只有一种匹配方法
else if(a[l] == -1*a[r])b = 1;//两端匹配成功,只有一种匹配方法
else b = 0;//没有匹配成功
dp[l][r] = 1ll*ans[l+1][r-1]*b%mod;
}
for(int i = l;i <= r;i += 2){
ans[l][r] = (ans[l][r] + 1ll*ans[l][i-1]*dp[i][r])%mod;
}
}
}
cout<<ans[1][n]<<endl;
}
int main(){
int t;
cin>>t;
while(t--)solve();
system("pause");
return 0;
}
1003题
Magic
思路
显然是差分约束(不懂的可以做一下这一题:P5960 【模板】差分约束算法)。我们将区间操作转换为点操作,设 a [ i ] a[i] a[i] 表示前 i i i 个魔法塔中加入的魔法原料之和。
-
每个魔法塔都能影响半径为 k k k 内的魔法塔的魔法值,则第 i i i 个魔法塔对魔法值的需求可以表示为 a [ m i n ( i + k − 1 , n ) ] − a [ m a x ( 1 , i − k + 1 ) − 1 ] ≥ p i a[min(i+k-1,n)]-a[max(1,i-k+1)-1]\ge p_i a[min(i+k−1,n)]−a[max(1,i−k+1)−1]≥pi;
-
每个魔法塔的魔法原料都 ≥ 0 \ge0 ≥0,则有 a [ i ] − a [ i − 1 ] ≥ 0 a[i]-a[i-1]\ge0 a[i]−a[i−1]≥0;
-
对每个区间添加魔法原料的限制可以表示为 a [ R i ] − a [ L i − 1 ] ≤ B i a[R_i]-a[L_i-1]\le B_i a[Ri]−a[Li−1]≤Bi,即 a [ L i − 1 ] − a [ R i ] ≥ − B i a[L_i-1]-a[R_i]\ge-B_i a[Li−1]−a[Ri]≥−Bi。
因为最终答案求的是 a [ n ] a[n] a[n] 的最小值,我们可以转化为求最长路的形式,从节点 0 0 0 开始跑 SPFA,如果存在正环,无解;否则,答案就是 d i s t [ n ] dist[n] dist[n]。
代码
bool spfa() {
memset(cnt, 0, sizeof(cnt));
memset(dist, -127, sizeof(dist));
memset(st, 0, sizeof(st));
dist[0] = 0, st[0] = 1;
queue<int> q;
q.push(0);
while (q.size()) {
int x = q.front();
q.pop();
st[x] = 0;
for (auto i : e[x]) {
int y = i.first, z = i.second;
if (dist[y] < dist[x] + z) {
dist[y] = dist[x] + z;
cnt[y] = cnt[x] + 1;
if (cnt[y] >= n + 1) return 0;
if (!st[y]) q.push(y), st[y] = 1;
}
}
}
return 1;
}
void solve() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) {
int x;
scanf("%d", &x);
int l = max(1, i - k + 1), r = min(i + k - 1, n);
e[l - 1].push_back({r, x});
}
for (int i = 1; i <= n; i++) e[i - 1].push_back({i, 0});
scanf("%d", &m);
while (m--) {
int l, r, x;
scanf("%d%d%d", &l, &r, &x);
e[r].push_back({l - 1, -x});
}
if (spfa())
printf("%d\n", dist[n]);
else
puts("-1");
for (int i = 0; i < N; i++) e[i].clear();
}