10.11 NOIP模拟赛 DP + 线段树 + DP + 单调队列

时间:2022-12-17 07:30:01

今日rk1! 虽然t4数据有锅不过我也是受害者…以后要注意只要时间复杂度过得去, 挑尽量简单尽量拿手的来写.

Gray

内存限制 256MB 时间限制 1S
程序文件名 gray.pas/ gray.c/ gray.cpp
输入文件 gray.in 输出文件 gray.out
Gnaw刚刚学习在数字逻辑中学到了格雷码,它的定义是这样的,对于二进制数A,它对应的格雷码为G= A xor(A>>1),格雷码有个很有趣的性质是相邻二进制数对应的格雷码只有一位不同。
现在以”01?”的方式给出一个长为的二进制数m,’?’表示这个位置上可以填0或1.
现在给出n位分别对应的分数,当二进制m对应的格雷码在该位上为1是可以得到对应的分数。
输入
第一行一个二进制数串,包含0 1 ? 长为n
第二行n个数,表示对应位的分数(分数小于1000)
输出
一个整数,表示可能的二进制数能得到的最大分数
样例输入
Input1
00?0
1 2 4 8
Input2
????
1 2 4 8
样例输出
Output1
12
Output
15
30%数据 1<=n<=20
50%数据 1<=n<=2000
100%数据 1<=n<=200000 HINT 0010对应格雷码为0010xor001=0011,故得到12分

dp[i][0/1]表示到了第i位这位为0/1能获得的最大分数.

#include<stdio.h>
#include<cstring>
#include<algorithm>
using namespace std;
const int maxn = 200005;
const int inf = -2100000000;
char ch[maxn];
int dp[maxn][2], a[maxn], n;
int main(){
freopen("gray.in", "r", stdin);
freopen("gray.out", "w", stdout);
scanf("%s", ch + 1);
n = strlen(ch + 1);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
for(int i = 0; i <= n; ++i) dp[i][0] = dp[i][1] = inf;
dp[0][0] = 0;
for(int i = 1; i <= n; ++i){
if(ch[i] == '0') dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + a[i]);
if(ch[i] == '1') dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + a[i]);
if(ch[i] == '?'){
dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + a[i]);
dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] + a[i]);
}
}
printf("%d\n", max(dp[n][0], dp[n][1]));
return 0;
}

number

内存限制 256MB 时间限制 1S
程序文件名 number.pas/number.c/number.cpp
输入文件 number.in 输出文件 number.out
Miss D 是学财会的高材生,她有次扔给了gnaw N个数,让gnaw统计第l到第r个数中最小的数是多少。
Miss D会有很多次询问哟~
输入
一个整数 n 表示有n个数据
之后一行有n个数字,表示需要处理的n个数字(<=1e9)
下一行一个数字q
之后又q 行 每行两个整数l,r(1<=l<=r<=n)
输出
Q行 每行一个数字,表示从第l到r个数中最小的数是多少
样例输入 5 3 2 1 4 3 3 1 3 2 4 4 5 样例输出
1
1
3
数据范围
50% 1<=n<=10000 1<=q<=1000
100% 1<=n<=100000 ,1<=q<=10000

线段树求区间最小值.(st表也可)

#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn = 100005;
const int inf = 2100000000;
int a[maxn], n, q;
struct node{
node *ls, *rs;
int cmin;
}pool[maxn * 4], *tail = pool, *root;
node* build(int lf, int rg){
node *bt = ++tail;
if(lf == rg) {bt->cmin = a[lf]; return bt;}
int mid = (lf + rg) >> 1;
bt->ls = build(lf, mid), bt->rs = build(mid + 1, rg);
bt->cmin = min(bt->ls->cmin, bt->rs->cmin);
return bt;
}
int query(node *bt, int lf, int rg, int L, int R){
if(L <= lf && rg <= R) return bt->cmin;
int mid = (lf + rg) >> 1, rt1 = inf, rt2 = inf;
if(L <= mid) rt1 = query(bt->ls, lf, mid, L, R);
if(R > mid) rt2 = query(bt->rs, mid + 1, rg, L, R);
return min(rt1, rt2);
}
int main(){
freopen("number.in", "r", stdin);
freopen("number.out", "w", stdout);
scanf("%d", &n);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
root = build(1, n);
scanf("%d", &q);
for(int i = 1; i <= q; ++i){
int l, r;
scanf("%d%d", &l, &r);
printf("%d\n", query(root, 1, n, l, r));
}
}

hotel

内存限制 256MB 时间限制 1S
程序文件名 hotel.pas/hotel.c/ hotel.cpp
输入文件 hotel.in 输出文件 hotel.out
Miss D和gnaw出去玩的时候,发现一个很奇怪的旅馆,宾馆老板特别喜欢数字4和7,如果一个房间里住4或7个人,他就会很开心,不然他甚至不想让这个房间里住人。现在告诉你每个房间住的人数(7人以内),将一个原在i号房间的人移动到j房间的代价是abs(i-j),要想能满足老板的要求,花费的代价是多少?
输入
第一行一个数n,表示房间数
第二行n个数,表示每个房间原先住着的人数
输出
一个数,表示按照老板要求最小的花费
如果不能按要求分配,输出-1
样例输入
7
1 0 7 0 0 0 3
样例输出
6
数据范围
50%:
1<=n<=1e3
100%:
1<=n<=1e5

dp[i][0 ~ 14]表示从1~i再向外走出去-7(负数就是进来)~7个人就能使1~i房间合法的最小费用.

#include<stdio.h>
#include<cmath>
#include<cstring>
#include<algorithm>
#define fufil(a) memset(a, 0x3f, sizeof(a))
#define inf 1000000000000000
using namespace std;
typedef long long dnt;
int n;
int a[100010];
dnt dp[100010][15], ans;
inline bool check(int x){
if(x == 0 ||x == 4 || x == 7) return true;
return false;
}
int main(){
freopen("hotel.in", "r", stdin);
freopen("hotel.out", "w", stdout);
scanf("%d", &n); fufil(dp);
for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);
dp[0][7] = 0; ans = inf;
for(int i = 0; i < n; ++i)
for(int j = 0; j <= 14; ++j)
if(dp[i][j] < inf){
int t = j - 7;
for(int k = 0; k <= 14; ++k){
int u = k - 7;
if(check(a[i + 1] + t - u)) //t就是i到i+1的费用
dp[i + 1][k] = min(dp[i + 1][k], dp[i][j] + abs(t));
}
}
ans = dp[n][7]; //从1~n不进也不出(n之后也没房间供进来和出去了)
if(ans >= inf) puts("-1");
else printf("%I64d\n", ans);
}

gift
内存限制 256MB 时间限制 1S
程序文件名 gift.pas/gift.c/ gift.cpp
输入文件 gift.in 输出文件 gift.out
Gnaw给Miss D准备了一个长为n的礼物,还让m个朋友来帮他完成这个礼物,每个人开始是站在位置p,最多可以装扮连续的长为x的部分,一个人可以不参与,一旦参与,他装扮的部分一定包含他一开始站的位置。每个人装扮长为1的部分能让整个礼物增加y的美丽度,gnaw想让整个礼物的美丽值最高,会是多少?
输入
第一行两个整数n m,表示礼物长度和朋友数。
接下来n行,每行3个整数,分别为一个人最多装扮长度,单位美丽值和初始位置。
输出
一个整数,表示整个礼物最大的美丽值
样例输入
8 4
3 2 2
3 2 3
3 3 5
1 1 7
样例输出
17
Hint
1 号朋友装扮 [1, 2]; 2 号朋友装扮 [3, 4]; 3 号朋友装扮 [5, 7]; 4号朋友没有参与装饰
数据范围
30%:
1 <= m <= 100
1 <= n <= 100
1 <= y <= 10 000
100%:
1 <= m <= 16 000
1 <= n <= 100
1 <= y <= 10 000

数据欺骗我…n和m的数据范围是搞反了的…本来写了个和一次函数有关的一维dp转移方程, 用李超线段树就能过维护二维坐标系单点上最大/小值,是nlog(nm)的做法。 后来嫌麻烦用set写了n^2 + nmlog(nm)也能过, 但是n不是100了…
单调队列也能做, 是nm的.
Dp[i][j]表示前i个人装饰前j个单位能得到的最大魅力度
Dp[i][j]=max(dp[i-1][k]+(j-k)*w);(k+1<=原始位置<=j)
经过变形,可以得到dp[i][j]=max(dp[i-1][k]-k*w)+j*w,对于每一个dp[i][j]来说,j是常量,而k是变量,故将dp[i-1][k]-k*w部分求max,而且根据条件,k是满足条件的连续长度内的数 .
dp[i-1][k] - k * w用单调队列维护即可. 注意如果徒步到j的话就从dp[i-1][j]和dp[i][j-1]转移过来即可.
对于合法的i和j,定义是j要在第i人的位置之后, 若在之前的话那么实际上第i个人是装饰了超过j的了(因为必须要包括当前位置), 与定义矛盾.

#include<stdio.h>
#include<algorithm>
using namespace std;
const int maxn = 200005;
int n, m;
int dp[105][maxn], q[maxn];
struct point{ int s, l, p;}a[105];
inline bool cmp(point x, point y){
return x.s < y.s;
}
int main(){
freopen("gift.in", "r", stdin);
freopen("gift.out", "w", stdout);
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; ++i) scanf("%d%d%d", &a[i].l, &a[i].p, &a[i].s);
sort(a + 1, a + m + 1, cmp);
for(int i = 1; i <= m; ++i){
int h = 1, t = 0;
q[++t] = max(a[i].s - a[i].l, 0);
for(int j = 1; j <= n; ++j){
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
if(j >= a[i].s + a[i].l) continue;
while(h <= t && q[h] + a[i].l < j) ++h;
if(j < a[i].s){
int tmp = dp[i - 1][j] - j * a[i].p;
while(h <= t && tmp > dp[i - 1][q[t]] - a[i].p * q[t]) --t;
q[++t] = j;
continue;
}
dp[i][j] = max(dp[i][j], dp[i - 1][q[h]] + (j - q[h]) * a[i].p);
}
}
printf("%d\n", dp[m][n]);
}

DP长路漫漫啊, 不过NOIP2017已经快到了…