0%

这场全是数学题啊。。

A

题意

给你一个数n,对它进行k次操作,问结果是多少 每次操作n加上它用十进制表示的每一位最小值乘最大值

题解

给的n和k非常大,乍看起来以为是要什么公式来做。。但实际运算了一下 很容易看到如果结果包含一个0 那么每次操作之后数其实都不会变了 因为最小值0 自己尝试一下就会发现 这种运算很容易出现含0的结果 当然最后题解证明了一下81次内一定可以 我们写的时候就猜就好了。

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
#include<cstdio>
#include<algorithm>
using namespace std;
int maxdig,mindig;
void finddig(long long res){
maxdig=0,mindig=9;
while(res){
int tmp=res%10;
maxdig=max(maxdig,tmp);
mindig=min(mindig,tmp);
if(mindig==0){
return;
}
res/=10;
}
}
int main(){
int T;
scanf("%d",&T);
while(T--){
long long a,k;
scanf("%lld%lld",&a,&k);
for(long long i=1;i<k;++i){
finddig(a);
if(mindig==0){
break;
}
a+=(maxdig*mindig);
}
printf("%lld\n",a);
}
return 0;
}

B

题意

n个人取野营 每个人有一个参数e 对这个人必须e个人以上组队才可以成一组 问最多分几组 允许有人没有组

题解

和之前有一轮老奶奶的题很像。。很明显我们这个要尽可能少人组队 因为要求是最多组 所以实际上就是只要可能就组队 把人数排个序然后依次计算只要人够了就组队就成

还有一个问题是需要稍稍优化一下。。一开始整个遍历tle了。。如果剩余人数已经比当前的系数小那么剩下的人肯定组不了队直接结束即可 截掉之后瞬间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
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int coun[200010];
int main(){
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
int tmp;
for(int i=0;i<n;++i){
scanf("%d",&coun[i]);
}
sort(coun,coun+n);
int res=0,remai=0,last=0;
for(int i=0;i<n;++i){
if(coun[i]!=last){
if(last!=0){
res+=remai/last;
remai=remai%last;
}
last=coun[i];
}
if((n-i+remai)<last){
break;
}
++remai;
}
res+=remai/last;
printf("%d\n",res);
}
return 0;
}

C

题意

给四个数A,B,C,D 四个数划分出中间的三个闭区间 从三个区间分别取值作为一条边,能拼成三角形的有几种

题解

显然这种取值方法我们不需要考虑重复的问题 因为不可能会出现第一个数比第二个数大的情况 然后分两个问题来考虑

组成三角形的条件我们都很清楚 检验两边之和是不是大于第三边即可 对于某一个固定的两小边之和 我们可以直接得出第三边可能的取值 而每一个固定的两小边之和会出现几次是我们要考虑的下一个问题

仔细思考一下可以看出 两小边取值的区间从最低值到最高值是连续的 而且是类似 1 2 3 3 3 2 1 这样的递增再递减序列 它的规律是递增的最大值只能取到两个小边区间长度的小值,而递增的长度是两个小边区间长度的大值,如果不能递增就是维持不变 而后递减至1 这样我们就有了规律 结合起来就能够算出答案

有一个问题要注意的是 题目给的范围实际上在某一个两小边值得次数乘上第三边可能得取值这一次乘法就会爆int。。所以一开始wa了一次。。后面全改longlong就没事了。。

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
#include<cstdio>
#include<algorithm>
using namespace std;
int main(){
long long a,b,c,d;
scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
long long x=b-a+1,y=c-b+1,z=d-c+1;
long long minnum=min(x,y),maxnum=max(x,y);
long long maxval=d;
long long res=0;
long long tmpnum=0,tmpval=a+b;
for(long long i=0;i<maxnum;++i){
if(tmpnum<minnum){
++tmpnum;
}
if(tmpval>maxval){
long long tmp=z*tmpnum;
res+=tmp;
}else{
long long tmp=tmpnum*max(0LL,tmpval-c);
res+=tmp;
}
++tmpval;
}
while(tmpnum){
--tmpnum;
if(tmpval>maxval){
long long tmp=z*tmpnum;
res+=tmp;
}else{
long long tmp=tmpnum*max(0LL,tmpval-c);
res+=tmp;
}
++tmpval;
}
printf("%lld\n",res);
return 0;
}

D

题意

给n和s 要你想办法给n个正整数其和为s 并给出一个其中的数k 如果对方不能够从这n个数中挑出一部分令其和为k或s-k 那么你就赢了 问对于给定的n和s有没有必胜策略

题解

严谨的证明一样需要看题解。。做题的时候我的思路大概是这样想的 如果s<2*n 那么我们选的序列n一定会带有元素1,而且这种时候无论怎么选都会让每一种和都能得到 而s>=2*n的时候 我们可以让n-1个元素都是2 最后一个元素是剩下的值也是大于等于2的 那么这时候无论如何都拼不出1来 就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<cstdio>
#include<algorithm>
using namespace std;
int main(){
int n,s;
scanf("%d%d",&n,&s);
if(n*2>s){
printf("NO\n");
}else{
printf("YES\n");
for(int i=1;i<n;++i){
printf("2 ");
}
printf("%d\n1\n",s-(n-1)*2);
}
return 0;
}

E

题意

n个墙 我们想要让墙的高度一致 可以有三种操作 加一个砖块 减一个砖块 挪一个砖块 有分别的花费 问最小的花费

题解

我看到这题的时候已经没时间了。。看来一下题解 大致思路倒是很简单 假设我们想要达到的高度是已知的 我们只需要看差的高度和以及多的高度和即可 然后一部分可以移动另一部分需要加或减 这样我们统计过前缀和之后用二分查找可以logn时间确定花费 遍历可能的高度即可

以及一个小问题 挪砖块的花费要先处理成挪或者是加砖块减砖块花费之和的最小值 也很好理解就不多说了

隔了一段时间来做。。感觉又变菜了。。

A

题意

给定n,找到0到n中任意两个数的gcd最大值

题解

最大的gcd很显然就是n和n/2的gcd,看好奇偶性就好了

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<cstdio>
#include<algorithm>
using namespace std;
int main(){
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
printf("%d\n",n/2);
}
return 0;
}

B

题意

给2n个数 挑n-1对 让每一对的和作为一个元素 所有元素的gcd不为1

题解

乍看有点唬人 实际一想 每个元素都是偶数就可以解决问题了 所以就一直挑奇数挑到剩0个或1个或已经够n-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
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
int main(){
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
vector<int> cou[2];
for(int i=0;i<n*2;++i){
int tmp;
scanf("%d",&tmp);
cou[tmp%2].push_back(i+1);
}
int num=0;
for(int i=0;(i+1)<cou[1].size()&&num<(n-1);i+=2){
printf("%d %d\n",cou[1][i],cou[1][i+1]);
++num;
}
for(int i=0;num<(n-1);i+=2){
printf("%d %d\n",cou[0][i],cou[0][i+1]);
++num;
}
}
return 0;
}

C

题意

俩人玩游戏 从n开始 两种操作 减一或者除这个数的任意一个大于一的奇数因子 谁不能动了就输

题解

看了一下题解 我的思路是没问题的。。然而就是wa也不知道为啥。。

可以看出 1是必败态 2是必胜态 所有的奇数都是必胜态 因为可以除以自身 偶数里所有不含奇数因子的除2以外都是必败态 也就是2的n次方形式 因为只能减一操作 转移到必胜态 所以是必败态 而任何带有奇数因子且因子中2的个数大于1的都是必胜态 也很简单 直接除以所有奇数因子的积转移到必败态 形式为2*x x为一个质数的是必败态 因为两种操作都转移到必胜态 形式为2*x*x。。。的为必胜态 除到之前说的形式也就是一步转移能到必败态

然而不知道哪里写错了。。。好气哦

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<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
int prime[1000020];
vector<int> fpri;
void pre(){
prime[0]=prime[1]=1;
for(int i=2;i<1000000;++i){
if(!prime[i]){
for(int j=i*2;j<1000000;j+=i){
prime[j]=1;
}
}
}
for(int i=2;i<1000000;++i){
if(!prime[i]){
fpri.push_back(i);
}
}
}
int main(){
int T;
pre();
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
int res=0;
if(n==1){
res=1;
}else if(n==2){
res=0;
}else if(n&1){
res=0;
}else{
int tmp=n/2;
if(tmp%2){
for(int i=0;i<fpri.size();++i){
if(tmp%fpri[i]==0){
if(tmp/fpri[i]==1){
res=1;
}
break;
}
}
}else{
while(tmp%2==0){
tmp/=2;
}
if(tmp==1){
res=1;
}
}
}
if(res){
printf("FastestFinger\n");
}else{
printf("Ashishgup\n");
}
}
return 0;
}

D

题意

。。困 懒得看了 就这样吧先。。

A

题意

n*m的矩阵 可以在任意一条边上放灯 灯可以照到边连着的小矩形 问最少需要几盏灯能点亮整个矩阵

题解

实际上推理一下就可以看出来了 每两个小矩形需要一盏灯 如果最后是单个的还需要一盏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int main(){
int T;
scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
int ans=n*m/2;
if((n*m)%2){
++ans;
}
printf("%d\n",ans);
}
return 0;
}

B

题意

有n+1个granny 一开始只有一个granny在场地 她可以一次叫任意个granny 但每个granny有自己的人数要求 如果她来的时候人数不达标她就会走 人数包括同时喊来的其他人但不包括她自己 问最多能聚集几个granny

题解

也很简单 按照要求聚集人数来存 然后逐个求sum 如果sum[i]>i那么就说明是能够达到这个数字的 求满足条件的最大的sum[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
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gran[200020],su[200020];
int main(){
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
int tmp;
memset(gran,0,sizeof(gran));
memset(su,0,sizeof(su));
gran[0]=su[0]=1;
for(int i=0;i<n;++i){
scanf("%d",&tmp);
++gran[tmp];
}
int ans=1;
for(int i=1;i<=n;++i){
su[i]=su[i-1]+gran[i];
if(su[i]>i){
ans=su[i];
}
}
printf("%d\n",ans);
}
return 0;
}

C

题意

给一个矩阵 定义方式见题 问从x1,y1到x2,y2的的路径和有多少种可能 只能向下或向右走

题解

给的矩阵很容易看出 向下的值比向右的值大1 也就是一次向下和向右操作的颠倒会差1 推理一下很容易发现 可能的路径和是下限到上限的所有值 下限是一直向右最后向下 上限是一直向下最后向右 再计算一下就能推出差值实际上就是(x2-x1)*(y2-y1) 宽度决定一个差1会积累几次 高度决定积累几个差1 最后答案就是差值+1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int main(){
int T;
scanf("%d",&T);
while(T--){
long long x1,x2,y1,y2;
scanf("%lld%lld%lld%lld",&x1,&y1,&x2,&y2);
long long tmp=(x2-x1)*(y2-y1);
long long res=tmp+1;
printf("%lld\n",res);
}
return 0;
}

D

题意

一年n个月 一个月ki天 每天的拥抱值是所在月份的天数 要选连续的x天 问怎么选能让拥抱值最大 可以跨年

题解

跨年问题很好解决 我们计算两年的就可以了 而后我们可以证明 答案的右边界一定是卡着月份结束的 反证法可以推出来 当然做题时候就是靠感觉了 然后只要试每个边界就完了

然而我写的tle了。。用了二分查找依旧tle。。方法写的还是不太好看起来。。

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
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int n;
long long month[400020],su[400020];
long long last(int t){
long long tmpindex;
while(tmpindex=(upper_bound(su,su+n+1,t)-su),tmpindex!=(n+1)){
return su[tmpindex]-t;
}
return 0;
}
long long val(long long start,long long len){
long long res=0,las=0,tmpindex;
while(tmpindex=(upper_bound(su,su+n+1,start)-su),tmpindex!=(n+1)){
if(len==0){
break;
}
int i=tmpindex;
long long tmplen=min(len,su[i]-start);
las=0;
if(i!=0){
las=su[i-1];
}
long long calst=start-las+1;
res+=(calst+calst+tmplen-1)*tmplen/2;
start+=tmplen;
len-=tmplen;
}
// for(int i=0;i<=n;++i){
// if(len==0){
// break;
// }
// if(su[i]>start){
// long long tmplen=min(len,su[i]-start);
// long long calst=start-las+1;
// res+=(calst+calst+tmplen-1)*tmplen/2;
// start+=tmplen;
// len-=tmplen;
// }
// las=su[i];
// }
return res;
}
int main(){
int x;
scanf("%d%d",&n,&x);
for(int i=0;i<n;++i){
scanf("%lld",&month[i]);
month[n+i]=month[i];
}
n=n*2;
su[0]=month[0];
for(int i=0;i<n;++i){
su[i+1]=su[i]+month[i+1];
}
long long ans=val(0,x),start=0,las=x;
long long res=ans;
long long tmp=0;
while(tmp=last(las)){
ans-=val(start,tmp);
ans+=val(las,tmp);
res=max(res,ans);
start+=tmp;
las+=tmp;
}
printf("%lld\n",res);
return 0;
}

A

题意

n个数挑x个 能不能令其和为奇数

题解

判断一下奇偶数个数即可

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
#include<cstdio>
#include<algorithm>
using namespace std;
int main(){
int T;
scanf("%d",&T);
while(T--){
int n,x;
scanf("%d%d",&n,&x);
int odd=0,even=0,tmp;
for(int i=0;i<n;++i){
scanf("%d",&tmp);
if(tmp&1){
++odd;
}else{
++even;
}
}
bool can=1;
if(odd==0){
can=0;
}else{
int oddpair=(odd-1)/2*2;
int need=(x-1)/2*2;
int actual=min(need,oddpair);
if((x-1-actual)>even){
can=0;
}
}
if(can){
printf("Yes\n");
}else{
printf("No\n");
}
}
return 0;
}

B

题意

给一个01串 一次操作可以把0变1 反之亦然 最少操作几次能让序列不出现101 010 的格式

题解

实际就是要将串转化为从某个分界点开始 向前全为0或1,向后全为0或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
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int main(){
int T;
scanf("%d",&T);
char dat[1020];
int num[1020][2];
while(T--){
scanf("%s",dat);
int n=strlen(dat);
if(n<3){
printf("0\n");
continue;
}
num[0][0]=0,num[0][1]=0;
for(int i=0;i<n;++i){
if(dat[i]=='0'){
num[i+1][0]=num[i][0]+1;
num[i+1][1]=num[i][1];
}else{
num[i+1][0]=num[i][0];
num[i+1][1]=num[i][1]+1;
}
}
int ans=1040;
for(int i=0;i<=n;++i){
ans=min(ans,num[i][1]+num[n][0]-num[i][0]);
ans=min(ans,num[i][0]+num[n][1]-num[i][1]);
}
printf("%d\n",ans);
}
return 0;
}

C

题意

给一颗树 每次只能取叶子节点 谁取到x节点谁赢 问谁能赢

题解

。。太菜了。。这里开始想不透了 开始看题解。。

好吧又是我想复杂了。。实际上很简单 如果要求得x已经是叶子节点 那么显然是先手直接获胜 其他情况 只需要数孩子节点个数就能够知道是谁胜出了 因为实际上每种形式都可以从最简单的三节点取中间节点的模型通过一步步加节点转化来 加一个必胜态必败态转变一次。。就很显然了。。

D

题意

n个元素的数组 分成k个互不相交的子集 给你每个子集包含元素的index 要求的结果串长度为k,每一位是去除对应子集的所有元素的最大值,可以做最多12次查询,每次给定要查的index而后返回这些index中最大值

题解

有一个点很关键 想通了就很好解决。。因为所有数组有一个最大值 只会包含在一个子集里 所以所有除了这个子集的最大值都是固定的 可以通过二分来查 10次查询能够确定 除此以外 查一次所有元素最大值 查一次除了最大值子集的最大值 12次查询正好够使用

基本dp刷的差不多到这个意思 开始尝试模拟比赛来锻炼了

A

题意

两人轮流放棋子,一行一列只允许一个棋子 问谁能赢

题解

很简单 每个棋子实际上是占据了一行一列的位置,根据开始条件判断一下可放的行和列的最小值,根据奇偶性判断就可以了

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
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int row[52],col[52];
int main(){
int T;
scanf("%d",&T);
while(T--){
int n,m;
scanf("%d%d",&n,&m);
memset(row,0,sizeof(row));
memset(col,0,sizeof(col));
for(int i=0;i<n;++i){
for(int j=0;j<m;++j){
int tmp=0;
scanf("%d",&tmp);
if(tmp){
row[i]=1,col[j]=1;
}
}
}
int ans=502,cnt=0;
for(int i=0;i<n;++i){
if(!row[i]){
++cnt;
}
}
ans=min(ans,cnt);
cnt=0;
for(int i=0;i<m;++i){
if(!col[i]){
++cnt;
}
}
ans=min(ans,cnt);
if(ans%2){
printf("Ashish\n");
}else{
printf("Vivek\n");
}
}
return 0;
}

B

题意

每个元素有自己的种类 0或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
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int main(){
int T;
scanf("%d",&T);
while(T--){
int n;
scanf("%d",&n);
int tmp,lasttmp=0,disorder=0;
for(int i=0;i<n;++i){
scanf("%d",&tmp);
if(tmp>=lasttmp){
lasttmp=tmp;
}else{
disorder=1;
}
}
int has[2]={0,0};
for(int i=0;i<n;++i){
scanf("%d",&tmp);
if(tmp){
has[1]=1;
}else{
has[0]=1;
}
}
if(has[0]^has[1]&&disorder){
printf("No\n");
}else{
printf("Yes\n");
}
}
return 0;
}

C

题意

给定两个排列,可以循环左右移任意多次,问两个排列在相同位置元素一样的数量最多是几个

题解

记录每个元素的位置,两个一减就可以知道需要几次左右移 找到最多的即可

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<algorithm>
#include<cstring>
using namespace std;
int res[200020],a[200020],b[200020];
int main(){
int n,tmp;
scanf("%d",&n);
for(int i=0;i<n;++i){
scanf("%d",&tmp);
a[tmp]=i;
}
for(int i=0;i<n;++i){
scanf("%d",&tmp);
b[tmp]=i;
}
for(int i=1;i<=n;++i){
++res[(a[i]+n-b[i])%n];
}
int ans=0;
for(int i=0;i<n;++i){
ans=max(ans,res[i]);
}
printf("%d\n",ans);
return 0;
}

D

题意

给定迷宫 最右下是出口 有好人与坏人 问有没有办法通过将某些空地变成墙来让坏人走不出去而好人可以走出去 出口也可以变成墙

题解

从这里开始我就没做出来了。。太菜了。。所以只通过查看题解大概讲一下我对这题的理解

。。我一开始想复杂了 实际上是很简单的思路

要让坏人走不出去 那么实际上一定可以直接把坏人周围的路全部变成墙,因为如果存在另一个不需要堵周围的路的解的话,那么坏人以及他周围的路一定也在更外层被堵住了,那么他周围的路堵与不堵实际上是都可以的 基本思路就是这个样子 要注意一些细节

  • 如果有好人临近坏人 那么无论如何都是堵不住的 肯定不可以
  • 如果坏人临近出口 必须将出口堵住 如果场上没有好人 实际上也是可行解

E

题意

一个数组 从中选出部分元素 从位运算角度来看 如果某一位有max(1,k-2)个元素是1,那么结果的这一位也置一,问如何选元素令其结果最大

题解

。。非常奇妙。。我蠢了。。。

实际上只需要找所有3个元素的可能就好 因为当超过三个元素时,根据要求可知至少有k-2个元素在结果置1的位上是1,根据鸽笼原理,选三个元素 一定会包含至少一个这一位是1的,而三个元素时实际上就是三个元素不断求或即可 那么选三个元素的结果不会小于选更多的 就可以非常简单的求了

题意

m个区间 每个区间的工作有对应效率 开始一个区间的工作则必须完成才可选择下一个,每次完成需要休息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
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int dp[1020];
struct inter{
int begin,end,power;
};
inter line[1040];
bool cmp(inter a,inter b){
if(a.begin==b.begin){
return a.end<b.end;
}
return a.begin<b.begin;
}
int main(){
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
for(int i=0;i<m;++i){
scanf("%d%d%d",&line[i].begin,&line[i].end,&line[i].power);
line[i].end+=k;
}
sort(line,line+m,cmp);
for(int i=0;i<m;++i){
dp[i]=line[i].power;
for(int j=0;j<i;++j){
if(line[j].end<=line[i].begin){
dp[i]=max(dp[i],dp[j]+line[i].power);
}
}
}
printf("%d\n",*max_element(dp,dp+m));
return 0;
}

题意

给定一个n阶方阵,问里面最大的对阵子矩阵有多大 要求对称为从左下至右上

题解

稍稍仔细思考一下 这种问题很明显我们需要从小到大 观察一下可以知道 对一个三阶对称子矩阵,可以看出需要满足对称轴上有两个二阶对称子矩阵 同时左上与右下元素要求满足对称,那么可由二阶对称矩阵推至三阶 实际上从一阶到二阶 从三阶到四阶完全是类似的过程 由两个低一阶的对称子矩阵加上一对对称元素即可 那么用dp[i][j]表示坐标(i,j)的对称矩阵最大值,初始值设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
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
char matrix[1200][1200];
int dp[1200][1200];
int main(){
int n;
while(scanf("%d",&n),n){
for(int i=0;i<n;++i){
scanf("%s",matrix[i]);
}
memset(dp,0,sizeof(dp));
int ans=0;
for(int i=1;i<n;++i){
for(int j=0;(j+i)<n;++j){
for(int t=0;(t+i)<n;++t){
if(dp[j][t+1]==(i-1)&&dp[j+1][t]==(i-1)&&matrix[j][t]==matrix[j+i][t+i]){
dp[j][t]=i;
ans=i;
}
}
}
}
printf("%d\n",ans+1);
}
return 0;
}

题意

二维地图 每个位置有一个分数 从(0,0)开始走,走到哪里可以得到哪里的分数,要求每次只能横向或竖向走最多k个单位,每一次走到的地方分数要比上一次大

题解

蛮简单。。记忆化搜索做个dfs就可以了。。

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
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int dp[120][120],hole[120][120];
int dfs(int &k,int &n,int i,int j){
if(dp[i][j]!=-1){
return dp[i][j];
}
int ans=0;
for(int t=1;t<=k;++t){
if((i+t)<n&&hole[i+t][j]>hole[i][j]){
ans=max(ans,dfs(k,n,i+t,j));
}
if((i-t)>=0&&hole[i-t][j]>hole[i][j]){
ans=max(ans,dfs(k,n,i-t,j));
}
if((j+t)<n&&hole[i][j+t]>hole[i][j]){
ans=max(ans,dfs(k,n,i,j+t));
}
if((j-t)>=0&&hole[i][j-t]>hole[i][j]){
ans=max(ans,dfs(k,n,i,j-t));
}
}
dp[i][j]=ans+hole[i][j];
return dp[i][j];
}
int main(){
int n,k;
while(scanf("%d%d",&n,&k),n!=-1){
for(int i=0;i<n;++i){
for(int j=0;j<n;++j){
scanf("%d",&hole[i][j]);
}
}
memset(dp,-1,sizeof(dp));
int ans=dfs(k,n,0,0);
printf("%d\n",ans);
}
return 0;
}

题意

给一个队列的商品 每天只能拿最左或者最右 商品的价值是自身的价值乘卖出的天数 问最大能卖多少

题解

一开始想的dp复杂了。。实际上就是简单的区间dp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
long long dp[2040][2040];
int treat[2020];
int main(){
int n;
scanf("%d",&n);

for(int i = 1; i <= n; i++)
scanf("%d", &treat[i]), dp[i][i] = treat[i]*n;
for(int len = 1; len < n; len++){
for(int i = 1; i < n; i++){
int j = i + len;
dp[i][j] = max(dp[i+1][j]+treat[i]*(n-len), dp[i][j-1]+treat[j]*(n-len));
}
}
printf("%lld\n",dp[1][n]);
return 0;
}

题意

裸的最长上升子序列。。要求严格上升

题解

。。真没啥说的 这次用了二分查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int MAXN=0x3f3f3f3f;
int dp[1040];
int main(){
int n;
scanf("%d",&n);
memset(dp,MAXN,sizeof(dp));
int ans=0;
for(int i=0;i<n;++i){
int tmp;
scanf("%d",&tmp);
int ide=lower_bound(dp,dp+n,tmp)-dp;
dp[ide]=tmp;
ans=max(ans,ide);
}
printf("%d\n",ans+1);
return 0;
}