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;
}

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;
}

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
*/

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)

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;
}

10129. 「一本通 4.3 练习 3」维护序列

题意

有长为 $n$ 的数列,不妨设为 $a_1,a_2,\cdots ,a_n$。有如下三种操作形式:

  • 把数列中的一段数全部乘一个值;
  • 把数列中的一段数全部加一个值;
  • 询问数列中的一段数的和,由于答案可能很大,你只需输出这个数模 $P$ 的值。

思路

线段树瞎搞。乘法的优先级要高一些。

注意MOD

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
#include<bits/stdc++.h>
#define N 10000000+10
#define ll long long
using namespace std;
ll n,m,a[N],add[N*4+10],mod,p,mul[N*4+10];
ll tree[N*4+10];
void pushup(ll id){
tree[id]=tree[id<<1]+tree[(id<<1)|1];
tree[id]%=mod;
}
void push_down(ll cur,ll l,ll r,ll mid){
if(mul[cur]==1&&add[cur]==0) return;
mul[cur<<1]=mul[cur<<1]*mul[cur]%mod;
add[cur<<1]=(add[cur<<1]*mul[cur]%mod+add[cur])%mod;
tree[cur<<1]=(tree[cur<<1]*mul[cur]%mod+add[cur]*(ll)(mid-l+1)%mod)%mod;
mul[cur<<1|1]=mul[cur<<1|1]*mul[cur]%mod;
add[cur<<1|1]=(add[cur<<1|1]*mul[cur]%mod+add[cur])%mod;
tree[cur<<1|1]=(tree[cur<<1|1]*mul[cur]%mod+add[cur]*(ll)(r-mid)%mod)%mod;
mul[cur]=1;
add[cur]=0;
return;
}
void update(ll id,ll l,ll r,ll ql,ll qr,ll val){
if(ql<=l&&qr>=r){
add[id]+=val;add[id]%=mod;
tree[id]=(tree[id]+(ll)(r-l+1)*val%mod)%mod;
return ;
}
push_down(id,l,r,l+r>>1);
ll mid=l+((r-l)>>1);
if(ql<=mid) update(id<<1,l,mid,ql,qr,val);
if(qr>=mid+1) update((id<<1)|1,mid+1,r,ql,qr,val);
pushup(id);
}
void updatemul(ll cur,ll L,ll R,ll l,ll r,ll x){
if(L>=l&&R<=r){
mul[cur]=mul[cur]*(ll)x%mod;
add[cur]=add[cur]*(ll)x%mod;
tree[cur]=tree[cur]*(ll)x%mod;
return;
}
ll mid=L+R>>1;
push_down(cur,L,R,mid);
if(l<=mid) updatemul(cur<<1,L,mid,l,r,x);
if(r>mid) updatemul(cur<<1|1,mid+1,R,l,r,x);
pushup(cur);
}
ll query(ll id,ll L,ll R,ll l,ll r){
if(L>=l&&R<=r) return tree[id]%mod;
ll mid=L+R>>1;
ll ans=0;
push_down(id,L,R,mid);
if(l<=mid) ans=(ans+query(id<<1,L,mid,l,r))%mod;
if(r>mid) ans=(ans+query(id<<1|1,mid+1,R,l,r))%mod;
pushup(id);
return ans;
}
void build(ll L,ll R,ll x,ll y,ll cur){
mul[cur]=1;add[cur]=0;tree[cur]+=y;
if(L==R) return;
ll mid=L+R>>1;
if(x>mid) build(mid+1,R,x,y,cur<<1|1);
else build(L,mid,x,y,cur<<1);
pushup(cur);
}
int main(){
scanf("%lld%lld",&n,&mod);
for(ll i=1;i<=n;i++) scanf("%lld",&a[i]),build(1,n,i,a[i]%mod,1);
scanf("%lld",&m);
for(ll i=1;i<=m;i++){
ll type;
scanf("%lld",&type);
if(type==1){
ll x,y,k;
scanf("%lld%lld%lld",&x,&y,&k);
updatemul(1,1,n,x,y,k);
}
if(type==2){
ll x,y,k;
scanf("%lld%lld%lld",&x,&y,&k);
update(1,1,n,x,y,k);
}
if(type==3){
ll x,y;
scanf("%lld%lld",&x,&y);
ll temp=query(1,1,n,x,y);
printf("%lld\n",temp%mod);
}
}
return 0;
}

10128. 「一本通 4.3 练习 2」花神游历各国

题意

每一次旅行中,花神会选择一条旅游路线,它在那一串国家中是连续的一段,这次旅行带来的开心值是这些国家的喜欢度的总和,当然花神对这些国家的喜欢程序并不是恒定的,有时会突然对某些国家产生反感,使他对这些国家的喜欢度 $\delta$ 变为 $\sqrt \delta$(可能是花神虐爆了那些国家的 OI,从而感到乏味)。

现在给出花神每次的旅行路线,以及开心度的变化,请求出花神每次旅行的开心值。

思路

为了使时间复杂度降低,我们可以发现:$\sqrt 1=1$所以,最大数字$10^9$操作较少次数便能到$1$。所以在操作前判断一下,如果区间内都是$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
#include<bits/stdc++.h>
#define ll long long
#define MAXNUM 1000000*4+10
using namespace std;
struct node{
ll l,r,lef,rig;
ll val,Max;
}tree[210000];
ll a[110000],len,n,m;
void build(ll l,ll r){
ll root=++len;
tree[root].l=l;tree[root].r=r;tree[root].lef=tree[root].rig=-1;
if(l==r)tree[root].val=tree[root].Max=a[l];
else{
ll mid=(l+r)/2;
ll lef=tree[root].lef=len+1;build(l,mid);
ll rig=tree[root].rig=len+1;build(mid+1,r);
tree[root].val=tree[lef].val+tree[rig].val;
tree[root].Max=max(tree[lef].Max,tree[rig].Max);
}
}
void update(ll root,ll l,ll r){
if(tree[root].Max<2) return ;
if(tree[root].l==tree[root].r){tree[root].val=sqrt(tree[root].val);tree[root].Max=tree[root].val;return ;}
ll lef=tree[root].lef,rig=tree[root].rig,mid=(tree[root].l+tree[root].r)/2;
if(r<=mid) update(lef,l,r);
else if(mid<l) update(rig,l,r);
else update(lef,l,mid),update(rig,mid+1,r);
tree[root].val=tree[lef].val+tree[rig].val;
tree[root].Max=max(tree[lef].Max,tree[rig].Max);
}
ll query(ll root,ll l,ll r){
if(tree[root].l>=l&&tree[root].r<=r) return tree[root].val;
ll lef=tree[root].lef,rig=tree[root].rig,mid=(tree[root].l+tree[root].r)/2;
if(r<=mid) return query(lef,l,r);
else if(mid<l) return query(rig,l,r);
else return query(lef,l,mid)+query(rig,mid+1,r);
}
int main(){
scanf("%lld",&n);
for(ll i=1;i<=n;i++) scanf("%lld",&a[i]);
build(1,n);
scanf("%lld",&m);
while(m--){
char s;cin>>s;
if(s=='2'){
ll x,y;scanf("%lld%lld",&x,&y);
update(1,x,y);
}else{
ll x,y;scanf("%lld%lld",&x,&y);
printf("%lld\n",query(1,x,y));
}
}
return 0;
}

10127. 「一本通 4.3 练习 1」最大数

题意

给定一个正整数数列 $a_1, a_2, a_3, \cdots , a_n$,每一个数都在 $0\sim p – 1$ 之间。可以对这列数进行两种操作:

  • 添加操作:向序列后添加一个数,序列长度变成 $n + 1$;

  • 询问操作:询问这个序列中最后 $L$ 个数中最大的数是多少。

程序运行的最开始,整数序列为空。写一个程序,读入操作的序列,并输出询问操作的答案。

思路

模板题呀。

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
#include<bits/stdc++.h>
#define ll long long
#define MAXNUM 200000*4+10
using namespace std;
struct node{
ll l,r,lef,rig,c;
}tree[MAXNUM];
ll n,m,q,len,las;
void build(ll l,ll r){
ll root=++len;
tree[root].l=l;tree[root].r=r;tree[root].lef=tree[root].rig=-1;
if(l<r){
ll mid=(l+r)/2;
tree[root].lef=len+1;build(l,mid);
tree[root].rig=len+1;build(mid+1,r);
}
}
void update(ll root,ll x,ll k){
if(tree[root].l==tree[root].r){tree[root].c=k;return ;}
ll lef=tree[root].lef,rig=tree[root].rig;
ll mid=(tree[root].l+tree[root].r)/2;
if(x<=mid) update(lef,x,k);
else update(rig,x,k);
tree[root].c=max(tree[lef].c,tree[rig].c);
}
ll query(ll root,ll l,ll r){
if(tree[root].l>=l&&tree[root].r<=r) return tree[root].c;
ll lef=tree[root].lef,rig=tree[root].rig;
ll mid=(tree[root].l+tree[root].r)/2;
if(r<=mid) return query(lef,l,r);
else if(mid<l) return query(rig,l,r);
else return max(query(lef,l,mid),query(rig,mid+1,r));
}
int main(){
scanf("%lld%lld",&m,&q);
build(1,m);
while(m--){
char s;cin>>s;
if(s=='A'){
ll x;scanf("%lld",&x);n++;
update(1,n,(x+las)%q);
}else{
ll x;scanf("%lld",&x);
las=query(1,n-x+1,n);
printf("%lld\n",las);
}
}
}

10126. 「一本通 4.3 例 2」A Simple Problem with Integers

题意

这是一道模板题。\r\n\r\n给定数列 $a[1], a[2], \dots, a[n]$,你需要依次进行 $q$ 个操作,操作有两类:

  • 1 l r x:给定 $l,r,x$,对于所有 $i\in[l,r]$,将 $a[i]$ 加上 $x$(换言之,将 $a[l], a[l+1], \dots, a[r]$ 分别加上 $x$);
    • 2 l r:给定 $l,r$,求 $\sum_{i=l}^ra[i]$ 的值(换言之,求 $a[l]+a[l+1]+\dots+a[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
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
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n,m,a[1000010],add[1000000*4+10];
ll tree[1000000*4+10];
void pushup(ll id){
tree[id]=tree[id<<1]+tree[(id<<1)|1];
}
void build(ll id,ll l,ll r){
if(l==r) tree[id]=a[l];
else{
ll mid=l+((r-l)>>1);
build(id<<1,l,mid);
build((id<<1)|1,mid+1,r);
pushup(id);
}
}
void pushdown(ll id,ll l,ll r){
if(add[id]!=0){
add[id<<1]+=add[id];
add[(id<<1)|1]+=add[id];
ll mid=l+((r-l)>>1);
tree[id<<1]+=add[id]*(mid-l+1);
tree[(id<<1)|1]+=add[id]*(r-mid);
add[id]=0;
}
}
void update(ll id,ll l,ll r,ll ql,ll qr,ll val){
if(ql<=l&&qr>=r){
add[id]+=val;
tree[id]+=val*(r-l+1);
return ;
}
pushdown(id,l,r);
ll mid=l+((r-l)>>1);
if(ql<=mid) update(id<<1,l,mid,ql,qr,val);
if(qr>=mid+1) update((id<<1)|1,mid+1,r,ql,qr,val);
pushup(id);
}
ll query(ll id,ll l,ll r,ll ql,ll qr){
if(ql<=l&&qr>=r) return tree[id];
pushdown(id,l,r);
ll mid=l+((r-l)>>1);
ll ans=0;
if(ql<=mid) ans+=query(id<<1,l,mid,ql,qr);
if(qr>=mid+1) ans+=query((id<<1)|1,mid+1,r,ql,qr);
return ans;
}
int main(){
scanf("%lld%lld",&n,&m);
for(ll i=1;i<=n;i++) scanf("%lld",&a[i]);
build(1,1,n);
for(ll i=1;i<=m;i++){
ll type;
scanf("%lld",&type);
if(type==1){
ll x,y,k;
scanf("%lld%lld%lld",&x,&y,&k);
update(1,1,n,x,y,k);
}
if(type==2){
ll x,y;
scanf("%lld%lld",&x,&y);
ll temp=query(1,1,n,x,y);
printf("%lld\n",temp);
}
}
return 0;
}
Your browser is out-of-date!

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

×