0%

题目大意 给你n种砖块,问最高能垒多高,要求上层砖块严格小于下层,高度无限制

一开始想的简单了一点,因为n非常小(30以内),就在输入的时候遍历一下判断当前砖块能不能放在之前的砖块上面或者下面,有的话建一条边,最后从每个位置试一下所有边的情况遍历一下就出来了

然后试样例时候一拍脑袋想起来。。砖块可以转。。实际相当于一块顶六块。。然后重新改了改思路还是一致的,就是每次输入相当于输入六个。。

提交之后emmmm TLE( 好吧 可能是多组输入这个太暴力了导致的。。

毕竟是DP的题目嘛。。就想想怎么用DP解决。。后来仔细思考一下 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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
vector<int> G[200];
struct block{
int x,y,z;
int maxhei;
}blocks[200];
bool checkblock(int i,int j){
return (blocks[i].x<blocks[j].x)&&(blocks[i].y<blocks[j].y);
}
int findlongest(int t){
if(blocks[t].maxhei){
return blocks[t].maxhei;
}
int res=blocks[t].z;
int tmp=0;
for(int i=0;i<G[t].size();++i){
tmp=max(tmp,findlongest(G[t][i]));
}
blocks[t].maxhei=res+tmp;
return res+tmp;
}
int main(){
int n;
int T=1;
while(scanf("%d",&n),n){
for(int i=0;i<n;++i){
int x[3];
scanf("%d%d%d",&x[0],&x[1],&x[2]);
for(int t=0;t<6;++t){
G[i*6+t].clear();
blocks[i*6+t].maxhei=0;
}
blocks[i*6].x=x[0],blocks[i*6].y=x[1],blocks[i*6].z=x[2];
blocks[i*6+1].x=x[0],blocks[i*6+1].y=x[2],blocks[i*6+1].z=x[1];
blocks[i*6+2].x=x[1],blocks[i*6+2].y=x[2],blocks[i*6+2].z=x[0];
blocks[i*6+3].x=x[1],blocks[i*6+3].y=x[0],blocks[i*6+3].z=x[2];
blocks[i*6+4].x=x[2],blocks[i*6+4].y=x[0],blocks[i*6+4].z=x[1];
blocks[i*6+5].x=x[2],blocks[i*6+5].y=x[1],blocks[i*6+5].z=x[0];
for(int t=0;t<6;++t){
for(int j=i*6+t-1;j>=0;--j){
if(checkblock(i*6+t,j)){
G[j].push_back(i*6+t);
}
if(checkblock(j,i*6+t)){
G[i*6+t].push_back(j);
}
}
}
}
int res=0;
for(int i=0;i<n*6;++i){
res=max(res,findlongest(i));
}
printf("Case %d: maximum height = %d\n",T,res);
++T;
}
return 0;
}

虽然归类归到DP里了,但是这道题emmmmm确实没有怎么用dp的思路

题意说给你奇数个数,里面会有一个数出现至少(n+1)/2次,问你这个数是啥

实际上有这个性质很好求,我们只需要统计当前遇到的数,看它出现了几次,一旦遇到别的数出现次数-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
#include<cstdio>
#include<algorithm>
using namespace std;
int main(){
int n;
while(~scanf("%d",&n)){
int count=0,number=0;
for(int i=0;i<n;++i){
int tmp;
scanf("%d",&tmp);
if(number!=tmp){
if(count<=1){
number=tmp;
}else{
--count;
}
}else{
++count;
}
}
printf("%d\n",number);
}
return 0;
}

重新开始慢慢写题作为练习,考虑到我本身在动态规划的算法上面比较弱,先从dp开始练起

第一道题就emmmm没想得特别明白其实 题目大意是给n个数,要你选择m段互补相交的段让它们的和最大

dp[i][j]表示前j个数里分成i段的话最大值是多少,这里有一个隐含的意思是此时第j个数一定被分到了第i段里,那么更新公式有$dp[i][j]=max(dp[i][j-1]+a[j],max(dp[i-1][k]+a[j])(0\leq k\leq j-1))$,第一种表示在第i段最后添加第j个数,第二种表示找到前i-1段的情况下最大值而后将第j个数作为新的一段的开始,但是如果每次我们都要搜一遍前j个数来找最大值的话显然复杂度是会爆掉的,所以我们需要想想办法,实际上,在更新的过程中,我们实际上就隐含的确定了$max(dp[i][k]+a[j])(0\leq k\leq j)=dp[i][j]$,所以我们本轮更新的结果是可以记录下来到下一轮来使用的,这样可以把时间复杂度缩到O(mn),空间上实际上dp[i][j]只与上一轮相关,所以不需要存如此多的轮次,只需要存一个轮次而后渐次更新即可。

++
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
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int dp[1000030],number[1000030],maxm[1000030];
const int INF=0x3fffffff;
int main(){
int m,n;
while(~scanf("%d%d",&m,&n)){
memset(dp,0,sizeof(dp));
memset(maxm,0,sizeof(maxm));
for(int i=1;i<=n;++i){
scanf("%d",&number[i]);
}
int lastmaxm=-INF;
for(int i=1;i<=m;++i){
lastmaxm=-INF;
for(int j=i;j<=n;++j){
dp[j]=max(dp[j-1]+number[j],maxm[j-1]+number[j]);
maxm[j-1]=lastmaxm;
lastmaxm=max(lastmaxm,dp[j]);
}
}
printf("%d\n",lastmaxm);
}
return 0;
}

又有一段时间没写东西了嗷 有点忙 之前找了在字节的实习 现在是一边实习一边上课 得亏是疫情期间我还不需要来回跑 不然大概会更麻烦。。

在字节待了一段时间了 福利相当不错 真的不错 现在做的是开发岗位 是给我了挺多收获的

5/3更新

忙了好几个月 终于稍微闲一点了 实习的工作基本结束了 后面得准备复习 保研的事项 具体要写点啥我想想吧

思路简单 直接反转 重点要看 是否超限

++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
* @lc app=leetcode.cn id=7 lang=cpp
*
* [7] 整数反转
*/

// @lc code=start
class Solution {
public:
int reverse(int x) {
long long intmax=(1<<31)-1,intmin=(1<<31)*(-1);
long long ant=x,res=0;
while(ant!=0){
res*=10;
res+=ant%10;
ant/=10;
}
if(res<intmin||res>intmax){
return 0;
}
return res;
}
};
// @lc code=end

一道很有意思的题目 如果不是出在网络流的套题里面我很可能就往精确覆盖问题去想然后用dlx来搞了..?

题目意思是给你一个n*m的方格图 白格里填1-9的数字是横向和纵向之和都满足给的约束条件 既然在网络流套题里就按照网络流的思路来想呗
一开始想的还有些问题 第一次跑出来全是9吓得我仔细寻找了一下是不是模板又改错了…

正确的建图思路大概是 每个纵列或横行的约束看作一个点 每个白格看作一个点 源点连横行约束 容量是给定容量(这里为了好理解暂定这样 后面为了适应条件会做小小的改动)
纵列约束连汇点 容量也是给定容量 横约束连它控制的白格 容量是9 白格再连它所属的纵约束 容量一样 这样跑一遍最大流 观察经过每个白格的流量就是应该填的数字(注意是流量而不是剩余流量)

这样还会遇到一个问题 就是会有流量为0的边出现 仔细看条件的话会发现白格是不允许填0的 所以我们需要限制一下边的下界 但是仔细思考一下 通过一些小的改变 我们实际避免这个问题 每个白格一定至少需要流过1的流量(原题未提无解情况 理解为默认有解) 那么我们可以预先保证每个白格流过1但是不在图中计算 这样原来容量为9的变成容量为8,约束的容量减掉它控制的白格数量 在最后计算流量的时候再加上1即可

在这里为了方便检查流量 我将白格拆点并编号 保证与编号一致的边正好是我们需要检查的边 来方便检查流量以确定结果

AC代码

++
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#include <cstdio>
#include <vector>
#include <cstring>
#include<algorithm>
#include <queue>
using namespace std;
const int MAXV = 20050;
const int maxm=100050;
const int INF = 1<<30;
int edge;
int level[MAXV];
int iter[MAXV]; //当前弧,之前的已经没有用了
int head[MAXV];
struct edgenode
{
int from;
int to;//边的指向
int cap;//边的容量
int next;//链表的下一条边
} edges[maxm];
void init()
{
edge=0;
memset(head,-1,sizeof(head));
}
void addedge(int from, int to, int cap) {
edges[edge].from=from;
edges[edge].cap=cap;
edges[edge].to=to;
edges[edge].next=head[from];
head[from]=edge++;
edges[edge].from=to;
edges[edge].cap=0;
edges[edge].to=from;
edges[edge].next=head[to];
head[to]=edge++;
}

void bfs(int s) {
memset(level, -1, sizeof level);
std::queue<int> que;
level[s] = 0;
que.push(s);
while (!que.empty()) {
int v = que.front(); que.pop();
for (int i = head[v]; i !=-1; i=edges[i].next) {
edgenode &e = edges[i];
if (e.cap > 0 && level[e.to] < 0) {
level[e.to] = level[v] + 1;
que.push(e.to);
}
}
}
}

int dfs(int v, int t, int f) {
if (v == t) return f;
if(iter[v]==0)
{
iter[v]=head[v];
}
for (int &i = iter[v]; i !=-1; i=edges[i].next) { // 注意i是引用 实现当前弧优化
edgenode &e = edges[i];
if (e.cap > 0 && level[v] < level[e.to]) {
int d = dfs(e.to, t, std::min(f, e.cap));
if (d > 0) {
e.cap -= d;
edges[i^1].cap += d;
return d;
}
}
}
return 0;
}

int maxflow(int s, int t) {
int flow = 0;
for (; ;) {
bfs(s);
if (level[t] < 0) return flow;
memset(iter, 0, sizeof iter);
int f;
while ((f = dfs(s, t, INF)) > 0) {
flow += f;
}
}
return flow;
}
int row[120][120],col[120][120],name[120][120],pos;
int todigit(char *dig)
{
return (dig[0]-'0')*100+(dig[1]-'0')*10+(dig[2]-'0');
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m))
{
memset(row,-1,sizeof(row));
memset(col,-1,sizeof(col));
memset(name,-1,sizeof(name));
init();
pos=0;
char item[10];
for(int i=0;i<n;++i)
{
for(int j=0;j<m;++j)
{
scanf("%s",item);
if(item[0]=='.')
{
name[i][j]=pos;
addedge(pos*2,pos*2+1,8);
pos++;
}
else
{
if(item[0]!='X')
{
col[i][j]=todigit(&item[0]);
}
if(item[4]!='X')
{
row[i][j]=todigit(&item[4]);
}
}
}
}
int start=pos*2,end=pos*2+1,cons=pos*2+2;
for(int i=0;i<n;++i)
{
for(int j=0;j<m;++j)
{
if(row[i][j]!=-1)
{
int cotmp=j+1,num=0;
while(cotmp<m&&name[i][cotmp]!=-1)
{
addedge(cons,name[i][cotmp]*2,INF);
++cotmp,++num;
}
addedge(start,cons,row[i][j]-num);
++cons;
}
if(col[i][j]!=-1)
{
int rotmp=i+1,num=0;
while(rotmp<n&&name[rotmp][j]!=-1)
{
addedge(name[rotmp][j]*2+1,cons,INF);
++rotmp,++num;
}
addedge(cons,end,col[i][j]-num);
++cons;
}
}
}
int ans=maxflow(start,end);
for(int i=0;i<n;++i)
{
for(int j=0;j<m;++j)
{
if(name[i][j]==-1)
{
printf("_ ");
}
else
{
printf("%d ",9-edges[name[i][j]*2].cap);
}
}
printf("\n");
}
}
return 0;
}

n个城市m条边 每条边都有一定的费用 让你选出最小的费用来将两座城市划成两部分

很典型的最小割问题 只是输出的要求是割边而不是花费 为了方便我这次用的模板是上一道题我魔改过的版本 求边时候比较好求

正好模板每次都会跑bfs 当发现已经切分成两部分返回结果 那么此时bfs的结果正好可以拿来利用 我们只需要对每条边 看一下两侧是不是属于两个集合的 若是那么这条边一定是割边了 输出即可

哦有一个问题是给的是双向边 所以建图时候需要双向建 输出结果时候记得去重或者干脆只考虑一个方向的即可

代码(交的时候VJ上UVA炸了…等下次不炸的时候看看有没有别的问题)好了过了

++
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
#include <cstdio>
#include <vector>
#include <cstring>
#include<algorithm>
#include <queue>
using namespace std;
const int MAXV = 1050;
const int maxm=10050;
const int INF = 1<<30;
int edge;
int level[MAXV];
int iter[MAXV]; //当前弧,之前的已经没有用了
int head[MAXV];
struct edgenode
{
int from;
int to;//边的指向
int cap;//边的容量
int next;//链表的下一条边
} edges[maxm];
void init()
{
edge=0;
memset(head,-1,sizeof(head));
}
void addedge(int from, int to, int cap) {
edges[edge].from=from;
edges[edge].cap=cap;
edges[edge].to=to;
edges[edge].next=head[from];
head[from]=edge++;
edges[edge].from=to;
edges[edge].cap=0;
edges[edge].to=from;
edges[edge].next=head[to];
head[to]=edge++;
}

void bfs(int s) {
memset(level, -1, sizeof level);
std::queue<int> que;
level[s] = 0;
que.push(s);
while (!que.empty()) {
int v = que.front(); que.pop();
for (int i = head[v]; i !=-1; i=edges[i].next) {
edgenode &e = edges[i];
if (e.cap > 0 && level[e.to] < 0) {
level[e.to] = level[v] + 1;
que.push(e.to);
}
}
}
}

int dfs(int v, int t, int f) {
if (v == t) return f;
if(iter[v]==0)
{
iter[v]=head[v];
}
for (int &i = iter[v]; i !=-1; i=edges[i].next) { // 注意i是引用 实现当前弧优化
edgenode &e = edges[i];
if (e.cap > 0 && level[v] < level[e.to]) {
int d = dfs(e.to, t, std::min(f, e.cap));
if (d > 0) {
e.cap -= d;
edges[i^1].cap += d;
return d;
}
}
}
return 0;
}

int maxflow(int s, int t) {
int flow = 0;
for (; ;) {
bfs(s);
if (level[t] < 0) return flow;
memset(iter, 0, sizeof iter);
int f;
while ((f = dfs(s, t, INF)) > 0) {
flow += f;
}
}
return flow;
}
int main()
{
int n,m;
while(~scanf("%d%d",&n,&m),n+m)
{
init();
for(int i=0;i<m;++i)
{
int u,v,cap;
scanf("%d%d%d",&u,&v,&cap);
addedge(u,v,cap);
addedge(v,u,cap);
}
int res=maxflow(1,2);
for(int i=0;i<edge;i+=2)
{
if(level[edges[i].from]!=-1&&level[edges[i].to]==-1)
{
printf("%d %d\n",edges[i].from,edges[i].to);
}
}
printf("\n");
}
return 0;
}

N个城市M条边 有恐怖分子从S到T都已知 在每个城市布防花费不同 问能够保证阻拦恐怖分子的最小花费

乍看之下以为是最小费用流 但是仔细思考怎么来让一个城市布防能够影响到其他城市 想来想去越来越觉得 这不是个最小割问题吗

城市拆成两个点 连边容量是花费 城市之间的高速公路容量无穷 按照给的起点和终点跑一遍最大流即可得解

AC代码

++
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
#include <cstdio>
#include <vector>
#include <cstring>
#include <queue>
using namespace std;
const int MAXV = 2005;
const int INF = 1<<30;
struct Edge{ int to, cap, rev; };
std::vector<Edge> G[MAXV];
int level[MAXV];
int iter[MAXV]; //当前弧,之前的已经没有用了

void addedge(int from, int to, int cap) {
G[from].push_back(Edge{to, cap, G[to].size()});
G[to].push_back(Edge{from, 0, G[from].size()-1});
}

void bfs(int s) {
memset(level, -1, sizeof level);
std::queue<int> que;
level[s] = 0;
que.push(s);
while (!que.empty()) {
int v = que.front(); que.pop();
for (int i = 0; i < G[v].size(); ++i) {
Edge &e = G[v][i];
if (e.cap > 0 && level[e.to] < 0) {
level[e.to] = level[v] + 1;
que.push(e.to);
}
}
}
}

int dfs(int v, int t, int f) {
if (v == t) return f;
for (int &i = iter[v]; i < G[v].size(); ++i) { // 注意i是引用 实现当前弧优化
Edge &e = G[v][i];
if (e.cap > 0 && level[v] < level[e.to]) {
int d = dfs(e.to, t, std::min(f, e.cap));
if (d > 0) {
e.cap -= d;
G[e.to][e.rev].cap += d;
return d;
}
}
}
return 0;
}

int maxflow(int s, int t) {
int flow = 0;
for (; ;) {
bfs(s);
if (level[t] < 0) return flow;
memset(iter, 0, sizeof iter);
int f;
while ((f = dfs(s, t, INF)) > 0) {
flow += f;
}
}
return flow;
}
int main()
{
int N,M;
while(~scanf("%d%d",&N,&M))
{
for(int i=0;i<N*2;++i)
{
G[i].clear();
}
int start,end;
scanf("%d%d",&start,&end);
--start,--end;
for(int i=0;i<N;++i)
{
int cap;
scanf("%d",&cap);
addedge(i*2,i*2+1,cap);
}
for(int i=0;i<M;++i)
{
int u,v;
scanf("%d%d",&u,&v);
u--,v--;
addedge(u*2+1,v*2,INF);
addedge(v*2+1,u*2,INF);
}
int res=maxflow(start*2,end*2+1);
printf("%d\n",res);
}
return 0;
}

题意有些难理解 需要结合样例来理解 当然很直观的思路就是先这么排布开直接按照公式一行行的拼凑结果字符串
我一开始也是这么想的…看了官方的思路发现还是自己太蠢 实际上这很像一个上下来回扫描的过程 给的字符串逐个向后 填充的字符串来回扫描即可

ps:要注意给的列是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
/*
* @lc app=leetcode.cn id=6 lang=cpp
*
* [6] Z 字形变换
*/
#include<string>
using namespace std;
// @lc code=start
class Solution {
public:
string convert(string s, int numRows) {
if(numRows==1){
return s;
}
string *res=new string[numRows];
int index=0,curlin=0,dir=0;
while(index<s.length()){
res[curlin]+=s[index++];
if(dir){
--curlin;
if(curlin==0){
dir=0;
}
}
else{
++curlin;
if(curlin==numRows-1){
dir=1;
}
}
}
string ans;
for(int i=0;i<numRows;++i){
ans+=res[i];
}
return ans;
}
};
// @lc code=end

也是蛮标准的题目了 写了一个dp的O($n^2$)的解法 马拉车的O(n)解法回头有时间来学习吧…

哦空串要单独判断…因为似乎substr对空串即使取0长度也会出问题…

++
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
/*
* @lc app=leetcode.cn id=5 lang=cpp
*
* [5] 最长回文子串
*/
#include<string>
#include<cstring>
using namespace std;
// @lc code=start

class Solution {
public:
string longestPalindrome(string s) {
if(s.empty())
{
return string();
}
int dp[1020][1020],ans=0,start=0;
memset(dp,0,sizeof(dp));
for(int i=0;i<s.length();++i){
dp[i][i]=1;
ans=1;
}
for(int i=0;i<s.length()-1;++i){
if(s[i]==s[i+1]){
dp[i][i+1]=1;
ans=2,start=i;
}
}
for(int i=2;i<s.length();++i){
for(int j=0;j+i<s.length();++j){
if(dp[j+1][j+i-1]&&s[j]==s[j+i]){
dp[j][j+i]=1;
ans=i+1,start=j;
}
}
}
return s.substr(start,ans);
}
};
// @lc code=end