【状压DP】斯坦纳树.md

说明 - 2022-05-05

本篇博客为本人原创, 原发布于CSDN, 在搭建个人博客后使用爬虫批量爬取并挂到个人博客, 出于一些技术原因博客未能完全还原到初始版本(而且我懒得修改), 在观看体验上会有一些瑕疵 ,若有需求会发布重制版总结性新博客。发布时间统一定为1111年11月11日。钦此。

前言

斯坦纳树常用于解决类似这样的最小代价联通问题:https://www.luogu.com.cn/problem/P4294

思路

tip1:
对于一个结点, 通过状态压缩的思想,用一个整数sta表示这个结点与所有目标节点的联通状态,
在sta的二进制位中,若第x位为1,表示该结点与第x个目标结点联通,反之则为不连通
用dp[i][sta]表示结点i联通sta状态的最小代价

状态转移part1
若结点i与状态sta1和sta2都联通,且sta1与sta2无重合结点(即sta1, sta2
是对sta1+sta2的一个划分)。令P表示把点i联通所需的代价, 显然有:
dp[i][sta1 + sta2] = MIN(dp[i][sta1 + sta2], dp[i][sta1] + dp[i][sta2] - P[i])
由于dp[i][sta1] 和dp[i][sta2]都包含了联通点i的代价P[i],所以要减去一个P[i]。
dp[i][sta1]中的[i]只是一种结点的表示方法.
状态转移part2
按照边进行松弛
对于一个结点i, dp[i][sta] = MIN(dp[i][sta], dp[i周围一步可达的结点][sta] + P[i])
像极了最短路, 直接spfa

题目

P4294 [WC2008]游览计划

https://www.luogu.com.cn/problem/P4294

由于你还需要输出所有位置的摆放 所以加入了pre数组记录某状态的前置状态。得出最小答案后通过dfs找出斯坦纳树的所有结点。
(对于状态转移part1, 只需要记录一个前置状态,另一个前置状态通过减法求出)

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
#include <bits/stdc++.h>
const int N = 13, LIM = 1028, INF = 0x3f3f3f3f;
int dx[] = {0, 0, 1, -1};
int dy[] = {1, -1, 0, 0};
int board[N][N], dp[N][N][LIM];
int n, m, idx, mSta, rex, rey;
bool vis[N][N], judge;
struct PRE{
int x, y, s;
}pre[N][N][LIM];
void dfs(int x, int y, int st){
vis[x][y] = true;
PRE tmp = pre[x][y][st];
if(!tmp.x && !tmp.y) return ;
dfs(tmp.x, tmp.y, tmp.s);
if(tmp.x == x && tmp.y == y) dfs(x, y, st - tmp.s);
}
inline bool cango(int x, int y) {return x<=n && y<=m && x>=1 && y>=1; }
int main(){
memset(dp, 63, sizeof(dp));
scanf("%d%d", &n, &m);
for(int i=1; i<=n; i++) for(int j=1; j<=m; j++) {
scanf("%d", &board[i][j]);
if(!board[i][j]) dp[i][j][1 << idx ++] = 0;
}
mSta = 1 << idx;
for(int sta=1; sta<mSta; sta++){
std::queue<std::pair<int ,int> > q;
for(int i=1; i<=n; i++) for(int j=1; j<=m; j++){
for(int sub=sta; sub; sub=(sub-1)&sta){
if(dp[i][j][sub] + dp[i][j][sta - sub] - board[i][j] < dp[i][j][sta]){
dp[i][j][sta] = dp[i][j][sub] + dp[i][j][sta - sub] - board[i][j];
pre[i][j][sta] = (PRE){i, j, sub};
}
}
if(dp[i][j][sta] < INF) q.push(std::make_pair(i, j)), vis[i][j] = true;
}
while(!q.empty()){
std::pair<int , int> x = q.front(); q.pop(); vis[x.first][x.second] = false;
for(int i=0; i<4; i++){
int xx = x.first + dx[i], yy = x.second + dy[i];
if(cango(xx, yy)) if(dp[x.first][x.second][sta] + board[xx][yy] < dp[xx][yy][sta]){
dp[xx][yy][sta] = dp[x.first][x.second][sta] + board[xx][yy];
pre[xx][yy][sta] = (PRE){x.first, x.second, sta};
if(!vis[xx][yy]) vis[xx][yy] = true, q.push(std::make_pair(xx, yy));
}
}
}
}
for(int i=1; i<=n && !judge; i++) for(int j=1; j<=m && !judge; j++) if(!board[i][j]) rex = i, rey = j, judge = true;
printf("%d\n", dp[rex][rey][mSta - 1]);
dfs(rex, rey, mSta - 1);
for(int i=1; i<=n; i++) {
for(int j=1; j<=m; j++){
if(board[i][j] == 0) putchar('x');
else if(vis[i][j] == 1) putchar('o');
else putchar('_');
}
putchar('\n');
}
return 0;
}

【状压dp练习】洛谷P1896 [SCOI2005]互不侵犯 P2704 [NOI2001]炮兵阵地.md

说明 - 2022-05-05

本篇博客为本人原创, 原发布于CSDN, 在搭建个人博客后使用爬虫批量爬取并挂到个人博客, 出于一些技术原因博客未能完全还原到初始版本(而且我懒得修改), 在观看体验上会有一些瑕疵 ,若有需求会发布重制版总结性新博客。发布时间统一定为1111年11月11日。钦此。

可以把一种只有两种状态的一维的数据组用一个整数表示,其二进制的值可以表示每一个位置的状态。如:1010 表示第1、3个位置有,第2、4个位置没有。

洛谷 P1896 [SCOI2005]互不侵犯

init函数枚举所有的m位不存在两个相邻的1的整数,将整数的值记录在status数组中,将此数含有1的数量记录在cnt数组中
dp[i][s][n] 表示进行道第i行时,在第s个状态,使用了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
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <queue>
#include <map>
#include <set>
#include <stack>
#include <vector>
typedef std::pair<int ,int> PP;
typedef long long LL;
//const int N = 1e5+5;
LL dp[10][1025][100];
int status[1025],cnt[1025];
int n,k;
int idx;
LL ans;

void init(int v,int b,int total)
{
if(b >=n){
status[idx] = v;
cnt[idx++] = total;
return ;
}
init(v,b+1,total);
init(v+(1<<b),b+2,total+1);
}

int main()
{
scanf("%d%d",&n,&k);
init(0,0,0);
for(int i=0; i<idx; i++){
dp[0][i][cnt[i]] += 1;
}
for(int r=1; r<n; r++){
for(int i=0; i<idx; i++){
for(int j=0; j<idx; j++){
if(status[i]&status[j] || (status[i]<<1)&status[j] || (status[i]>>1)&status[j]) continue;
for(int x=cnt[i]; x<=k; x++){
dp[r][i][x] += dp[r-1][j][x-cnt[i]];
}
}
}
}
ans = 0;
for(int i=0; i<idx; i++) ans += dp[n-1][i][k];
printf("%lld",ans);
return 0;
}

P2704 [NOI2001]炮兵阵地

抄完了互不侵犯之后拿这道题练了练手,然后失败。

写一个错误的思路
二维数组 dp[i][s]表示进行到第i行时,当状态为s,可以摆放的最多的炮兵的数量。
初始化前两行后,当一种状态s可以摆放,则dp[i][s] = max(自己,dp[i-2][上两行的状态]+上一行状态的总人数+这一行状态的总人数)
即用上到二行为止的最大人数+上一行某状态的人数+这一行s状态的人数来更新这一行的s状态
这种思路存在一个缺陷,即只记录了单行某个状态的最大人数。
沿用这个思路,当进行到第n行时,我们用“截止到n-2行k状态的最大人数”+“n-1行取状态j的人数”+“第n行状态s的人数”来更新第n行。 问题在于
:我们无法保证当“截止到n-2行k状态的最大人数”取得时,用状态j当作n-2行的下一行时合法的,因为第n-1行的状态受到n-2行和n-3行限制,有可能当“截止到n-2行k状态的最大人数”取得时,j状态是不能与n-3行的状态共存的。然而在这种思路中,我们下意识地认为这种错误时合法的,所以在多数情况下无法得到正确答案。
更正思路:
开三维数组dp[i][s][last],表示进行到第i行时,当状态为s,并且上一行状态为last时,可以摆放的最多的炮兵的数量。
错误代码: (竟然还能得20分)

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
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <queue>
#include <map>
#include <set>
#include <stack>
#include <vector>
//typedef std::pair<int ,int> PP;
typedef long long LL;
const int N = 105,M = 12,ER9 = 1030;
int rows[N],status[ER9],va[ER9];
LL dp[N][ER9];
char ss[12];
int n,m;
int idx;

int id[N],mp[N][ER9];

int toStatus()
{
int re = 0;
for(int i=0; i<m; i++){
re += (ss[i] == 'H') ? (1<<i) : 0;
}
return re;
}

void init(int val,int pos,int cnt1)
{
if(pos >= m){
status[idx] = val;
va[idx++] = cnt1;
return ;
}
init(val,pos+1,cnt1);
init(val+(1<<pos),pos+3,cnt1+1);
}

int main()
{
scanf("%d%d",&n,&m);
for(int i=0; i<n; i++){
scanf("%s",ss);
rows[i] = toStatus();
}
init(0,0,0);
for(int i=0; i<idx; i++){
if(status[i]&rows[0]) continue;
mp[0][id[0]] = i;
dp[0][id[0]] = va[i];
id[0] += 1;
}
for(int i=0; i<idx; i++){
if(status[i]&rows[1]) continue;
mp[1][id[1]] = i;
for(int j=0; j<id[0]; j++){
if(status[i]&status[mp[0][j]]) continue;
dp[1][id[1]] = std::max(dp[1][id[1]],va[i]+dp[0][j]);
}
id[1] += 1;
}
for(int r=2; r<n; r++){
for(int i=0; i<idx; i++){
if(status[i]&rows[r]) continue;
mp[r][id[r]] = i;
for(int j=0; j<id[r-1]; j++){
for(int k=0; k<id[r-2]; k++){
if(status[i]&status[mp[r-1][j]] || status[i]&status[mp[r-2][k]] || status[mp[r-2][k]]&status[mp[r-1][j]]) continue;
dp[r][id[r]] = std::max(dp[r][id[r]],va[i]+va[mp[r-1][j]]+dp[r-2][k]);
}
}
id[r] += 1;
}
}
LL ans = 0;
for(int i=0; i<id[n-1]; i++){
ans = std::max(ans,dp[n-1][i]);
}
printf("%lld",ans);

return 0;
}

AC代码:
看很多题解都说要用到三行的滚动数组,否则会MLE。
但是这个没有用,直接开了100行,可能是由于动态加状态节省了空间吧。 破案了,题目改过,不限制空间了

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
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <queue>
#include <map>
#include <set>
#include <stack>
#include <vector>
//typedef std::pair<int ,int> PP;
typedef long long LL;
const int N = 105,M = 12,E10 = 1024;
int rows[N],status[E10],va[E10],dp[N][E10][E10];
//rows:把地图的每一行转化为数字,H为1,P为0,储存在rows数组
//status记录所有状态的对应的数字
//va记录所有状态对应的人数
//dp[i][j][k]表示第i行状态为j,且i-1行状态为k时,可以放置的最大炮兵数
char ss[12];
int n,m;
int idx,ans;
//idx记录status数组的大小
int id[N],mp[N][E10];
//id[i]数组记录第i行可行的总状态数
//mp[i][j]记录第i行第j个状态在status数组中对应的编号

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
int toStatus()
{
int re = 0;
for(int i=0; i<m; i++){
re += (ss[i] == 'H') ? (1<<i) : 0;
}
return re;
}

void init(int val,int pos,int cnt1)
{
if(pos >= m){
status[idx] = val;
va[idx++] = cnt1;
return ;
}
init(val,pos+1,cnt1);
init(val+(1<<pos),pos+3,cnt1+1);
}

int main()
{
scanf("%d%d",&n,&m);
for(int i=0; i<n; i++){
scanf("%s",ss);
rows[i] = toStatus();
}
init(0,0,0);
for(int i=0; i<idx; i++){
if(status[i]&rows[0]) continue;
mp[0][id[0]] = i;
dp[0][id[0]][0] = va[i];
id[0] += 1;
}
for(int i=0; i<idx; i++){
if(status[i]&rows[1]) continue;
mp[1][id[1]] = i;
for(int j=0; j<id[0]; j++){
if(status[i]&status[mp[0][j]]) continue;
dp[1][id[1]][j] = va[i] + dp[0][j][0];
}
id[1] += 1;
}
for(int r=2; r<n; r++){
for(int i=0; i<idx; i++){
if(status[i]&rows[r]) continue;
mp[r][id[r]] = i;
for(int j=0; j<id[r-1]; j++){
for(int k=0; k<id[r-2]; k++){
if(status[i]&status[mp[r-1][j]] || status[i]&status[mp[r-2][k]] || status[mp[r-2][k]]&status[mp[r-1][j]]) continue;
dp[r][id[r]][j] = std::max(dp[r][id[r]][j],va[i]+dp[r-1][j][k]);
}
}
id[r] += 1;
}
}
for(int i=0; i<id[n-1]; i++) for(int j=0; j<id[n-2]; j++){
ans = std::max(dp[n-1][i][j],ans);
}
printf("%d",ans);
return 0;
}

换了一波滚动数组,但是还是有10.+MB

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
#include <cstdio>
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <queue>
#include <map>
#include <set>
#include <stack>
#include <vector>
//typedef std::pair<int ,int> PP;
typedef long long LL;
const int N = 105,M = 12,E10 = 1024;
int rows[N],status[E10],va[E10],dp[3][E10][E10];
//rows:把地图的每一行转化为数字,H为1,P为0,储存在rows数组
//status记录所有状态的对应的数字
//va记录所有状态对应的人数
//dp[i][j][k]表示第i行状态为j,且i-1行状态为k时,可以放置的最大炮兵数
char ss[12];
int n,m;
int idx,ans;
//idx记录status数组的大小
int id[3],mp[3][E10];
//id[i]数组记录第i行可行的总状态数
//mp[i][j]记录第i行第j个状态在status数组中对应的编号

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
int toStatus()
{
int re = 0;
for(int i=0; i<m; i++){
re += (ss[i] == 'H') ? (1<<i) : 0;
}
return re;
}

void init(int val,int pos,int cnt1)
{
if(pos >= m){
status[idx] = val;
va[idx++] = cnt1;
return ;
}
init(val,pos+1,cnt1);
init(val+(1<<pos),pos+3,cnt1+1);
}

int main()
{
scanf("%d%d",&n,&m);
for(int i=0; i<n; i++){
scanf("%s",ss);
rows[i] = toStatus();
}
init(0,0,0);
for(int i=0; i<idx; i++){
if(status[i]&rows[0]) continue;
mp[0][id[0]] = i;
dp[0][id[0]][0] = va[i];
id[0] += 1;
}
for(int i=0; i<idx; i++){
if(status[i]&rows[1]) continue;
mp[1][id[1]] = i;
for(int j=0; j<id[0]; j++){
if(status[i]&status[mp[0][j]]) continue;
dp[1][id[1]][j] = va[i] + dp[0][j][0];
}
id[1] += 1;
}
for(int r=2; r<n; r++){
int rm3 = r%3,rs1m3 = (r-1)%3, rs2m3 = (r-2)%3;
memset(dp[rm3],0,sizeof(rm3));
id[rm3] = 0;
for(int i=0; i<idx; i++){
if(status[i]&rows[r]) continue;
mp[rm3][id[rm3]] = i;
for(int j=0; j<id[rs1m3]; j++){
for(int k=0; k<id[rs2m3]; k++){
if(status[i]&status[mp[rs1m3][j]] || status[i]&status[mp[rs2m3][k]] || status[mp[rs2m3][k]]&status[mp[rs1m3][j]]) continue;
dp[rm3][id[rm3]][j] = std::max(dp[rm3][id[rm3]][j],va[i]+dp[rs1m3][j][k]);
}
}
id[rm3] += 1;
}
}
int ns1m3 = (n-1)%3, ns2m3 = (n-2)%3;
for(int i=0; i<id[ns1m3]; i++) for(int j=0; j<id[ns2m3]; j++){
ans = std::max(dp[ns1m3][i][j],ans);
}
printf("%d",ans);
return 0;
}

2020ICPC 江西省大学生程序设计竞赛L WZB’s Harem

题目链接https://ac.nowcoder.com/acm/contest/15806/L
题目翻译: n皇后问题的退化版——n车问题 n<=20
状压 yyds

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 <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <queue>
#include <vector>
#include <stack>
#include <map>
#include <set>
typedef long long int LL;
const int INF = 0x7fffffff, SCF = 0x3fffffff;
const int N = 21, MOD = 1000000007;
int status[N][1<<N],dp[N][1<<N];
int id[N];
int board[N];
int qu[N];
int n;
int toStatus()
{
int re = 0;
for(int i=1; i<=n; i++){
re = (re<<1)+qu[i];
}
return re;
}
int main()
{
scanf("%d",&n);
for(int i=1; i<=n; i++){
for(int j=1; j<=n; j++){
scanf("%d",&qu[j]);
}
board[i] = toStatus();
}
for(int i=0; i<(1<<n); i++){
int tt = i, cnt = 0;
while(tt > 0){
cnt += (tt&1);
tt >>= 1;
}
status[cnt][id[cnt]++] = i;
}
dp[0][0] = 1; //No.Status 0, id 0;
for(int i=1; i<=n; i++){
for(int k=0; k<id[i-1]; k++){
int pre = status[i-1][k];
for(int j=0; j<n; j++){
if((pre>>j)&1) continue;
if( !( (1<<j) & board[i]) ) {
int nxt = pre | (1<<j);
dp[i][nxt] = (dp[i][nxt] + dp[i-1][pre]) % MOD;
}
}
}
}
LL ans = 1;
for(int i=2; i<=n; i++){
ans = ans*i%MOD;
}
printf("%lld",ans * dp[n][(1<<n)-1] % MOD);
}

【线形DP练习】洛谷 P1868饥饿的奶牛 P1091合唱队形 P1541乌龟棋 P1020导弹拦截.md

说明 - 2022-05-05

本篇博客为本人原创, 原发布于CSDN, 在搭建个人博客后使用爬虫批量爬取并挂到个人博客, 出于一些技术原因博客未能完全还原到初始版本(而且我懒得修改), 在观看体验上会有一些瑕疵 ,若有需求会发布重制版总结性新博客。发布时间统一定为1111年11月11日。钦此。

洛谷 P1868 饥饿的奶牛

https://www.luogu.com.cn/problem/P1868
思路:
dp[N]表示 在前i块草皮中最多能吃到几块
sort 把所有区间按照右端的大小排序
for 遍历[0,区间中出现过的最大长度]
{
dp[i] = dp[i-1] (继承上一个草皮的dp)
if 存在区间[x,y] 使得y=i 则dp[y] = max ( dp[y] , dp[x-1]+len([x,y]) )
}


#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef long long LL;
typedef std::pair<int ,int> PP;
const int N = 3e6+5;
int n,v,vv;
int dp[N];
bool cmp(PP x, PP y)
{
return x.second < y.second ;
}

int main()
{
   std::vector<PP> vc;
   std::cin >> n;
   for(int i=0; i<n; i++){
      std::cin>> v  >> vv;
      vc.push_back(std::make_pair(v,vv));
   }
   std::sort(vc.begin(),vc.end(),cmp);
   int idx=1;
   for(std::vector<PP>::iterator iter = vc.begin(); iter != vc.end(); iter++){
      //这里要加等号,否则最后两个样例会WA
      while(idx <= iter->second){
         dp[idx] = std::max(dp[idx],dp[idx-1]);
         idx += 1;
      }
      dp[iter->second] = std::max(dp[iter->first-1]+iter->second-iter->first+1,dp[iter->second]);
   }
   std::cout << dp[vc[vc.size()-1].second] << std::endl;
   return 0;
}

洛谷 P1091 合唱队形

https://www.luogu.com.cn/problem/solution/P1091
此题要求提出最少的人,获得一个最长的先单调递增后点掉递减的队伍,对于队伍递增递减部分的长度没有要求。
思路:
up[]数组记录 在正向遍历下 第i个位置的最长升序列长度(第一个长度计0)
dn[]数组记录 在反向遍历下 第i个位置的最长升序列长度(第一个长度计0)
然后遍历一遍 [0,n-1],找出最大的 先增后降序列长度 (up[i]+dn[i]+1)
n-最大长度 即为答案


#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef long long LL;
typedef std::pair<int ,int> PP;
const int N = 105;
int qu[N],up[N],dn[N];
int n;

int main()
{
   scanf("%d",&n);
   for(int i=0; i<n ;i++){
      scanf("%d",&qu[i]);
   }
   for(int i=1; i<n; i++){
      for(int j=i-1; j>=0; j--){
         if(qu[i] > qu[j]){
            up[i] = std::max(up[i],up[j]+1);
         }
      }
   }
   for(int i=n-2; i>=0; i--){
      for(int j=i+1; j<n; j++){
         if(qu[i] > qu[j]){
            dn[i] = std::max(dn[i],dn[j]+1);
         }
      }
   }
   int mx = 0;
   for(int i=0; i<n; i++){
      mx = std::max(up[i]+dn[i]+1,mx);
   }
   std::cout << n-mx;
   return 0;
}

洛谷 P1541 乌龟棋

https://www.luogu.com.cn/problem/P1541
思路:
开四重循环dp,数组dp[i][j][k][r]记录当玩家使用i张1,j张2,k张3,r张4时能得到的最高分
.
最开始空间超限一个样例,由于dp数组中存在0下标时不好计算,我把数组的规模增加了1(用dp[1][1][1][1]当成没有牌时的情况,相当于原本的dp[0][0][0][0])
.
存在未AC样例 时间40ms 空间150M 空间超限,应该需要刚好卡完美空间,不能增加规模


#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef long long LL;
typedef std::pair<int ,int> PP;
const int N = 355,M = 125;
int dp[M][M][M][M];
int cell[N],num[5];
int n,m,v;

int main()
{
   scanf("%d%d",&n,&m);
   for(int i=0; i<n; i++){
      scanf("%d",&cell[i]);
   }
   for(int i=0; i<m; i++){
      scanf("%d",&v);
      num[v] += 1;
   }
   num[1] += 1;
   num[2] += 1;
   num[3] += 1;
   num[4] += 1;
   for(int i=1; i<=num[1]; i++){
      for(int j=1; j<=num[2]; j++){
         for(int k=1; k<=num[3]; k++){
            for(int r=1; r<=num[4]; r++){
               dp[i][j][k][r] = std::max(
                  std::max(dp[i-1][j][k][r],dp[i][j-1][k][r])
                 ,std::max(dp[i][j][k-1][r],dp[i][j][k][r-1])
                  ) + cell[(i-1)+(j-1)*2+(k-1)*3+(r-1)*4];
            }
         }
      }
   }
   std::cout << dp[num[1]][num[2]][num[3]][num[4]];
   return 0;
}

所以需要优化一下空间,数组规模需要缩减多加的那一个单位的规模
.
把原来的每一轮通过三个max函数进行一次dp,变成每一轮对i/j/k/r dp四次
dp前做一次判断,只有当i/j/k/r != 0时,才进行dp
这样做的好处时第一轮时i/j/k/r都=0,所以dp[0][0][0][0]就可以空过,于是dp[0][0][0][0]就可以提前初始化而不用担心在循环中被更改
.
该样例AC 时间52ms 空间125M


#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef long long LL;
typedef std::pair<int ,int> PP;
const int N = 355,M = 125;
int dp[M][M][M][M];
int cell[N],num[5];
int n,m,v;

int main()
{
   scanf("%d%d",&n,&m);
   for(int i=0; i<n; i++){
      scanf("%d",&cell[i]);
   }
   for(int i=0; i<m; i++){
      scanf("%d",&v);
      num[v] += 1;
   }
   dp[0][0][0][0] = cell[0];
   for(int i=0; i<=num[1]; i++){
      for(int j=0; j<=num[2]; j++){
         for(int k=0; k<=num[3]; k++){
            for(int r=0; r<=num[4]; r++){
               int idx = i+j*2+k*3+r*4;
               if(i) dp[i][j][k][r] = std::max(dp[i][j][k][r],dp[i-1][j][k][r]+cell[idx]);
               if(j) dp[i][j][k][r] = std::max(dp[i][j][k][r],dp[i][j-1][k][r]+cell[idx]);
               if(k) dp[i][j][k][r] = std::max(dp[i][j][k][r],dp[i][j][k-1][r]+cell[idx]);
               if(r) dp[i][j][k][r] = std::max(dp[i][j][k][r],dp[i][j][k][r-1]+cell[idx]);
            }
         }
      }
   }
   std::cout << dp[num[1]][num[2]][num[3]][num[4]];
   return 0;
}


洛谷 P1020 导弹拦截

https://www.luogu.com.cn/problem/P1020
两个输出分别为:最长不上升子序列长度,最长上升子序列长度
O(n2)思路: dp
用dp[]数组记录到第i个位置的最长不上升子序列长度。遍历所有导弹[0,n-1],遍历到i是,检查i之前的不比i低的导弹j dp[i] =
max(dp[i] , dp[j]+1)
最长上升同理


#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef long long LL;

const int N = 100005;
int hh[N],noup[N],up[N];


int main()
{
int idx=0,mxno,mxup;
do{
scanf("%d",&hh[idx]);
}while(getchar() == ’ ');
mxno = mxup = 0;
for(int i=2; i<=idx; i
){
for(int j=i-1; j>=1; j–){
if(hh[i]<=hh[j]) noup[i] = std::max(noup[i],noup[j]+1);
else up[i] = std::max(up[i],up[j]+1);
}
mxno = std::max(noup[i],mxno);
mxup = std::max(up[i],mxup);
}
printf("%d\n%d",mxno+1,mxup+1);
return 0;
}

O(nlog2)思路:
遍历所有导弹[0,n-1]
若第i个导弹对于前面的导弹满足“不上升”则存入数组arr[]的尾部,若打破了不上升原则,则再arr[]中二分查找height[i](i导弹的高度),使得最后可以用height[i]代替最左边的可以使height[i]代替此位置后满足“不上升”原则的位置
最后arr数组的长度就是所求答案
最长上升同理

注意:若序列12 13 15,插入13 若求不下降 则 变成 12 13 13, 若求上升 则变成 12 13 15


#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef long long LL;
typedef std::pair<int ,int> PP;
const int N = 100005,M = 125;
int qu[N],up[N],noup[N];
int idx,lno,lup;
int main()
{
do{
scanf("%d",&qu[idx++]);
}while(getchar() == ’ ');

   noup[0] = up[0] = qu[0];
   for(int i=1; i<idx; i++){
      if(qu[i] <= noup[lno]) noup[++lno] = qu[i];
      else *std::upper_bound(noup,noup+lno+1,qu[i],std::greater<int>()) = qu[i];

      if(qu[i] > up[lup]) up[++lup] = qu[i];
      else *std::lower_bound(up,up+lup+1,qu[i]) = qu[i];
   }
   std::cout << lno+1 << std::endl << lup+1;
   return 0;
}

【算法】【C++】初学算法,在此记录.md

说明 - 2022-05-05

本篇博客为本人原创, 原发布于CSDN, 在搭建个人博客后使用爬虫批量爬取并挂到个人博客, 出于一些技术原因博客未能完全还原到初始版本(而且我懒得修改), 在观看体验上会有一些瑕疵 ,若有需求会发布重制版总结性新博客。发布时间统一定为1111年11月11日。钦此。

在这里插入图片描述

0.预留位置

1.使用Manacher算法求最长回文子串

给出一个字符串,要求计算出这一字符串的最长回文子串的长度。
如果遍历每一个字符,并以该字符为中心向两边查找,则其时间
复杂度为O(n2)。
Manacher算法,又被戏称为“马拉车”算法,可以在时间复杂度
为O(n)的情况下求解一个字符串的最长回文子串的长度。

1.

回文串分为奇回文(abcba)和偶回文(baccba),为了避免区分奇偶的麻烦,把字符串变一下形式,在字符串之间加入特殊符号,并且
首位再加不同的特殊符号以防越界 ,于是字符串变成奇回文(上述偶回文得到的新字符串:@#a#b#c#c#b#a!)

2.

给得到的新字符串str_new定义一个辅助数组int p[],p[i]表示在str_new中以第i个位置为中心得到的最长回文串的 半径

3.

可想而知,若新字符串str_new的最大半径为p[i],则原字符串的最长回文子串长度为p[i]-1

4.![在这里插入图片描述](https://img-blog.csdnimg.cn/20200722184021570.PNG?x-oss-

process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dsZGNtenk=,size_16,color_FFFFFF,t_70)

对于p[i],如果i<mx,设j是i关于id对称点,如图所示,则基于以下三
种情况,可以求出p[i]的值:
(1)以j为中心的回文串有一部分在以id为中心的回文串之外。因为mx
是以id为中心的最长回文的右边界,所以以i为中心的回文串不可能会
有字符在以id为中心的回文串之外;否则mx就不是以id为中心的最长回
文的右边界。所以,在这种情况下,p[i]=mx–i。
(2)以j为中心的回文串全部在以id为中心的回文串的内部,则
p[i]=p[j],而且p[i]不可能再增加。
(3)以j为中心的回文串的左端正好与以id为中心的回文串的左端重合。
则p[i]=p[j]或p[i]=mx–i,并且p[i]还有可能会继续增加,即while
(str_new[i-p[i]]==str_new[i+p[i]]) p[i]++;

5.

从str[1]开始遍历到末尾
先通过


if(i<mx) p[i]=std::min(p[2*id-i],mx-i);
else p[i]=1;

确定好id-mx范围内的以i为中心的最长子串半径,然后再通过


while(str[i+p[i]]==str[i-p[i]]) p[i]+=1;

确定最长半径

若得到以i为中心的子串最右位置超过mx,则更新范围


if(p[i]+i>mx) //超出最右边就更新范围
{
id=i;
mx=p[i]+i;
mxsub=std::max(mxsub,p[i]); //更新最长子串长度
}

代码:输入一个字符串,用Manacher算法求其中最长回文子串的长度


#include
#include
#include
#include
const int max_len = 1e6+3; //输入字符串的最大长度
const int inPos=max_len+2; //字符串输入位置,从str数组中间输入字符串
int p[max_len2]; //p[i]:以i为中心的回文串的最大长度
int len;
char str[max_len
2]; //用于储存输入的字符串

//把字符串各字符之间插入字符,首位再各插入不同字符防止越界
void change_str()
{
    int index=0;
    len=strlen(str);            //获得输入字符串的长度
    str[index++]='@';
    for(int i=inPos; i<len; i++)
    {
        str[index++]='#';
        str[index++]=str[i];
    }
    str[index++]='#';
    str[index]='!';
    len=index;                  //获得改造后字符串的长度
}

int manacher()
{
    int mx=0,id=1;              //mx表示最右端位置 id表示中心点位置
    int mxsub=0;                //最长子串长度
    for(int i=1; i<len; i++)
    {
        if(i<mx)    p[i]=std::min(p[2*id-i],mx-i);
        else        p[i]=1;
        while(str[i+p[i]]==str[i-p[i]])     p[i]+=1;
        if(p[i]+i>mx)           //超出最右边就更新范围
        {
            id=i;
            mx=p[i]+i;
            mxsub=std::max(mxsub,p[i]);     //更新最长子串长度
        }
    }
    return mxsub-1;             //返回结果
}

int main()
{
    memset(str,' ',sizeof(str));
    scanf("%s",str+inPos);
    change_str();
    std::cout << manacher() << std::endl;
    return 0;
}

2.用树结构支持并查集

并查集:

在一些应用中,需要把n个不同元素划分成不相交的若干组,每
一组的元素构成一个集合,由于这类问题主要涉及对集合的合
并和查找,因此称为并查集。
并查集维护一些互不相交的集合S={S1
, S2
, …, Sr},每个集合Si
都有一个特殊元素rep[Si],称为集合的代表元。

并查集的三种操作:

Make_Set(x):加入一个含单元素x的集合{x}到并查集S,且rep[{x}]=x。 x不能被包含在任何一个Si中, 因为S里任何两个集合是不相交的。
初始时,对每个元素x执行一次Make_Set(x)。

join(x, y):把x和y所在的两个不同集合Sx和Sy
合并:从S中删除Sx和Sy,
并加入Sx与Sy的并集。

set_find(x):返回x所在集合Sx
的代表元rep[Sx]。

树结构

每个集合用一棵树表示, 根为集合的代表元。每个节点p设一个指针(抄的原话,看起来不像指针)
set[p],记录它所在树的根节点序号。如果set[p]<0,则表明p为根节点。
初始时,为每一个元素建立一个集合,即 set[x]=-1(1≤x≤n)。

树结构查找操作set_find(x) :边查找边“路径压缩”

首先,从节点x出发,沿set指针查找节点x所在树的根节点f(set[f]<0)。
然后,进行路径压缩,将x至f的路径上经过的每个节点的set指针都指向f。
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200722203030627.PNG?x-oss-
process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dsZGNtenk=,size_16,color_FFFFFF,t_70)

树结构查找操作:

int set_find(int p) // 查找p所在集合的代表元,用路径压缩优化
{
if (set[p]<0)
return p;
return set[p]=set_find(set[p]);
}

树结构合并操作join(x, y) :将两棵树的根节点相连

计算x元素所在并查集的树根fx和y元素所在并查集的树根fy。如果fx==fy,则说
明元素x和元素y在同一并查集中;否则将x所在的集合并入y所在的集合,也就
是将fx的set指针设为fy。

题目:Find them, Catch them

The police office in Tadu City decides to say ends to the chaos, as
launch actions to root up the TWO gangs in the city, Gang Dragon and
Gang Snake. However, the police first needs to identify which gang a
criminal belongs to. The present question is, given two criminals; do
they belong to a same clan? You must give your judgment based on
incomplete information. (Since the gangsters are always acting
secretly.)

Assume N (N <= 10^5) criminals are currently in Tadu City, numbered
from 1 to N. And of course, at least one of them belongs to Gang
Dragon, and the same for Gang Snake. You will be given M (M <= 10^5)
messages in sequence, which are in the following two kinds:

D [a] [b] where [a] and [b] are the numbers of two criminals, and they
belong to different gangs.

A [a] [b] where [a] and [b] are the numbers of two criminals. This
requires you to decide whether a and b belong to a same gang. Input
The first line of the input contains a single integer T (1 <= T <=
20), the number of test cases. Then T cases follow. Each test case
begins with a line with two integers N and M, followed by M lines each
containing one message as described above. Output For each message “A
[a] [b]” in each case, your program should give the judgment based on
the information got before. The answers might be one of “In the same
gang.”, “In different gangs.” and “Not sure yet.”

Sample Input
1
5 5
A 1 2
D 1 2
A 1 2
D 2 4
A 1 4
Sample Output Not
sure yet.
In different gangs.
In the same gang.

翻译一下:

Tadu市的警察局决定结束混乱,因此要采取行动,根除城市中的两大 帮派:龙帮和蛇帮。然而,警方首先需要确定某个罪犯是属于哪个帮派。
目前的问题是,给出两个罪犯,他们是属于同一个帮派吗?您要基于不 完全的信息给出您的判断,因为歹徒总是在暗中行事。
假设在Tadu市现在有N(N≤105 )个罪犯,编号从1到N。当然,至少有 一个罪犯属于龙帮,也至少有一个罪犯属于蛇帮。给出M(M≤105
)条 消息组成的序列,消息有下列两种形式:
D [a] [b] 其中[a]和[b]是两个犯罪分子的编号,他们属于不同的帮派;
A [a] [b] 其中[a]和[b]是两个犯罪分子的编号,您要确定a和b是否属于同一帮派。 输入
输入的第一行给出给出一个整数T(1≤T≤20),表示测试用例的个 数。后面跟着T个测试用例,每个测试用例的第一行给出两个整数
N和M,后面的M行每行给出一条如上面所描述的消息。 输出 对于在测试用例中的每条“A [a] [b]”消息,您的程序要基于此前
给出的信息做出判断。回答是如下之一 “In the same gang.”, “In different gangs.”或“Not sure
yet.”。

解题思路:

1.建立一个数组int
set[]其大小大于等于罪犯数的两倍,把数组初始化为-1,即数组的每个元素代表一个独立的集合。把set[i+n]看作是集合set[i]的对立集合(n为罪犯数目)
2.遇到D[a]
[b]语句,说明a和b不能在同一个集合,就把a所在的集合的值set[a]赋值为b的对立集合set[b+n]对应的下标b+n,把b所在的集合的值赋值给a的对立集合的下标a+n
每次遇到D[a] [b]语句都进行如此操作,便可以做到集合合并(相互链接的就算一个集合了)
3.遇到A[a] [b]语句,查询(参照路径压缩的思路)a、b所在的集合与b所在的集合相同,输出 “In the same gang.”
,若a、b所在的集合不同,而且a不在b的对立集合,输出“Not sure
yet.”,否则输出 “In different gangs.”

代码:


#include
#include
#include
#include
const int max_criminal = 1e5+5;
int sett[max_criminal*2]; //建立多于罪犯总数两倍个集合

//查找元素所在的集合(顺便压缩路径)
int set_find(int index)
{
    if(sett[index]<0)
        return index;
    return sett[index]=set_find(sett[index]);
}

int main()
{
    int t,n,m,a,b;
    char ch;
    std::cin >> t;
    while(t-- && std::cin >> n >> m)
    {
        memset(sett,-1,sizeof(sett));
        for(int i=0; i<m; i++)
        {
            std::cin >> ch >> a >> b;
            if(ch=='D')
            {
                sett[set_find(a)]=set_find(b+n);
                sett[set_find(b)]=set_find(a+n);
            }
            else
            {
                if(set_find(a)==set_find(b))
                    std::cout << "In the same gang." << std::endl;
                else if(set_find(a)==set_find(b+n))
                    std::cout << "In different gangs." << std::endl;
                else
                    std::cout << "Not sure yet." << std::endl;
            }
        }
    }
    return 0;
}

3.应用Trie树查询字符串

定义(Trie树)

Trie树,也被称为单词查找树,前缀树,或字典树。
其基本性质如下:
• 1,根节点不包含字符,除根节点外,每个节点只包含一个字符。
• 2,将从根节点到某一个节点的路上经过的节点所包含的字符连
接起来,就是该节点对应的字符串。
• 3,对于每个节点,其所有子节点包含的字符是不相同的。

![在这里插入图片描述](https://img-blog.csdnimg.cn/20200723171651441.PNG?x-oss-
process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dsZGNtenk=,size_16,color_FFFFFF,t_70)
Trie树的根节点root对应空字符串。一般情况下,不是所有的节点
都有对应的值,只有叶节点和部分内节点所对应的字符串才有相
关的值。所以,Trie树是一种用于快速检索的多叉树结构,每个
节点保存一个字符,一条路可以用于表示一个字符串、一个电话
号码等等信息。

题目:Shortest Prefixes

字符串的前缀是从给出的字符串的开头开始的子字符串。
“carbon"的前缀是:“c”,“ca”,“car”,“carb”,“carbo"和"carbon”。
在本题中,空串不被视为前缀,但是每个非空字符串都被视为其
自身的前缀。在日常语言中,我们会用前缀来缩写单词。例如,
“carbohydrate” (“碳水化合物”)通常被缩写为"carb”。在本题
中,给出一组单词,请您为每个单词找出能唯一标识该单词的最
短前缀。
• 在给出的样例输入中,“carbohydrate"可以被缩写为"carboh”,但
不能被缩写为"carbo"(或者更短),因为有其他单词以"carbo"开
头。
• 完全匹配也可以作为前缀匹配。例如,给出的单词"car",其前缀
"car"与"car"完全匹配。因此,"car"是"car"的缩写,而不是
"carriage"或列表中以"car"开头的其他任何其他词的缩写。

输入
• 输入至少有两行,最多不超过1000行。每行给出一个由1到20
个小写字母组成的单词。

输出
• 输出的行数与输入的行数相同。输出的每一行先给出输入对应行
中的单词,然后给出一个空格,最后给出唯一(无歧义)标识该
单词的最短前缀。

代码(可能有问题):


#include
#include
#include
#include
int len,idx,letterIdx; //letterIdx表示英文字母按字母表的序号(0-25)
const int max_node = 1000*22; //最大节点数
char save_str[1001][22]; //用于保存字符串
int Node[max_node][26]; //提前给节点开出空间来
int vis[max_node]; //节点访问次数

struct Trie
{
    int tidx;                       //目前第一个尚未使用的节点序号(到目前位置一共使用了多少节点)
    Trie()
    {
        tidx=1;
        memset(Node,0,sizeof(Node));
    }

    //得到字母序号letterIdx
    int getletterIdx(char ch)
    {
        return ch-'a';
    }

    void insert(char * str)
    {
        idx=0,len=strlen(str);
        for(int i=0; i<len; i++)
        {
            letterIdx=getletterIdx(str[i]);
            if(Node[idx][letterIdx]==0)//如果此节点应该对应的下一个解点还没有给出,则创建这个解点
            {
                vis[tidx]=0;
                Node[idx][letterIdx]=tidx++;
            }
            idx=Node[idx][letterIdx];   //序号idx更新为的下一个节点的序号
            vis[idx]++;                 //访问次数+1
        }
    }

    void search(char * str)
    {
        idx=0,len=strlen(str);
        for(int i=0; i<len; i++)
        {
            putchar(str[i]);
            letterIdx=getletterIdx(str[i]);
            if(vis[Node[idx][letterIdx]]<=1) //如果下一个节点访问次数仅为1,则只用之前的节点就可以确定唯一单词
            {
                return ;
            }
            idx=Node[idx][letterIdx];       //序号idx更新为下一个节点的序号

        }
    }
};

int main()
{
    int t,strIdx=0;
    Trie trie;
    std::cin >> t;
    for(int i=0; i<t; i++)
    {
        scanf("%s",save_str[strIdx]);
        trie.insert(save_str[strIdx]);
        strIdx+=1;
    }
    for(int i=0; i<t; i++)
    {
        std::cout << save_str[i] << " ";
        trie.search(save_str[i]);
        std::cout << std::endl;
    }

    return 0;
}

这个解法必须建立在所有输入没有重复的前提上,有的话要另外加对应的处理。

4.使用KMP算法匹配字符串

Knuth-Morris-Pratt 字符串查找算法,简称为 “KMP算法”,常用于在一个文本串S内查找一个模式串P 的出现位置,这个算法由Donald
Knuth、Vaughan Pratt、James H. Morris三人于1977年联合发表,故取这3人的姓氏命名此算法。

以下为个人理解

大概思路:

为了避免逐位查找带来的时间浪费,KMP算法使用一个数组 int
next[]记录一个字符串从第0位到第i位相同的前缀和后缀长度(或者此长度-1,都可以),查询时可以跨指定长度匹配,这个next数组怎么求稍后说。这样可以提高匹配速度。
比如: 字符串 str1:abcababcabca 与字符串 str2:abcabc
匹配,str2的next数组为{-1,-1,-1,0,1,2}。两字符串匹配到str1[4]与str2[4]完全相同,第五位不同,不完全相同。下一次匹配不需要逐位匹配(从str1[1]与str[0]开始匹配),而是尝试从str1[5]与str2[next[4]+1]匹配

next数组求法:

对所查询字符串求next数组,第一位不管(一般为0或-1),从第二位开始,根据以前的信息,如果上一位前后缀匹配,则直接判断下一位是否匹配,若下一位也匹配,长度求出,若下一位不匹配,则在目前已经匹配了的若干为中,寻找最长相同前后缀

代码:(匹配一个字符串在另一个字符串中的出现次数)


#include
#include
#include
#include
char str1[100005],str2[1005];
int next[1005];

void getNext()
{
    next[0]=-1;
    int len=strlen(str2),k;
    for(int i=1; i<len ;i++)
    {
        k=next[i-1];
        while(str2[k+1]!=str2[i] && k>=0) k=next[k];
        if(str2[k+1]==str2[i]) next[i]=k+1;
        else next[i]=-1;
    }
}
int kmp()
{
    int counter=0,i=0,j=0,len=strlen(str1),sublen=strlen(str2);
    while(i<len && j<sublen)
    {
        if(str1[i]==str2[j])
        {
            i+=1,j+=1;
            if(j==sublen)
            {
                counter+=1;
                j=next[j-1]+1;
            }
        }
        else
        {
            if(j==0) i+=1;
            else j=next[j-1]+1;
        }
    }
    return counter;
}

int main()
{
    int t;
    std::cin >> t;
    std::cin >> str1;
    while(t--)
    {
        std::cin >> str2;
        getNext();
        std::cout << kmp() << std::endl;
    }
    return 0;
}

【线段树】 L3-017 森森快递 (30分) 27分WA代码.md

说明 - 2022-05-05

本篇博客为本人原创, 原发布于CSDN, 在搭建个人博客后使用爬虫批量爬取并挂到个人博客, 出于一些技术原因博客未能完全还原到初始版本(而且我懒得修改), 在观看体验上会有一些瑕疵 ,若有需求会发布重制版总结性新博客。发布时间统一定为1111年11月11日。钦此。

L3-017 森森快递 (30分)

森森开了一家快递公司,叫森森快递。因为公司刚刚开张,所以业务路线很简单,可以认为是一条直线上的N个城市,这些城市从左到右依次从0到(N−1)编号。由于道路限制,第i号城市(i=0,⋯,N−2)与第(i+1)号城市中间往返的运输货物重量在同一时刻不能超过C
​i
​​ 公斤。

公司开张后很快接到了Q张订单,其中j张订单描述了某些指定的货物要从S
​j
​​ 号城市运输到T
​j
​​ 号城市。这里我们简单地假设所有货物都有无限货源,森森会不定时地挑选其中一部分货物进行运输。安全起见,这些货物不会在中途卸货。

为了让公司整体效益更佳,森森想知道如何安排订单的运输,能使得运输的货物重量最大且符合道路的限制?要注意的是,发货时间有可能是任何时刻,所以我们安排订单的时候,必须保证共用同一条道路的所有货车的总重量不超载。例如我们安排1号城市到4号城市以及2号城市到4号城市两张订单的运输,则这两张订单的运输同时受2-3以及3-4两条道路的限制,因为两张订单的货物可能会同时在这些道路上运输。

输入格式:

输入在第一行给出两个正整数N和Q(2≤N≤10
​5
​​ , 1≤Q≤10
​5
​​ ),表示总共的城市数以及订单数量。

第二行给出(N−1)个数,顺次表示相邻两城市间的道路允许的最大运货重量C
​i
​​ (i=0,⋯,N−2)。题目保证每个C
​i
​​ 是不超过2
​31
​​ 的非负整数。

接下来Q行,每行给出一张订单的起始及终止运输城市编号。题目保证所有编号合法,并且不存在起点和终点重合的情况。

输出格式:

在一行中输出可运输货物的最大重量。

输入样例:

10 6
0 7 8 5 2 3 1 9 10
0 9
1 8
2 7
6 3
4 5
4 2

输出样例:

7

样例提示:

我们选择执行最后两张订单,即把5公斤货从城市4运到城市2,并且把2公斤货从城市4运到城市5,就可以得到最大运输量7公斤。


#include
#include
#include
#include

const int maxn=1e5+5;

int n,q;
int vv1,vv2;
int ans;

struct Node
{
    int val,lz;
}tree[maxn<<2];


int getMid(int a,int b){ return (a+b)>>1; }



void build(int l,int r,int rt)
{
tree[rt].lz=0;
if(l==r)
{
std::cin >> tree[rt].val;
return ;
}
else
{
int mid=getMid(l,r);
build(l,mid,rt<<1);
build(mid+1,r,rt<<1|1);
}
tree[rt].val=std::min(tree[rt<<1].val,tree[rt<<1|1].val);
}

void pushDown(int rt)
{
    if(!tree[rt].lz) return ;
    tree[rt<<1].val-=tree[rt].lz;
    tree[rt<<1|1].val-=tree[rt].lz;
    tree[rt<<1].lz+=tree[rt].lz;
    tree[rt<<1|1].lz+=tree[rt].lz;
    tree[rt].lz=0;
}

void update(int sl,int sr,int l,int r,int rt,int val)
{
    if(l>=sl && r<=sr)
    {
        tree[rt].val-=val;
        tree[rt].lz+=val;
        return ;
    }
    pushDown(rt);
    int mid=getMid(l,r);
    if(sr<=mid) update(sl,sr,l,mid,rt<<1,val);
    else if(sl>mid) update(sl,sr,mid+1,r,rt<<1|1,val);
    else
    {
        update(sl,sr,l,mid,rt<<1,val);
        update(sl,sr,mid+1,r,rt<<1|1,val);
    }
//子树update完之后,根还还还还还还还还要取子树最小值
    tree[rt].val=std::min(tree[rt<<1].val,tree[rt<<1|1].val);
}

int query(int sl,int sr,int l,int r,int rt)
{
    if(l>=sl && r<=sr) return tree[rt].val;
    pushDown(rt);
    int mn=2147483647,mid=getMid(l,r);
    if(sr<=mid) mn=std::min(mn,query(sl,sr,l,mid,rt<<1));
    else if(sl>mid) mn=std::min(mn,query(sl,sr,mid+1,r,rt<<1|1));
    else
    {
        mn=std::min(mn,query(sl,sr,l,mid,rt<<1));
        mn=std::min(mn,query(sl,sr,mid+1,r,rt<<1|1));
    }
    return mn;
}

bool cmp(std::pair<int ,int > a,std::pair<int ,int > b)
{
    if(a.second-a.first==b.second-b.first) return a.first<b.first;
    return a.second-a.first<b.second-b.first;
}

int main()
{
    std::cin >> n >> q;
    std::pair<int ,int > pa[q];
    build(1,n-1,1);
    ans=0;
    for(int i=0; i<q; i++)
    {
        std::cin >> pa[i].first >> pa[i].second;
        if(pa[i].first>pa[i].second) std::swap(pa[i].first,pa[i].second);
//线段树的一个点表示相邻的一段路,所以要second-1
//但由于题目的城市编号从0开始,这里默认从1开始,所以变成first+1
        pa[i].first+=1;
    }
    std::sort(pa,pa+q,cmp);
    for(int i=0; i<q; i++)
    {
        int ch=query(pa[i].first,pa[i].second,1,n-1,1);
        ans+=ch;
        update(pa[i].first,pa[i].second,1,n-1,1,ch);
    }
    std::cout << ans;

    return 0;
}

【线段树练习】 洛谷线段树模板1 CF1288E.md

说明 - 2022-05-05

本篇博客为本人原创, 原发布于CSDN, 在搭建个人博客后使用爬虫批量爬取并挂到个人博客, 出于一些技术原因博客未能完全还原到初始版本(而且我懒得修改), 在观看体验上会有一些瑕疵 ,若有需求会发布重制版总结性新博客。发布时间统一定为1111年11月11日。钦此。

洛谷 P3372 线段树模板1


#include <bits/stdc++.h>

const int N = 1e5+4;
typedef long long LL;
LL val[N<<2],lazy[N<<2];
int qu[N];
int n,m,x,y,k,op;

int getMid(int l,int r)
{
   return (l+r) >> 1;
}

void build(int l,int r,int rt)
{
   if(l == r) val[rt] = qu[r];
   else{
      int mid = getMid(l,r);
      build(l,mid,rt<<1);
      build(mid+1,r,rt<<1|1);
      val[rt] = val[rt<<1] + val[rt<<1|1];
   }
}

void pushdown(int l,int r,int rt)
{
   if(!lazy[rt]) return;
   int mid = getMid(l,r);
   lazy[rt<<1] += lazy[rt];
   lazy[rt<<1|1] += lazy[rt];
   val[rt<<1] += lazy[rt]*(mid-l+1);
   val[rt<<1|1] += lazy[rt]*(r-mid);
   lazy[rt] = 0;
}


LL query(int sl,int sr,int l,int r,int rt)
{
if(sl >= l && sr <= r) return val[rt];
pushdown(sl,sr,rt);
int mid = getMid(sl,sr);
if(r <=mid) return query(sl,mid,l,r,rt<<1);
else if(l > mid) return query(mid+1,sr,l,r,rt<<1|1);
else return query(sl,mid,l,r,rt<<1) + query(mid+1,sr,l,r,rt<<1|1);
}

void update(int sl,int sr,int l,int r,int v,int rt)
{
   if(sl >= l && sr <= r){
      val[rt] += v*(sr-sl+1);
      lazy[rt] += v;
      return ;
   }
   pushdown(sl,sr,rt);
   int mid = getMid(sl,sr);
   if(r <= mid) update(sl,mid,l,r,v,rt<<1);
   else if(l > mid)  update(mid+1,sr,l,r,v,rt<<1|1);
   else{
      update(sl,mid,l,r,v,rt<<1);
      update(mid+1,sr,l,r,v,rt<<1|1);
   }
   val[rt] = val[rt<<1] + val[rt<<1|1];
}

void show(int l,int r,int rt)
{
   if(l == r) {printf("%lld ",val[rt]); return ;}
   pushdown(l,r,rt);
   int mid = getMid(l,r);
   show(l,mid,rt<<1);
   show(mid+1,r,rt<<1|1);
}

int main()
{
   scanf("%d%d",&n,&m);
   for(int i=0; i<n; i++){
      scanf("%d",&qu[i]);
   }
   build(0,n-1,1);
   for(int i=0; i<m; i++){
      scanf("%d",&op);
      if(op == 2){
         scanf("%d%d",&x,&y);
         printf("%lld\n",query(0,n-1,x-1,y-1,1));
      }
      else{
         scanf("%d%d%d",&x,&y,&k);
         update(0,n-1,x-1,y-1,k,1);
      }
      //show(0,n-1,1);
      //std::cout << std::endl;
   }

   return 0;
}

CF1288E

https://www.luogu.com.cn/problem/CF1288E
最小的排名 可想而知,若发过消息,则是1,否则为初始值。
最大的排名 ,用线段树记录:

假设人数为N,发消息次数为M,则开一个区间大小为M+N的线段树,用于记录所有人的最大排名。前M位初始化为1,后N为为[1,2,3,4…N]。(代码中用qu[]数组模拟出这样的值,然后初始化线段树),初始化整形变量idx=M

把每个人的最小排名和最晚排名都初始化为其序号。

用qu[]数组记录每个人最后出现的位置。(发一次消息算出现一次,qu[]具体表示什么,看完下方的3条就明白了)

每当一个人i发消息:

1.更新这个人的最小排名为1,最大排名为max(原最大排名,线段树查询qu[i]位置的结果)
2.把区间[idx+1,qu[i]]即这个人以及之前所有人对应的区间+1(是否包含这个人时无所谓的)
3.然后把这个人提前为第一个人,即qu[i]改为出现的位置改为idx–(也可以先提前,这样的话线段树区间应该初始化为0),

输出答案时

最小排名即记录中的最小排名,直接输出
最大排名为max(记录中的最大排名,线段树查询qu[i]位置的结果)


#include
#include
#include
#include
#include
#include
#include
#include
typedef long long int LL;
typedef std::pair<int ,int> PP;
const int N = 3e5+2;
int lazy[(N+N)<<2];
PP span[N];
int qu[N+N];
int idx;
int n,m,k;
void build(int l,int r,int rt)
{
if(l == r){
lazy[rt] = qu[l];
return ;
}
int mid = (l+r) >> 1;
build(l,mid,rt<<1);
build(mid+1,r,rt<<1|1);
}

void pushdown(int rt)
{
   if(!lazy[rt]) return;
   lazy[rt<<1] += lazy[rt];
   lazy[rt<<1|1] += lazy[rt];
   lazy[rt] = 0;
}

void update(int sl,int sr,int l,int r,int val,int rt)
{
   if(sl >=l && sr<=r) {
      lazy[rt] += val;
      return ;
   }
   pushdown(rt);
   int mid = (sl+sr) >> 1;
   if(l<=mid) update(sl,mid,l,r,val,rt<<1);
   if(r>mid) update(mid+1,sr,l,r,val,rt<<1|1);
}

int query(int sl,int sr,int l,int r,int rt)
{
   if(sl>=l && sr<=r){
      return lazy[rt];
   }
   pushdown(rt);
   int mid = (sl+sr) >> 1;
   if(l<=mid)  return query(sl,mid,l,r,rt<<1);
   else   return query(mid+1,sr,l,r,rt<<1|1);
}

int main()
{
   scanf("%d%d",&n,&m);
   idx = m;
   for(int i=1; i<=n; i++){
      span[i].second = span[i].first = i;
      qu[i+m] = i;
   }
   for(int i=1; i<=m; i++){
      qu[i] = 1;
   }
   build(1,n+m,1);
   for(int i=1; i<=n; i++){
      qu[i] = i+m;
   }
   for(int i=0; i<m; i++){
      scanf("%d",&k);
      span[k].second = std::max(span[k].second,query(1,n+m,qu[k],qu[k],1));
      span[k].first = 1;
      update(1,n+m,idx+1,qu[k],1,1);

      qu[k] = idx--;

   }
   for(int i=1; i<=n; i++){
      span[i].second = std::max(span[i].second,query(1,n+m,qu[i],qu[i],1));
      printf("%d %d\n",span[i].first,span[i].second);
   }
   return 0;
}

【背包DP练习】洛谷 P5020货币系统 P1757通天之分组背包 P1064[NOIP2006 提高组]金明的预算方案 P5322 [BJOI2019]排兵布阵.md

说明 - 2022-05-05

本篇博客为本人原创, 原发布于CSDN, 在搭建个人博客后使用爬虫批量爬取并挂到个人博客, 出于一些技术原因博客未能完全还原到初始版本(而且我懒得修改), 在观看体验上会有一些瑕疵 ,若有需求会发布重制版总结性新博客。发布时间统一定为1111年11月11日。钦此。

洛谷 P5020货币系统

https://www.luogu.com.cn/problem/P5020

思路是把货币从小到大排序,然后按顺序依次完全背包dp,每次dp检查i-1种面值的货币能不能凑出第i种货币
做完看了看,这个思路始终进行着令人尴尬的重复,但是懒得改了
然后去题解看了看发现没有比这还麻烦的方法
.
最后四个样例数据范围比较大,但应该都不是需要时间特别长的,而是卡一些技巧。
80分代码:修改一(不是亿)点点就可以满分


#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef long long LL;
typedef std::pair<int ,int> PP;
const int N = 25005,M = 105;
int dp[N],val[M],kik[M];
int n,m,t,cnt;
int main()
{
scanf("%d",&t);
while(t–){
memset(dp,0,sizeof(dp));
memset(kik,0,sizeof(kik));
scanf("%d",&n);
for(int i=0; i<n; i++){
scanf("%d",&val[i]);
}
std::sort(val,val+n);
for(int i=1; i<n; i++){
for(int j=0; j<i; j++){
if(kik[j]) continue;
for(int k=val[j]; k<=val[i]; k++){
dp[k] = std::max(dp[k],dp[k-val[j]]+val[j]);
}
}
if(dp[val[i]] == val[i]) kik[i] = 1;
}
cnt = 0;
for(int i=0; i<n; i++){
if(!kik[i]){
cnt += 1;
}
}
printf("%d\n",cnt);
}
return 0;
}

TEL原因是 仅仅在for i 的一层循环中检查是否剔除面值i,做了很多无用功。
修改:
在for j的一层循环中,若出现面值i已经被凑出来,则不用把循环全部进行完,退出循环即可。这样可以节省一些时间
做出该修改后得分95,还是未AC
于是又在每次dp面值i之前先检查之前是否出现过可以被面值i整除的面值,有的话直接剔除,终于AC
AC代码:


#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef long long LL;
typedef std::pair<int ,int> PP;
const int N = 25005,M = 105;
int dp[N],val[M],kik[M];
int n,m,t,cnt;
int main()
{
scanf("%d",&t);
while(t–){
memset(dp,0,sizeof(dp));
memset(kik,0,sizeof(kik));
scanf("%d",&n);
for(int i=0; i<n; i++){
scanf("%d",&val[i]);
}
std::sort(val,val+n);
for(int i=1; i<n; i++){
for(int j=0; j<i; j++){
if(val[i]%val[j] == 0){
kik[i] = 1;
break;
}
}
if(kik[i]) continue;
for(int j=0; j<i; j++){
if(kik[j]) continue;
for(int k=val[j]; k<=val[i]; k++){
dp[k] = std::max(dp[k],dp[k-val[j]]+val[j]);
}
if(dp[val[i]] == val[i]) break;
}
if(dp[val[i]] == val[i]) kik[i] = 1;
}
cnt = 0;
for(int i=0; i<n; i++){
if(!kik[i]){
cnt += 1;
}
}
printf("%d\n",cnt);
}
return 0;
}

洛谷 P1757 通天之分组背包

https://www.luogu.com.cn/problem/P1757
分组背包


#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef long long LL;
typedef std::pair<int ,int> PP;
const int N = 1005,M = 105;

int m,n,w,v,g,gg;
struct Item
{
   int value,weight;
}gr[N][N];
int num[N],mp[N];
int dp[N];
int idx;

int main()
{
   scanf("%d%d",&m,&n);
   for(int i=0; i<n; i++){
      scanf("%d%d%d",&w,&v,&g);
      if(!mp[g]) mp[g] = ++idx;
      gg = mp[g];
      gr[gg][num[gg]].weight = w;
      gr[gg][num[gg]].value  = v;
      num[gg] += 1;
   }
   for(int i=1; i<=idx; i++){
      for(int j=m; j>=0; j--){
         for(int k=0; k<num[i]; k++){
            if(j >= gr[i][k].weight){
               dp[j] = std::max(dp[j],dp[j-gr[i][k].weight]+gr[i][k].value);
            }
         }
      }
   }
   printf("%d",dp[m]);
   return 0;
}

洛谷 P1064 [NOIP2006 提高组] 金明的预算方案

https://www.luogu.com.cn/problem/P1064


#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
typedef long long LL;
typedef std::pair<int ,int> PP;
const int N = 3.2e4+5,M = 105;
int va[N],ww[N],ex[N][3],zhu[N],dp[N];
int n,m,z,idx,tv,tz,tf,tf2;

int main()
{
   scanf("%d%d",&m,&n);
   for(int i=1; i<=n; i++){
      scanf("%d%d%d",&va[i],&ww[i],&z);
      if(z) {
         ex[z][0] += 1;
         ex[z][ex[z][0]] = i;
      }else{
         zhu[++idx] = i;
      }
   }
   for(int i=1; i<=idx; i++){
      tz = zhu[i];
      for(int j=m; j>=0; j--){
         tv = va[tz];
         if(j >= tv) dp[j] = std::max(dp[j],dp[j-tv]+ww[tz]*va[tz]);
         if(ex[tz][0]){
            tf = ex[tz][1];
            tv = va[tz] + va[tf];
            if(j >= tv) dp[j] = std::max(dp[j],dp[j-tv]+ww[tz]*va[tz]+ww[tf]*va[tf]);
         }
         if(ex[tz][0] >= 2){
            tf2 = ex[tz][2];
            tv = va[tz]+va[tf2];
            if(j >= tv) dp[j] = std::max(dp[j],dp[j-tv]+ww[tz]*va[tz]+ww[tf2]*va[tf2]);
            tv += va[tf];
            if(j >= tv) dp[j] = std::max(dp[j],dp[j-tv]+ww[tz]*va[tz]+ww[tf2]*va[tf2]+ww[tf]*va[tf]);
         }
      }
   }
   printf("%d",dp[m]);
   return 0;
}

洛谷 P5322 [BJOI2019]排兵布阵

分组背包
用dp[i] 记录总共派出i个人的时候,最大的得分
1.准备

想办法构造一个cas[][]数组 记录第i个城堡可以打败j个玩家时刚好需要多少兵力。
这样我们可以认为, 每个城堡为一组 ,每一组中有s个数据分别为:在城堡i,分别打败1,2,3…s个玩家[花费的兵力,得到的分数]

2.开始套分组背包模板

->for [1,2,3,4…n] 枚举所有的n个城堡:
…->for[m,m-1…3,2,1] 倒序枚举派遣总人数:
…/…->for[1,2,3,4…s] 枚举每个城堡的s组数据:
…/…/…->dp[w] = std::max(dp[w], dp[w-cost]+gain);

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <string>
#include <queue>
#include <vector>
#include <stack>
#include <map>
#include <set>
typedef long long int LL;
const int INF = 0x7fffffff, SCF = 0x3fffffff;
const int N = 105, M = 2e4+4, MOD =  1000000007;
int ssa[N][N];//every enemy's solders send to castle i; 
int cas[N][N];//min solders number you send when you can beat j player at castle i;
int dp/*[N]*/[M];//max score you gain when castle = i and solders = j
int s,n,m,v;
int main()
{
    scanf("%d%d%d",&s,&n,&m);
    for(int i=1; i<=s; i++){
        for(int j=1; j<=n; j++){
            scanf("%d",&ssa[j][i]);
        }
    }
    for(int i=1; i<=n; i++){
        std::sort(ssa[i]+1,ssa[i]+s+1);
    }
    for(int i=1; i<=n; i++){
        for(int j=1; j<=s; j++){
            int num = (ssa[i][j]<<1)+1;
            cas[i][j] = num;
            if(num > m) break;
        }
    }
    //for(int i=0; i<M; i++) dp[i] = -SCF;
    //dp[0] = 0;
    for(int i=1; i<=n; i++){
        for(int w=m; w>=0; w--){
            for(int j=0; j<=s; j++){
                int gain = i*j, cost = cas[i][j];
                if(w-cost < 0) break;
                dp[w] = std::max(dp[w], dp[w-cost]+gain);
            }
        }
    }
    printf("%d",dp[m]);
    return 0;
}

【记录】py文件怎么转成exe文件.md

说明 - 2022-05-05

本篇博客为本人原创, 原发布于CSDN, 在搭建个人博客后使用爬虫批量爬取并挂到个人博客, 出于一些技术原因博客未能完全还原到初始版本(而且我懒得修改), 在观看体验上会有一些瑕疵 ,若有需求会发布重制版总结性新博客。发布时间统一定为1111年11月11日。钦此。

其实转完了之后我也是啥也不会的状态,所有的操作仅仅是照葫芦画瓢

第一步:
首先你要有 pyinstaller

第二步:
打开cmd 输入pyinstaller -F --onefile 需要转换的文件
这样就ok了

附带一些pyinstaller的用法(转自https://blog.csdn.net/jirryzhang/article/details/78881512
-F, –onefile 产生一个文件用于部署 (参见XXXXX).
-D, –onedir 产生一个目录用于部署 (默认)
-K, –tk 在部署时包含 TCL/TK
-a, –ascii 不包含编码.在支持Unicode的python版本上默认包含所有的编码.
-d, –debug 产生debug版本的可执行文件
-w,–windowed,–noconsole 使用Windows子系统执行.当程序启动的时候不会打开命令行(只对Windows有效)
-c,–nowindowed,–console 使用控制台子系统执行(默认)(只对Windows有效)
-s,–strip 可执行文件和共享库将run through strip.注意Cygwin的strip往往使普通的win32 Dll无法使用.
-X, –upx 如果有UPX安装(执行Configure.py时检测),会压缩执行文件(Windows系统中的DLL也会)(参见note)
-o DIR, –out=DIR 指定spec文件的生成目录,如果没有指定,而且当前目录是PyInstaller的根目录,会自动创建一个用于输出(spec和生成的可执行文件)的目录.如果没有指定,而当前目录不是PyInstaller的根目录,则会输出到当前的目录下.
-p DIR, –path=DIR 设置导入路径(和使用PYTHONPATH效果相似).可以用路径分割符(Windows使用分号,Linux使用冒号)分割,指定多个目录.也可以使用多个-p参数来设置多个导入路径
–icon=<FILE.ICO> 将file.ico添加为可执行文件的资源(只对Windows系统有效)
–icon=<FILE.EXE,N> 将file.exe的第n个图标添加为可执行文件的资源(只对Windows系统有效)
-v FILE, –version=FILE 将verfile作为可执行文件的版本资源(只对Windows系统有效)
-n NAME, –name=NAME 可选的项目(产生的spec的)名字.如果省略,第一个脚本的主文件名将作为spec的名字
————————————————
版权声明:本文为CSDN博主「jirryzhang」的原创文章
原文链接:https://blog.csdn.net/jirryzhang/article/details/78881512

pyinstaller -F -i icon.ico -w sadf.py

可持久化线段树【主席树】可持久化并查集【主席树+并查集】.md

说明 - 2022-05-05

本篇博客为本人原创, 原发布于CSDN, 在搭建个人博客后使用爬虫批量爬取并挂到个人博客, 出于一些技术原因博客未能完全还原到初始版本(而且我懒得修改), 在观看体验上会有一些瑕疵 ,若有需求会发布重制版总结性新博客。发布时间统一定为1111年11月11日。钦此。

笼统的主席树原理

众所周知, 主席树是可以持久化的, 换言之你能知道你所维护信息的所有历史状态。 主席树是这样做的:

1.

首先建一颗朴素的线段树,代表初始状态 (下图黑色) , 也就是第0次操作后的状态。

tipA:

你每次只对一个叶子节点的数据进行更新,所以相当于更改了树上的一条链。

2.

我们不在原来的树上修改,而是创建若干个新的节点组成一条链代表树中修改后的各个节点 (下图红色) ,然后直接把这条链糊到树上,
并且让新链中的每个节点都连好它在树中应该连的节点 (下图蓝色)

tipB:

你会发现只要交换新旧链, 就可以得到两颗完整的树

tipC:

你还会发现:在每次更新中添加的新链,都会包含树的根节点,所以你只需要记录下第[0 - N]次操作后的树的根节点,就可以通过某一个根节点得到特定历史版本的树。
(0次操作后的根节点是黑色树根, 1次操作后的根节点是红色树根)

3.

然 后 随 便 van van , 主 席 树 就 学 完 了。

4.

然后我们就可以滚到别的题解上爬了
![在这里插入图片描述](https://img-
blog.csdnimg.cn/e988fd004312427286dd76e4a36b37e5.png?x-oss-
process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBAU3RhcnNXaGlzcGVy,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)

主席树 luogu.com.cn 的主席树板子

纯区间第K大
区间[r - (l - 1)] 相当于只插入了[l - 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
#include <bits/stdc++.h>

const int N = 2e5+4;
int lst[N]/*原序列*/, srt[N]/*排序数组*/, root[N]/*记录树根*/;
int sum[N<<5], L[N<<5], R[N<<5];
int n, m, ql, qr, k, idx, sn;

int build(int l, int r){
int rt = ++idx, mid = (l + r) >> 1;
if(l < r){
L[rt] = build(l, mid);
R[rt] = build(mid + 1, r);
}
return rt;
}

int update(int pre, int l, int r, int k){
int rt = ++idx, mid = (l + r) >> 1;
L[rt] = L[pre], R[rt] = R[pre], sum[rt] = sum[pre] + 1;
if(l < r){
if(k <= mid) L[rt] = update(L[pre], l, mid, k);
else R[rt] = update(R[pre], mid + 1, r, k);
}
return rt;
}

int query(int ql, int qr, int l, int r, int k){
if( l >= r) return l;
int num = sum[L[qr]] - sum[L[ql]], mid = (l + r) >> 1;
if(num >= k) return query(L[ql], L[qr], l, mid, k);
else return query(R[ql], R[qr], mid + 1, r, k - num);
}

int main(){
scanf("%d%d", &n, &m);
for(int i=1; i<=n; i++){
scanf("%d", &lst[i]);
srt[i] = lst[i];
}
std::sort(srt + 1, srt + 1 + n);
sn = std::unique(srt + 1, srt + 1 + n) - srt - 1;
root[0] = build(1, sn);
for(int i=1; i<=n; i++){
k = std::lower_bound(srt + 1, srt + 1 + sn, lst[i]) - srt;
root[i] = update(root[i - 1], 1, sn, k);
}
while(m --){
scanf("%d%d%d", &ql, &qr, &k);
n = query(root[ql - 1], root[qr], 1, sn, k);
printf("%d\n", srt[n]);
}
return 0;
}

主席树+并查集 luogu.com.cn 的可持久化并查集板子

纯并查集

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
#include <bits/stdc++.h>

const int N = 1e5 + 4, M = 2e5 + 4;
int n, q, idx, x, y, op, xx, yy;
int D[M << 5], F[M << 5], L[M << 5], R[M << 5], E[M];

int build(int l, int r){
int rt = ++idx;
if(l == r) { F[rt] = l, D[rt] = 1; return rt; }
int mid = (l + r) >> 1;
L[rt] = build(l, mid);
R[rt] = build(mid + 1, r);
return rt;
}

int update(int pre, int l, int r, int k, int f){
int rt = ++idx;
if(l == r){
D[rt] = D[pre];
F[rt] = f;
return rt;
}
L[rt] = L[pre], R[rt] = R[pre];
int mid = (l + r) >> 1;
if(k <= mid) L[rt] = update(L[pre], l, mid, k , f);
else R[rt] = update(R[pre], mid + 1, r, k, f);
return rt;
}

int query(int rt, int l, int r, int k){
if(l == r) return rt;
int mid = (l + r) >> 1;
if(k <= mid) return query(L[rt], l, mid, k);
return query(R[rt], mid + 1, r, k);
}

void cd(int rt, int l, int r, int k){
if(l == r) {D[rt] += 1; return ;}
int mid = (l + r) >> 1;
if(k <= mid) cd(L[rt], l, mid, k);
else cd(R[rt], mid + 1, r, k);
}

int ffind(int rt, int x){
int t = query(rt, 1, n, x);
if(x == F[t]) return t;
return ffind(rt, F[t]);
}

int main(){
scanf("%d%d", &n, &q);
E[0] = build(1, n);
for(int i=1; i<=q; i++){
scanf("%d", &op);
if(op == 1){
scanf("%d%d", &x, &y);
E[i] = E[i - 1];
xx = ffind(E[i], x), yy = ffind(E[i], y);
if(D[xx] > D[yy]) std::swap(xx, yy);
E[i] = update(E[i - 1], 1, n, F[xx], F[yy]);
if(D[xx] + 1 > D[yy]) cd(E[i], 1, n, F[yy]);
}
if(op == 2){
scanf("%d", &x);
E[i] = E[x];
}
if(op == 3){
scanf("%d%d", &x, &y);
E[i] = E[i - 1];
xx = ffind(E[i], x), yy = ffind(E[i], y);
printf("%d\n", F[xx] == F[yy] ? 1 : 0);

}
}
return 0;
}

学习在ECS上实现定期执行在学校官网发帖子签到的py脚本.md

说明 - 2022-05-05

本篇博客为本人原创, 原发布于CSDN, 在搭建个人博客后使用爬虫批量爬取并挂到个人博客, 出于一些技术原因博客未能完全还原到初始版本(而且我懒得修改), 在观看体验上会有一些瑕疵 ,若有需求会发布重制版总结性新博客。发布时间统一定为1111年11月11日。钦此。

本文不作为较专业的资料,仅为萌新不专业的试水和踩坑

1.编写签到脚本

从这个环节开始我就遇到了许多问题,比如可以复制cookie实现签到,但不会获取cooike
实际上直到现在写博客,我始终没有做到完整地吧所需要的cookie提取出来,对于爬虫也是一知半解

直接上误打误撞写出来的拉跨代码(部分内容隐蔽或更改):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests
import time

url='在学校官网登录请求的url'
header={
'Referer': 'secret',
'User-Agent': 'mystery',
'Content-Type': 'application/x-www-form-urlencoded'
}
data={
'IPT_LOGINUSERNAME': 'what?',
'IPT_LOGINPASSWORD': 'you don\'t know'
}

session=requests.session()
res=session.post(url,headers=header,data=data)

posturl='发帖子请求的url'

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
headersx={
'Content-Type': 'application/x-www-form-urlencoded',
'Referer': 'mytsery',
'User-Agent': 'jsadsadfsadfasd',
}

datax={
'opt': 'toreply',
'frompage': 'common',
'forumid':'3743',
'threadid':'114775',
'parentid':'239973',
'type': '0',
'subject':'帖子标题的一些内容'.encode("gbk"),
'body': '<p>学号 某某签到</p>'.encode("gbk")#编码格式一定要换,否则发出乱码

}
resx=session.post(posturl,headers=headersx,data=datax)

#相当于一个日志
with open('journal.txt','a') as f:#这是在本地写的,放在服务器上要使用绝对路径,否则会在根目录创建文件
string=''
tm=time.localtime()
for i in range(6):
string+=str(tm[i])+' '
else:
string+='OVER \n'
f.write(string)

2.在ECS上令其定时执行

我的云服务器装的乌班图系统

先通过以下操作测试了一下py文件是否能在云服务器上运行,且运行成功

python3 签到文件

然后通过第一行的操作打开控制定时执行的文件,添加内容,最后两行是我新添加的内容(排版不是很好,服务器上看是很清晰的)。
关于怎样设置执行时间见传送门:https://www.runoob.com/linux/linux-comm-crontab.html

vi /etc/corntab

m h dom mon dow user command

17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report
/etc/cron.daily )
47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report
/etc/cron.weekly )
52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report
/etc/cron.monthly )
0 14 * * 3 root /usr/bin/python3.6 /home/signIn195C/login195C.py
0 10 * * 5 root /usr/bin/python3.6 /home/signIn195C/login195C.py

注意

python脚本若要建立(或写入)文件一定要使用绝对路径。笔者手动执行程序时会在脚本所在的目录建立(或写入)文件,但自动执行时文件会在根目录建立(或写入)