您的位置:首页 > 其它

NOIP模拟 10.17 单调队列 + 树形Dp + 区间Dp

2017-10-17 21:09 411 查看
烟火 (fireworks.cpp/c/pas)

【题目描述】

城镇的主干道上有n个区域,从左到右编号为1到n,每个区域之间相距1个单位距离。在节日中要放m个烟火,第i个烟火会在ti时刻的ai区域放。如果在ti时刻你所处区域为x,那么你可以获得bi - | ai - x |的快乐值。在每个单位时间你可以移动不超过d个单位距离,初始的位置是任意的,求通过移动能获得快乐值和的最大值。

【输入格式】

第一行三个整数n,m,d。

接下来m行,每行三个整数ai,bi,ti。

【输出格式】

一行,能够获得快乐值和的最大值。

【样例输入】

10 2 1

1 1000 4

9 1000 4

【样例输出】

1992

【数据范围】

对于30%的数据, n <= 100,m <= 20。

对于100%的数据,1 <= n <= 150000,1 <= m <= 300,1 <= d, ai <= n,1 <= bi, ti <= 10^9,对于i >= 2有ti >= ti-1。

设dp[i][j]为第i时段站在j位置的最大收益. 由于j可以从dp[i-1]的某一段区间转移过来, 所以可用单调队列维护. 复杂度n*m.

#include<stdio.h>
#include<cmath>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long dnt;
const int maxn = 150005;
int n, m, d, last, cur, q[maxn];
dnt dp[2][maxn], a[305], b[305], tim[305], ans;
inline const dnt read(){
register dnt  x = 0;
register char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
while(ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - '0', ch = getchar();
return x;
}
dnt calc(int pos, int sta){
dnt ret = 0;
int ti = tim[sta];
for(int i = sta; tim[i] == ti && i <= m; ++i)
ret += b[i] - abs(a[i] - pos);
return ret;
}
int main(){
freopen("fireworks.in", "r", stdin);
freopen("fireworks.out", "w", stdout);
n = read(), m = read(), d = read();
for(int i = 1; i <= m; ++i)
a[i] = read(), b[i] = read(), tim[i] = read();
for(int i = 1; i <= m; ++i){
if(tim[i] == tim[i - 1]) continue;
last = cur, cur = cur ^ 1;
int h = 1, t = 0;
dnt lim = 1ll * (tim[i] - tim[i - 1]) * d;
if(lim > n) lim = n;
for(int j = 1; j <= n; ++j){
while(h <= t && q[h] + lim < j) ++h;
while(h <= t && dp[last][j] >= dp[last][q[t]]) --t;
q[++t] = j;
dp[cur][j] = dp[last][q[h]] + calc(j, i);
}
h = 1, t = 0;
for(int j = n; j; --j){
while(h <= t && q[h] - lim > j) ++h;
while(h <= t && dp[last][j] >= dp[last][q[t]]) --t;
q[++t] = j;
dp[cur][j] = max(dp[cur][j], dp[last][q[h]] + calc(j, i));
}
}
ans = dp[cur][1];
for(int i = 2; i <= n; ++i) ans = max(dp[cur][i], ans);
printf("%I64d\n", ans);
}
/*
150000 2 2
1 1000 1
10000 2000 2
*/


购物 (shopping.cpp/c/pas)

【题目描述】

商店里有n个物品,第i个物品的价格为ci元。每个物品只能买一次。商店发行了n张优惠券,每个物品各有一张优惠卷。如果使用了第i张优惠券,可以使该物品便宜di元钱,必须买商品才能够使用相对应的优惠券。第1张优惠卷可以无条件使用,但对于第i>=2张优惠卷,如果需要使用第i张优惠券,则必须先使用xi这张优惠券。

现在有b元钱,问最多能购买多少商品。

【输入格式】

第一行两个整数n,b。

接下来n行,第i行先有两个整数ci,di,如果i >= 2,紧接着有第三个整数xi。

【输出格式】

一行,一个整数表示最多购买的商品数量。

【样例输入】

8 9

4 3

8 3 1

2 1 1

4 2 2

7 2 2

3 1 2

7 3 5

2 1 3

【样例输出】

4

【数据范围】

对于30%的数据,n <= 100。

对于100%的数据,1 <= n <= 5000,1 <= b <= 10^9,1 <= di < ci <= 10^9,对于i >= 2有1<= xi < i.

树形dp f[i][j][0/1]表示当前i节点买j个物品i用不用优惠券的最小花费. 感觉n^3, 实际上是n^2的. 因为每一个节点最多更新其他所有节点. 不要被表面所蒙蔽.

#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn = 5005;
int n, b, num, siz[maxn];
long long f[maxn][maxn][2];
int c[maxn], d[maxn], h[maxn];
struct edge{ int nxt, v;}e[maxn];
inline void add(int u, int v){ num++;e[num].v = v; e[num].nxt = h[u]; h[u] = num;}
void dfs(int u){
for(int i = 0; i <= n; ++i) f[u][i][0] = f[u][i][1] = 2e18;
f[u][1][1] = c[u] - d[u]; siz[u] = 1;
f[u][0][0] = 0, f[u][1][0] = c[u];
for(int p = h[u]; p; p = e[p].nxt){
int v = e[p].v;
dfs(v);
for(int i = siz[u]; i >= 0 ; --i)
for(int j = 0; j <= siz[v]; ++j){
f[u][i + j][1] = min(f[u][i + j][1], f[u][i][1] + f[v][j][1]);
f[u][i + j][1] = min(f[u][i + j][1], f[u][i][1] + f[v][j][0]);
f[u][i + j][0] = min(f[u][i + j][0], f[u][i][0] + f[v][j][0]);
}
siz[u] += siz[v];
}
}

int main(){
freopen("shopping.in", "r", stdin);
freopen("shopping.out", "w", stdout);
int x;
scanf("%d%d", &n, &b);
for(int i = 1; i <= n; ++i){
scanf("%d%d", &c[i], &d[i]);
if(i >= 2) scanf("%d", &x), add(x, i);
}
dfs(1);
for(int i = siz[1]; i >= 0; --i)
if(f[1][i][1] <= b || f[1][i][0] <= b){
printf("%d\n", i);
break;
}
return 0;
}


括号匹配 (parenthesis.pas/cpp/c)

【题目描述】

给出长度为N的括号序列(只包含(,),[,]),问有多少种方法删掉这些括号的一个子集,使得剩下的括号序列是合法的,请注意不能全部删完。

【输入格式】

输入的第一行是一个整数N,表示序列的长度。

接下来一行N个字符,表示括号序列。

【输出格式】

一行,表示方案数模1000000007的结果。

【样例输入】

4

()[]

【样例输出】

3

【数据范围】

30%的数据保证:1 <= N <= 20。

100%的数据保证:1 <= N <= 300。

区间dp. 方案数为: 合法括号内的合法删数 * 合法括号外的合法删数. 这样就可以区间dp了.

转移显然只有两种决策,第l个括号不进行匹配(删除),转移到dp(l+1,r),和第i个括号进行匹配,

直接进行暴力枚举匹配即可。

#include<stdio.h>
#include<cstring>
#define deeper(a) memset(a, -1, sizeof(a))
const int mod = 1000000007;
typedef long long dnt;
int n;
char s[305];
dnt f[305][305];
dnt dp(int l, int r){
if(~f[l][r]) return f[l][r];
if(l >= r) return 1;
dnt& ret = f[l][r]; ret = 0;
ret = dp(l + 1, r);
char aim;
if(s[l] == ')' || s[l] == ']') return ret % mod;
if(s[l] == '(') aim = ')';
if(s[l] == '[') aim = ']';
for(int i = l + 1; i <= r; ++i)
if(s[i] == aim)
ret = (ret + dp(l + 1, i - 1) * dp(i + 1, r)) % mod;
return ret % mod;
}
int main(){
freopen("parenthesis.in", "r", stdin);
freopen("parenthesis.out", "w", stdout);
scanf("%d", &n);
scanf("%s", s);
deeper(f);
printf("%I64d\n", (dp(0, n - 1) - 1) % mod);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: