10184. 「一本通 5.6 例 1」任务安排 1 & 10185. 「一本通 5.6 例 2」任务安排 2 & 10186. 「一本通 5.6 例 3」任务安排 3


由于这三道题都是同一道题,只是数据范围不同,故拉成同一篇博客

题意

有$n$个任务,机器人($jxc$)会把这$n$个任务分成若干批,从时刻$0$开始,任务将被分批加工,执行第$i$个任务需要花费时间为$T_i$。另外,在每一批任务开始前,机器需要$S$的启动时间。

同一批任务将在同一时刻完成。每个任务的费用$=$它完成的时间$\times$一个费用的系数$C_i$。

思路

对于$n \leq 5000$的数据

时间复杂度$O(N^2)$

设$f[i]​$表示前$i​$个任务所花费最小价值。

初始化:

$$f[i]=inf|1<=i<=n,f[0]=0​$$

转移方程:

$$f[i]=min \left{f[j-1]+S(c[n]-c[j-1])+t[i](c[i]-c[j-1])|1<=j<=i \right}​$$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<bits/stdc++.h>
using namespace std;
int n,S,f[5010],t[5010],c[5010];
int main(){
cin>>n>>S;
for(int i=1;i<=n;i++){
int x,y;
cin>>x>>y;
t[i]=t[i-1]+x;
c[i]=c[i-1]+y;
}
memset(f,63,sizeof(f));
f[0]=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
f[i]=min(f[j-1]+S*(c[n]-c[j-1])+t[i]*(c[i]-c[j-1]),f[i]);
}
}
cout<<f[n]<<endl;
return 0;
}

对于$1\leq N \leq 10^4$的数据

时间复杂度为$O(N)$

转移方程同上,
$$f[i]=min \left{ f[j-1]+S(c[n]-c[j-1])+t[i](c[i]-c[j-1])|1<=j<=i \right }​$$

将只含有$i​$的项移出,得:

$$f[i]=min \left { f[j-1]-Sc[j-1]-t[i]c[j-1]|1<=j<=i \right }+Sc[n]+t[i]c[i]​$$

合并同类项,得:

$$f[i]=min \left { f[j]-(S+t[i])c[j]|0<=j<=i-1 \right }+Sc[n]+t[i]*c[i]$$

设$y=f[j]$,$k=S+t[i]$,$x=c[j]$,$b=Sc[n]+t[i]c[i]$

那么:

$$f[i]=y-kx+b$$

即:
$$y=kx+(f[i]-b)$$

  • 斜率优化一:
    因为$t[i]$在递增,斜率$k$在变大,直线越来越陡,所以队首的两个点之间斜率至少要大于$(S+t[i])$。
    即:$(f[q[l+1]]-f[q[l]])/(c[q[l+1]]-c[q[l]])>(S+t[i])$
    即:若$(f[q[l+1]]-f[q[l]])/(c[q[l+1]]-c[q[l]])<=(S+t[i])$,队首弹出
    化简,得:若$(f[q[l+1]]-f[q[l]])<=(S+t[i])*(c[q[l+1]]-c[q[l]])$,队首弹出

  • 斜率优化二:
    确保右侧的斜率大于左侧的斜率,即:$(f[q[r]]-f[q[r-1]])/(c[q[r]]-c[q[r-1]])<(f[i]-f[q[r]])/(c[i]-c[q[r]])​$。
    若$(f[q[r]]-f[q[r-1]])/(c[q[r]]-c[q[r-1]])>=(f[i]-f[q[r]])/(c[i]-c[q[r]])​$,队尾弹出。
    化简,得:若$(f[i]-f[q[r]])(c[q[r]]-c[q[r-1]])<=(f[q[r]]-f[q[r-1]])(c[i]-c[q[r]])​$,队尾弹出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<bits/stdc++.h>
using namespace std;
int n,S,f[300010],t[300010],c[300010],q[300010],l,r;
int main(){
cin>>n>>S;
for(int i=1;i<=n;i++){
int x,y;
cin>>x>>y;
t[i]=t[i-1]+x;
c[i]=c[i-1]+y;
}
memset(f,63,sizeof(f));
f[0]=0;
l=1,r=1;
for(int i=1;i<=n;i++){
int k=S+t[i];
while(l<r&&f[q[l+1]]-f[q[l]]<=k*(c[q[l+1]]-c[q[l]])) l++;
f[i]=f[q[l]]-k*c[q[l]]+t[i]*c[i]+S*c[n];
while(l<r&&(f[i]-f[q[r]])*(c[q[r]]-c[q[r-1]])<=(f[q[r]]-f[q[r-1]])*(c[i]-c[q[r]])) r--;
q[++r]=i;
}
cout<<f[n]<<endl;
return 0;
}

对于$1 \leq N \leq 3 \times 10^5 , \left| T_i \right|<2^8$的数据

虽然添加了单调队列的优化,但是时间可以为负数,所以之前维护的单调性就不存在了,所以我们需要使用二分+单调队列来维护斜率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<bits/stdc++.h>
using namespace std;
long long n,S,f[300010],t[300010],c[300010],q[300010],l,r,L,R;
long long Cut(long long j){return f[j]-c[n]*t[j]+c[j]*t[j]-c[j]*S;}
int main(){
cin>>n>>S;
for(long long i=1;i<=n;i++){
long long x,y;
cin>>x>>y;
t[i]=t[i-1]+x;
c[i]=c[i-1]+y;
}
memset(f,127,sizeof(f));
f[0]=0;
L=0,R=0;
for(long long i=1;i<=n;i++){
l=0,r=R-1;long long ans=R;
while(l<=r){
long long mid=(l+r)>>1;
if((c[q[mid+1]]-c[q[mid]])*t[i]<=Cut(q[mid+1])-Cut(q[mid])) ans=mid,r=mid-1;
else l=mid+1;
}
long long j=q[ans];
f[i]=f[j]+(c[n]-c[j])*(t[i]-t[j]+S);
while(L<R&&(Cut(q[R])-Cut(q[R-1]))*(c[i]-c[q[R]])>=(Cut(i)-Cut(q[R]))*(c[q[R]]-c[q[R-1]])) R--;
q[++R]=i;
}
cout<<f[n]<<endl;
return 0;
}

10175. 「一本通 5.5 例 1」滑动窗口

题意

给一个长度为 $N$ 的数组,一个长为 $K$ 的滑动窗体从最左端移至最右端,你只能看到窗口中的 $K$ 个数,每次窗体向右移动一位,你的任务是找出窗体在各个位置时的最大值和最小值。

思路

DP水过去。。。
加上优先队列优化。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<bits/stdc++.h>
using namespace std;
int n,k,a[1000010],h,t,f1[1000010],f2[1000010];
struct node{
int x,id;
}q[1000010];
void dp1(){
h=t=0;
for(int i=1;i<=n;i++){
while(h<=t&&i-q[h].id+1>k) h++;
while(h<=t&&a[i]<=q[t].x) t--;
q[++t].id=i;
q[t].x=a[i];
f1[i]=q[h].x;
}
}
void dp2(){
h=t=0;
for(int i=1;i<=n;i++){
while(h<=t&&i-q[h].id+1>k) h++;
while(h<=t&&a[i]>=q[t].x) t--;
q[++t].id=i;
q[t].x=a[i];
f2[i]=q[h].x;
}
}
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
fill(f1,f1+1000005,2e9);
fill(f2,f2+1000005,-2e9);
dp1();dp2();
for(int i=k;i<=n;i++){
cout<<f1[i]<<" ";
}cout<<endl;
for(int i=k;i<=n;i++){
cout<<f2[i]<<" ";
}cout<<endl;
return 0;
}

10148. 「一本通 5.1 例 2」能量项链

题意

有$n$颗珠子,每个珠子都有自己的标记,现将珠子串成项链(环),每相邻的两颗珠子可以通过聚合释放出能量=三颗珠子的标记之积,并合并成一颗更大的珠子,问聚合成一颗珠子后最大释放的能量。

比如有一串项链:$2–>3–>5–>10$,那么把第一颗与第四颗珠子合并后产生的能量$=2\times3\times10$。那么这一串项链最多可释放:$(((4\bigotimes1)\bigotimes2)\bigotimes3)=(10\times2\times3)+10\times3\times5+10\times10\times5=710$

思路

设$f[i][j]$表示区间$[i,j]$的珠子合并后产生能量的最大值。
$$
f[i][j]=max(f[i][k]+f[k+1][j]+a[i]a[j+1]a[k+1])
$$
也是把环展开就好了。。。233333333333………….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include<algorithm>
#include<bitset>
#include<complex>
#include<deque>
#include<exception>
#include<fstream>
#include<functional>
#include<iomanip>
#include<ios>
#include<iosfwd>
#include<iostream>
#include<istream>
#include<iterator>
#include<limits>
#include<list>
#include<locale>
#include<map>
#include<memory>
#include<new>
#include<numeric>
#include<ostream>
#include<queue>
#include<set>
#include<sstream>
#include<stack>
#include<stdexcept>
#include<streambuf>
#include<string>
#include<typeinfo>
#include<utility>
#include<valarray>
#include<vector>
#include<cctype>
#include<cerrno>
#include<cfloat>
#include<ciso646>
#include<climits>
#include<clocale>
#include<cmath>
#include<csetjmp>
#include<csignal>
#include<cstdarg>
#include<cstddef>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#define E 200010
using namespace std;
inline int read(){
int res=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') res=res*10+ch-'0',ch=getchar();
return res*f;
}
inline void write(int x){
if(x<0) putchar('-'),x=-x;
if(x<10) putchar(x+'0');
else{
write(x/10);
putchar(x%10+'0');
}
}
//queue<int> q;
//set<int> s;
//priority_queue<int> q1;
//priority_queue<int,vector<int>,greater<int> > q2;
//list<int> l;
//stack<int> s;
int n,f[210][210],a[210],sum[210],ans;
int main(){
n=read();
for(int i=1;i<=n;i++) a[i]=read(),a[i+n]=a[i];
memset(f,0,sizeof(f));
for(int i=1;i<=n*2;i++) f[i][i]=0;
for(int L=2;L<=n;L++){
for(int i=1;i<=n*2-L+1;i++){
int j=i+L-1;
for(int k=i;k<=j-1;k++){
f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+a[i]*a[j+1]*a[k+1]);
}
}
}
ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,f[i][i+n-1]);
}
write(ans);putchar('\n');
return 0;
}

10147. 「一本通 5.1 例 1」石子合并

题意

将 $n$ 堆石子绕圆形操场排放,现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆的石子数记做该次合并的得分。

请编写一个程序,读入堆数 $n$ 及每堆的石子数,并进行如下计算:

  1. 选择一种合并石子的方案,使得做 $n-1$ 次合并得分总和最大。
  2. 选择一种合并石子的方案,使得做 $n-1$ 次合并得分总和最小。

思路

DP水过去。。。

求最大值

设$f[i][j]$表示区间$[i,j]$得分的最大值。

很容易可以想到:
$$
f[i][j]=max(f[i][k]+f[k+1][j]+dist(i,j)))
$$

$$
dist(i,j)=a[i]+a[i+1]+…+a[j-1]+a[j]
$$

所以我们设$sum[i]=a[1]+a[2]+…+a[i-1]+a[i]$

可得:
$$
sum[i]=sum[i-1]+a[i]
$$
那么:
$$
f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1])
$$
但是,仔细读题,发现是环。。。

所以我们将环转换成链,即将$a$数组往后$n$个单位复制一遍。

最小值

与最大值差不多。改一个符号$max——>min$。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
#include<algorithm>
#include<bitset>
#include<complex>
#include<deque>
#include<exception>
#include<fstream>
#include<functional>
#include<iomanip>
#include<ios>
#include<iosfwd>
#include<iostream>
#include<istream>
#include<iterator>
#include<limits>
#include<list>
#include<locale>
#include<map>
#include<memory>
#include<new>
#include<numeric>
#include<ostream>
#include<queue>
#include<set>
#include<sstream>
#include<stack>
#include<stdexcept>
#include<streambuf>
#include<string>
#include<typeinfo>
#include<utility>
#include<valarray>
#include<vector>
#include<cctype>
#include<cerrno>
#include<cfloat>
#include<ciso646>
#include<climits>
#include<clocale>
#include<cmath>
#include<csetjmp>
#include<csignal>
#include<cstdarg>
#include<cstddef>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<ctime>
#define E 200010
using namespace std;
inline int read(){
int res=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') res=res*10+ch-'0',ch=getchar();
return res*f;
}
inline void write(int x){
if(x<0) putchar('-'),x=-x;
if(x<10) putchar(x+'0');
else{
write(x/10);
putchar(x%10+'0');
}
}
int n,f[210][210],a[210],sum[210],ans;
int main(){
n=read();
for(int i=1;i<=n;i++) a[i]=read(),a[i+n]=a[i];
for(int i=1;i<=n*2;i++) sum[i]=sum[i-1]+a[i];
memset(f,63,sizeof(f));
for(int i=1;i<=n*2;i++) f[i][i]=0;
for(int L=2;L<=n;L++){
for(int i=1;i<=n*2-L+1;i++){
int j=i+L-1;
for(int k=i;k<j;k++){
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);
}
}
}
ans=2e9;
for(int i=1;i<=n;i++){
ans=min(ans,f[i][i+n-1]);
}
write(ans);putchar('\n');
memset(f,0,sizeof(f));
for(int i=1;i<=n*2;i++) f[i][i]=0;
for(int L=2;L<=n;L++){
for(int i=1;i<=n*2-L+1;i++){
int j=i+L-1;
for(int k=i;k<j;k++){
f[i][j]=max(f[i][j],f[i][k]+f[k+1][j]+sum[j]-sum[i-1]);
}
}
}
ans=0;
for(int i=1;i<=n;i++){
ans=max(ans,f[i][i+n-1]);
}
write(ans);putchar('\n');
return 0;
}

10149. 「一本通 5.1 例 3」凸多边形的划分

题意

给定一个具有 $N$ 个顶点的凸多边形,将顶点从 $1$ 至 $N$ 标号,每个顶点的权值都是一个正整数。将这个凸多边形划分成 $N-2$ 个互不相交的三角形,试求这些三角形顶点的权值乘积和至少为多少。

思路

首先随便搞一个多边形:

然后给它顺时针每个顶点表上序号:

然后枚举$i、j$,要求:$i+1<j$,然后给$i、j$连一条线,分割出来另一个多边形:多边形23456

然后在$i、j$范围内枚举$k$,使得多边形23456又可以分割。

分割成如下图:

设$f[i][j]$表示把$i~j$的多边形切割成三角形后的权值乘积之和的最小值。

可得:
$$
f[i][j]=min{f[i][k]+f[k][j]+a[i]a[j]a[k]}(0<i<j<k\leq n)
$$
初始化:
$$
f[i][j]=inf(0<i\leq n,0<j\leq n)
$$

$$
f[i][i+1]=0(0<i<n)
$$

时间复杂度:$O(n^3)$

输出结果:$f[1][n]$

当然,这道题范围特别大:对于 $100\%$ 的数据,有 $N\le 50$,每个点权值小于 $10^9$。三个数相乘最高可达$10^{27}$,所以需要使用高精度。这里使用了C++大数类,转自代号4101

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
#include<bits/stdc++.h>
using namespace std;
const int maxn = 1000;
struct bign{
int d[maxn], len;

void clean() { while(len > 1 && !d[len-1]) len--; }

bign() { memset(d, 0, sizeof(d)); len = 1; }
bign(int num) { *this = num; }
bign(char* num) { *this = num; }
bign operator = (const char* num){
memset(d, 0, sizeof(d)); len = strlen(num);
for(int i = 0; i < len; i++) d[i] = num[len-1-i] - '0';
clean();
return *this;
}
bign operator = (int num){
char s[20]; sprintf(s, "%d", num);
*this = s;
return *this;
}

bign operator + (const bign& b){
bign c = *this; int i;
for (i = 0; i < b.len; i++){
c.d[i] += b.d[i];
if (c.d[i] > 9) c.d[i]%=10, c.d[i+1]++;
}
while (c.d[i] > 9) c.d[i++]%=10, c.d[i]++;
c.len = max(len, b.len);
if (c.d[i] && c.len <= i) c.len = i+1;
return c;
}
bign operator - (const bign& b){
bign c = *this; int i;
for (i = 0; i < b.len; i++){
c.d[i] -= b.d[i];
if (c.d[i] < 0) c.d[i]+=10, c.d[i+1]--;
}
while (c.d[i] < 0) c.d[i++]+=10, c.d[i]--;
c.clean();
return c;
}
bign operator * (const bign& b)const{
int i, j; bign c; c.len = len + b.len;
for(j = 0; j < b.len; j++) for(i = 0; i < len; i++)
c.d[i+j] += d[i] * b.d[j];
for(i = 0; i < c.len-1; i++)
c.d[i+1] += c.d[i]/10, c.d[i] %= 10;
c.clean();
return c;
}
bign operator / (const bign& b){
int i, j;
bign c = *this, a = 0;
for (i = len - 1; i >= 0; i--)
{
a = a*10 + d[i];
for (j = 0; j < 10; j++) if (a < b*(j+1)) break;
c.d[i] = j;
a = a - b*j;
}
c.clean();
return c;
}
bign operator % (const bign& b){
int i, j;
bign a = 0;
for (i = len - 1; i >= 0; i--)
{
a = a*10 + d[i];
for (j = 0; j < 10; j++) if (a < b*(j+1)) break;
a = a - b*j;
}
return a;
}
bign operator += (const bign& b){
*this = *this + b;
return *this;
}

bool operator <(const bign& b) const{
if(len != b.len) return len < b.len;
for(int i = len-1; i >= 0; i--)
if(d[i] != b.d[i]) return d[i] < b.d[i];
return false;
}
bool operator >(const bign& b) const{return b < *this;}
bool operator<=(const bign& b) const{return !(b < *this);}
bool operator>=(const bign& b) const{return !(*this < b);}
bool operator!=(const bign& b) const{return b < *this || *this < b;}
bool operator==(const bign& b) const{return !(b < *this) && !(b > *this);}

string str() const{
char s[maxn]={};
for(int i = 0; i < len; i++) s[len-1-i] = d[i]+'0';
return s;
}
};
istream& operator >> (istream& in, bign& x){
string s;
in >> s;
x = s.c_str();
return in;
}
ostream& operator << (ostream& out, const bign& x){

out << x.str();
return out;
}
#define ll bign
ll f[55][55],a[55];
int n;
int main(){
cin>>n;
for(int i=1;i<=n;i+=1) cin>>a[i];
memset(f,63,sizeof(f));
for(int i=1;i<=n;i++) f[i][i+1]=0;
for(int L=2;L<=n-1;L++){
for(int i=1;i<=n-L;i++){
int j=i+L;
for(int k=i+1;k<=j-1;k++){
f[i][j]=min(f[i][k]+f[k][j]+a[i]*a[j]*a[k],f[i][j]);
}
}
}
cout<<f[1][n];putchar('\n');
return 0;
}
//f[i][j]=min{f[i][k]+f[k][j]+a[i]*a[j]*a[k]}(0<i<k<j<=n)
//f[i][j]=inf
//f[i][i+1]=0;
//end:f[1][n]
//Time:O(n^3)

10154. 「一本通 5.2 例 2」选课

题意

有很多课程,但部分课程有先修课。学生不可能学完大学开设的所有课程,因此必须在入学时选定自己要学的课程。每个学生可选课程的总数是给定的。请找出一种选课方案使得你能得到的学分最多,并满足先修课优先的原则。假定课程间不存在时间上的冲突。

思路

瞎搞树形DP。

发现这是背包。

还是分组背包。(^▽^)

于是就愉快地解决了。

设$f[x][t]$表示以x为根节点的子树中选t门课能够获得的最高学分。(所以说是背包问题嘛)
$$Answer:f[0][m]$$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include<algorithm>
#include<bitset>
#include<complex>
#include<deque>
#include<exception>
#include<fstream>
#include<functional>
#include<iomanip>
#include<ios>
#include<iosfwd>
#include<iostream>
#include<istream>
#include<iterator>
#include<limits>
#include<list>
#include<locale>
#include<map>
#include<memory>
#include<new>
#include<numeric>
#include<ostream>
#include<queue>
#include<set>
#include<sstream>
#include<stack>
#include<stdexcept>
#include<streambuf>
#include<string>
#include<typeinfo>
#include<utility>
#include<valarray>
#include<vector>
#include<cstring>
#include<cmath>
#define ll long long
#define eps 1e-4
using namespace std;
inline int read(){
int ret=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
return ret*f;
}
inline void write(int zx){
if(zx<0){zx=-zx;putchar('-');}
if(zx<10) putchar(zx+'0');
else{
write(zx/10);
putchar(zx%10+'0');
}
}
int n,m,fir[310],nxt[310];
int s[310],w[310],f[310][310];
int DP(int x){
if(fir[x]==-1) return 0;
int sum=0;
for(int i=fir[x];i!=-1;i=nxt[i]){
int tmp=DP(i);
sum+=tmp+1;
for(int j=sum;j>=0;j--)
for(int k=0;k<=tmp;k++)
if(j-k-1>=0) f[x][j]=max(f[x][j],f[x][j-k-1]+f[i][k]);
}
return sum;
}
int main(){
n=read();m=read();
memset(fir,-1,sizeof(fir));
for(int i=1;i<=n;i++){
s[i]=read();w[i]=read();
nxt[i]=fir[s[i]];
fir[s[i]]=i;
}
for(int i=1;i<=n;i++) f[i][0]=w[i];
f[0][0]=0;
DP(0);
write(f[0][m]);
putchar('\n');
return 0;
}

10155. 「一本通 5.2 例 3」数字转换

题意

如果一个数 $x$ 的约数和 $y$ (不包括他本身)比他本身小,那么 $x$ 可以变成 $y$,$y$ 也可以变成 $x$。例如 $4$ 可以变为 $3$,$1$ 可以变为 $7$。限定所有数字变换在不超过 $n$ 的正整数范围内进行,求不断进行数字变换且不出现重复数字的最多变换步数。

思路

求树的最长链

设$d1[i]$为以$i$为根的子树中,i到叶子节点距离最大值

设$d2[i]$为以$i$为根的子树中,i的叶子节点距离次大值(除了最大值所在的子树)

若j为i的儿子,那么:

  • 若$d1[j]+dis[i][j]>d1[i]$,则$d2[i]=d1[i];d1[i]=d2[j]=dis[i][j];$
  • 否则,若$d1[j]+dis[i][j]>d2[i]$,则$d2[i]=d1[j]+dis[i][j]; $

最后扫描所有节点,最长链=max{d1[i]+d2[i]}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include<algorithm>
#include<bitset>
#include<complex>
#include<deque>
#include<exception>
#include<fstream>
#include<functional>
#include<iomanip>
#include<ios>
#include<iosfwd>
#include<iostream>
#include<istream>
#include<iterator>
#include<limits>
#include<list>
#include<locale>
#include<map>
#include<memory>
#include<new>
#include<numeric>
#include<ostream>
#include<queue>
#include<set>
#include<sstream>
#include<stack>
#include<stdexcept>
#include<streambuf>
#include<string>
#include<typeinfo>
#include<utility>
#include<valarray>
#include<vector>
#include<cstring>
#include<cmath>
#define ll long long
#define eps 1e-4
using namespace std;
//priority_queue<int,vector<int>,greater<int> > q1;
//priority_queue<int> q2;
//set<int> s;
//list<int> l;
//map<int> mp;
inline int read(){
int ret=0,f=1;char ch=getchar();
while (ch<'0'||ch>'9') {if (ch=='-') f=-f;ch=getchar();}
while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
return ret*f;
}
inline void write(int zx){
if(zx<0){zx=-zx;putchar('-');}
if(zx<10) putchar(zx+'0');
else{
write(zx/10);
putchar(zx%10+'0');
}
}
int sum[500001],n,d1[500001],d2[500001],ans;
void Pri(){
for(int i=1;i<=n;i++){
for(int j=2;j<=n/i;j++){
if(i*j>n) break;
sum[i*j]+=i;
}
}
}
void dp(){
for(int i=n;i>=1;i--){
if(sum[i]<i){
if(d1[i]+1>d1[sum[i]]){
d2[sum[i]]=d1[sum[i]];
d1[sum[i]]=d1[i]+1;
}else if(d1[i]+1>d2[sum[i]]) d2[sum[i]]=d1[i]+1;
}
}
}
int main(){
n=read();
Pri();
dp();
for(int i=1;i<=n;i++) ans=max(ans,d1[i]+d2[i]);
write(ans);putchar('\n');
return 0;
}

10153. 「一本通 5.2 例 1」二叉苹果树

题意

有一棵二叉苹果树,如果数字有分叉,一定是分两叉,即没有只有一个儿子的节点。这棵树共 $N$ 个节点,标号 $1$ 至 $N$,树根编号一定为 $1$。

我们用一根树枝两端连接的节点编号描述一根树枝的位置。一棵有四根树枝的苹果树,因为树枝太多了,需要剪枝。但是一些树枝上长有苹果,给定需要保留的树枝数量,求最多能留住多少苹果。
tree.png

思路

设$f[i][j]$表示以i为根节点,保留j个节点的最大苹果数量

$$f[i][j]=max{f[l[i]][k]+f[r[i]][j-k-1]+a[i]}(0<=k<=j-1)$$

$$f[i][j]=0(0<i<=n,0<=j<=Q+1)$$

$$f[i][j]=ai$$

$$Answer:f[1][Q+1]$$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline ll read(){
char ch=getchar();ll res=0,f=1;
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') res=res*10+ch-'0',ch=getchar();
return res*f;
}
inline void write(ll x){
if(x<0) putchar('-'),x=-x;
if(x<10) putchar(x+'0');
else{
write(x/10);
putchar(x%10+'0');
}
}
ll n,Q,f[110][110],a[110],l[110],r[110],mp[110][110];
void MakeTree(int x){
for(int i=1;i<=n;i++){
if(mp[x][i]!=-1){
l[x]=i;a[i]=mp[x][i];
mp[x][i]=mp[i][x]=-1;
MakeTree(i);
break;
}
}//Make Left Son
for(int i=1;i<=n;i++){
if(mp[x][i]!=-1){
r[x]=i;a[i]=mp[x][i];
mp[x][i]=mp[i][x]=-1;
MakeTree(i);
break;
}
}//Make Right Son
}
int DP(int x,int j){

if(j==0){f[x][j]=0;return 0;}
if((!l[x])&&(!r[x])){f[x][j]=a[x];return a[x];}
if(f[x][j]>0) return f[x][j];
for(int k=0;k<j;k++) f[x][j]=max(f[x][j],DP(l[x],k)+DP(r[x],j-k-1)+a[x]);
return f[x][j];
}
int main(){
n=read();Q=read();Q++;
memset(mp,-1,sizeof(mp));
for(int i=1;i<=n-1;i++){
int x=read(),y=read(),z=read();
mp[y][x]=mp[x][y]=z;
}
MakeTree(1);
write(DP(1,Q));putchar('\n');
/*
cout<<"-----------------------------------"<<endl;
for(int i=1;i<=n;i++){
cout<<"Node "<<i<<":\n";
cout<<"Left Son:"<<l[i]<<" Right Son:"<<r[i]<<endl;
cout<<"Val:"<<a[i]<<endl;
cout<<"DP :\n";
for(int j=0;j<=Q;j++){
cout<<"Has "<<j<<":"<<f[i][j]<<endl;
}
cout<<endl;
}
*/
return 0;
}
//设f[i][j]表示以i为根节点,保留j个节点的最大苹果数量
//f[i][j]=max{f[l[i]][k]+f[r[i]][j-k-1]+a[i]}(0<=k<=j-1)
//f[i][j]=0(0<i<=n,0<=j<=Q+1)
//f[i][j]=a[i](j!=0&&l[i]==0&&r[i]==0)
//Answer:f[1][Q+1]
/*
Sample Input:
5 2
1 3 1
1 4 10
2 3 20
3 5 20
Sample Output:
21
*/

2597. 「NOIP2011」选择客栈

题意

有$n​$个客栈,每个客栈都配有咖啡馆。有两名旅客想住在同色调的客栈中,又想在两客栈之间的咖啡馆中小聚,咖啡馆的价钱不能高于$p​$。

对于 $100\%$ 的数据,有 $2\leq n\leq2\times 10^6$,$0<k\leq10^4$ ,$0\leq p\leq100$,$0\leq$ 最低消费 $\leq100$ 。

思路

$n$的范围那么大,$k$的范围那么小。那么暴力吧。

设$h_i$表示目前颜色$i$的客栈数量,$las_i$表示最近的颜色为$i$的客栈的编号。

然后$O(n)$扫一遍就好了啊。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include<algorithm>
#include<bitset>
#include<complex>
#include<deque>
#include<exception>
#include<fstream>
#include<functional>
#include<iomanip>
#include<ios>
#include<iosfwd>
#include<iostream>
#include<istream>
#include<iterator>
#include<limits>
#include<list>
#include<locale>
#include<map>
#include<memory>
#include<new>
#include<numeric>
#include<ostream>
#include<queue>
#include<set>
#include<sstream>
#include<stack>
#include<stdexcept>
#include<streambuf>
#include<string>
#include<typeinfo>
#include<utility>
#include<valarray>
#include<vector>
#include<cstring>
#include<cmath>
#define ll long long
const int N=2e5+5,M=1e6;
using namespace std;
inline ll read(){
char ch=getchar();ll res=0,f=1;
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') res=res*10+ch-'0',ch=getchar();
return res*f;
}
inline void write(ll zx){
if(zx<0) zx=-zx,putchar('-');
if(zx<10) putchar(zx+'0');
else{
write(zx/10);
putchar(zx%10+'0');
}
}
ll n,m,color,price,now,las[N],h[N],sum[N],ans,p;
int main(){
n=read();m=read();p=read();
for(ll i=1;i<=n;i++){
color=read(),price=read();//读入
if(price<=p) now=i;//价钱要小于或等于p
if(now>=las[color]) sum[color]=h[color];//如果比上一个颜色相同的近,直接加上方案数
ans+=sum[color];//更新ANS
h[color]++;las[color]=i;//更新LAS和H
}
write(ans);putchar('\n');//输出
return 0;
}

10121. 「一本通 4.2 例 3」与众不同

题意

定义完美序列:一段连续的序列满足序列中的数互不相同。

想知道区间 $[L,R]$ 之间最长的完美序列长度。

思路

设$las[x]$表示盈利$x$最近出现位置。

设$st[i]$表示以第$i$个数结尾的最长完美序列的起始位置。
$$
st[i]=max(st[i-1],las[a[i]+1])
$$
设$f[i]$表示以第$i$个数结尾的最长完美序列的长度
$$
f[i]=i-st[i]+1
$$
由$st$的递推式可知,$st$的值是一个非递减的序列。

对于一个询问区间$[l_i,r_i]$,该区间内的$st$值可能会有两种情况:

  • 左边一部分的$st$值不在区间内,即$<l_i$
  • 右边一部分的$st​$值不在区间内,即$\ge l_i​$

由于$st$的值具有单调性,所以这个边界可以通过二分得到。设求出的边界为$mid$_i,可得:
$$
st[l_i…mid_i-1]<l_i
$$

$$
st[mid_i…r_i]\ge l_i
$$

那么整个区间$[l_i,r_i]$的最长完美序列的长度可以分两部分来求。

  • 左边:很显然为$mid_i-l_i$

  • 右边:$MAX(m_i…r_i)$

所以右边的长度要使用ST表,即RMQ来求。

整个问题的时间复杂度:
$$
O((M+N) \times logN)
$$

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include<algorithm>
#include<bitset>
#include<complex>
#include<deque>
#include<exception>
#include<fstream>
#include<functional>
#include<iomanip>
#include<ios>
#include<iosfwd>
#include<iostream>
#include<istream>
#include<iterator>
#include<limits>
#include<list>
#include<locale>
#include<map>
#include<memory>
#include<new>
#include<numeric>
#include<ostream>
#include<queue>
#include<set>
#include<sstream>
#include<stack>
#include<stdexcept>
#include<streambuf>
#include<string>
#include<typeinfo>
#include<utility>
#include<valarray>
#include<vector>
#include<cstring>
#include<cmath>
#define ll long long
const int N=2e5+5,M=1e6;
using namespace std;
inline ll read(){
char ch=getchar();ll res=0,f=1;
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') res=res*10+ch-'0',ch=getchar();
return res*f;
}
inline void write(ll zx){
if(zx<0) zx=-zx,putchar('-');
if(zx<10) putchar(zx+'0');
else{
write(zx/10);
putchar(zx%10+'0');
}
}
ll n,m,f[N][20],st[N],las[M<<1];
void ST(){
for(ll j=1;(1<<j)<=n;j++){
for(ll i=1;i+(1<<j)-1<=n;i++){
f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
}
}
}
ll RMQ(ll l,ll r){
ll k=0;
while((1<<(k+1))<=r-l+1) k++;
return max(f[l][k],f[r-(1<<k)+1][k]);
}
ll find(ll l,ll r){
if(st[l]==l) return l;
if(st[r]<l) return r+1;
int L=l,R=r;
while(L<=R){
int m=L+R>>1;
if(st[m]<l) L=m+1;
else R=m-1;
}
return L;
}
int main(){
n=read();m=read();
for(ll i=1;i<=n;i++){
int x=read();
st[i]=max(st[i-1],las[x+M]+1);
f[i][0]=i-st[i]+1;
las[x+M]=i;
}
ST();
for(ll i=1;i<=m;i++){
ll L,R;
L=read();R=read();L++;R++;
ll mid=find(L,R),ans=0,tmp;
if(mid>L) ans=mid-L;
if(mid<=R){
tmp=RMQ(mid,R);
ans=max(ans,tmp);
}
write(ans);putchar('\n');
}
return 0;
}
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×