牛客小白月赛34

A.dd爱科学1.0

题目链接
做法1:
在这里插入图片描述
在这里插入图片描述

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>
using namespace std;
const int maxn = 1000006, INF = 0x7fffffff;
struct Node {
    int val, num;
} z[maxn];
int T[maxn];
int n;
bool cmp(Node a, Node b) {
    return a.val == b.val ? a.num < b.num : a.val < b.val;
}
void modify(int x, int y)  //把val[x]替换为val[x]和y中较大的数
{
    for (; x <= n; x += x & (-x))
        T[x] = max(T[x], y);
}
int query(int x)  //返回val[1]~val[x]中的最大值
{
    int res = -INF;
    for (; x; x -= x & (-x))
        res = max(res, T[x]);
    return res;
}
int main() {
    int ans = 0;
    char a;
    scanf("%d", &n);
    getchar();
    for (int i = 1; i <= n; i++) {
        scanf("%c", &a);
        z[i].val = a - 'A';
        z[i].num = i;  //记住val[i]的编号,有点类似于离散化的处理,但没有去重
    }
    sort(z + 1, z + n + 1, cmp);  //以权值为第一关键字从小到大排序
    for (int i = 1; i <= n; i++)  //按权值从小到大枚举
    {
        int maxx = query(z[i].num);  //查询编号小于等于num[i]的LIS最大长度
        modify(z[i].num, ++maxx);  //把长度+1,再去更新前面的LIS长度
        ans = max(ans, maxx);      //更新答案
    }
    printf("%d\n", n - ans);
    return 0;
}

题解:注意题目是要求把数组编程不下降序列,也就是后面的元素都要比前面的元素大或者等于。于是我们求出这个序列中当前的最长不下降子序列,用n减去这个最长不下降子序列的长度,得到的差就是要改变的元素个数。ps:这个球最长不下降子序列是用了树状数组去求得,用一般dp去求会爆时间。

做法2:

#include<bits/stdc++.h>
using namespace std;
char a[1000005],q[1000005];
int n,tail;
int find(char x){
	int l,r,mid;
	l=1;r=tail;int s=-1;
	while (l<=r){
		mid=(l+r)/2;
		if (x>=q[mid-1]&&x<=q[mid])s=max(s,mid);
		if (x>=q[mid])l=mid+1;
		else r=mid-1;
	}
	return s;
}
int main(){
	scanf("%d",&n);
	scanf("%s",a+1);
	q[0]='A'-1;tail=0;
	for (int i=1;i<=n;i++)
		if (a[i]>=q[tail])q[++tail]=a[i];
		else{
			int x=find(a[i]);
			if (x!=-1)q[x]=a[i];
		}
	printf("%d\n",n-tail);
}

题解:
二分+单调队列维护,复杂度 O(nlogn)

做法3:

#include<bits/stdc++.h>
using namespace std;
int n,ans;
char a[1000005];
int f[1000005][30];
int main(){
	scanf("%d",&n);
	scanf("%s",a+1);
	memset(f,0x3f,sizeof(f));
	for (int i=1;i<=26;i++)f[0][i]=0;
	for (int i=1;i<=n;i++)
		for (int j=1;j<=26;j++)f[i][j]=min(f[i][j-1],f[i-1][j]+(a[i]!=j+'A'-1));
	printf("%d",f[n][26]);
} 

题解:
‘A’~'Z’对应1到26 ,f[i][j]表示改至第i位为止,最后一位<=j的最小代价,
对应转移方程: f[i][j] = min(f[i][j-1], f[i-1][[j] + (a[i]!=j+‘A’-1))
f[n][26]就是答案,复杂度O(26 x n)

B.dd爱探险

题目链接
在这里插入图片描述在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
int n,p[20][20],f[65540][20][5];
int c[20],a[20],b[20];
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		for (int j=1;j<=n;j++)scanf("%d",&p[i][j]);
	memset(f,0x3f,sizeof(f));	
	for (int i=0;i<n;i++)f[1<<i][i+1][0]=0;
	for (int t=1;t<(1<<n);t++){
		int y=t;
		for (int i=1;i<=n;i++)c[i]=y%2,y/=2;
		int m1,m2;
		m1=m2=0;
		for (int i=1;i<=n;i++)
			if (c[i])a[++m1]=i;
			else b[++m2]=i;
		for (int i=1;i<=m1;i++)
			for (int j=1;j<=m2;j++){
				f[t+(1<<(b[j]-1))][b[j]][0]=min(f[t+(1<<(b[j]-1))][b[j]][0],f[t][a[i]][0]+p[a[i]][b[j]]);	
				f[t+(1<<(b[j]-1))][b[j]][1]=min(f[t+(1<<(b[j]-1))][b[j]][1],min(f[t][a[i]][0],f[t][a[i]][1]+p[a[i]][b[j]]));
				f[t+(1<<(b[j]-1))][b[j]][2]=min(f[t+(1<<(b[j]-1))][b[j]][2],min(f[t][a[i]][0]+2*p[a[i]][b[j]],f[t][a[i]][2]+p[a[i]][b[j]]));
				f[t+(1<<(b[j]-1))][b[j]][3]=min(f[t+(1<<(b[j]-1))][b[j]][3],min(f[t][a[i]][1]+2*p[a[i]][b[j]],f[t][a[i]][2]));
				f[t+(1<<(b[j]-1))][b[j]][3]=min(f[t+(1<<(b[j]-1))][b[j]][3],f[t][a[i]][3]+p[a[i]][b[j]]);	
			}
	}
	int ans=1000000000;
	for (int i=1;i<=n;i++)ans=min(ans,f[(1<<n)-1][i][3]);
	printf("%d\n",ans);
}

题解:
状压dp
首先 n个点 n-1次跳完,明白这个跳跃过程其实是一条链,每个点只有被经过和没被经过两个状态,所以典型状压dp
对于状态 i,转成二进制数形式,第 k位对应 0或 1,对应为 1时表示这个点已经被经过,对应 0则表示没有
定义dp数组 f[i][j][k], ii状态表示如上, j表示当前停在 j这个点上, k有 0,1,2,3这四个状态,k=0表示没经过加速,k=1表示经过一次重力加速,k=2表示经过一次反重力加速,k=3表示两次加速都已经结束,注意状态转移
最后枚举最后停留的位置 j, min(f[(1<<n)-1][j][3])就是答案,复杂度 O(n^2 × 2n )

C.dd爱科学2.0

题目链接
在这里插入图片描述在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
int n,ans;
char a[1000005];
int f[1000005][30];
int main(){
	scanf("%d",&n);
	scanf("%s",a+1);
	memset(f,0x3f,sizeof(f));
	for (int i=1;i<=26;++i)f[0][i]=0;
	for (int i=1;i<=n;++i)
		for (int j=1;j<=26;++j)f[i][j]=min(f[i][j-1],f[i-1][j]+abs(a[i]-(j-1+'A')));
	printf("%d",f[n][26]);
} 

题解:
dp
与A题dp解法一样,只要转移条件改成 f[i][j]=min(f[i][j-1],f[i-1][j]+abs(a[i]-(j-1+‘A’)))即可

D.dd爱矩阵

题目链接

在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
int n,a[1005],b[1005],c[1005];
struct t{
	int x,z;
	friend bool operator<(t x,t y){
		return x.z<y.z;
	} 
};

bool cmp(int x,int y){
	return x>y;
}
int main(){
	priority_queue<t>q;
	scanf("%d",&n);
	for (int i=1;i<=n;i++)scanf("%d",&b[i]);
	sort(b+1,b+1+n,cmp);
	for (int i=2;i<=n;i++){
		for (int j=1;j<=n;j++)scanf("%d",&a[j]);
		sort(a+1,a+1+n,cmp);
		for (int j=1;j<=n;j++){
			t p;
			p.x=1;p.z=a[1]+b[j];q.push(p);
		}
		for (int j=1;j<=n;j++){
			t pp=q.top();t p;
			q.pop();
			c[j]=pp.z;
			p.x=pp.x+1;
			p.z=pp.z-a[pp.x]+a[p.x];
			q.push(p);
		}
		for (int j=1;j<=n;j++)b[j]=c[j];
		while (!q.empty())q.pop();	
	}
	for (int j=1;j<=n;j++)printf(j==n?"%d\n":"%d ",b[j]);
}

题解:
贪心+优先队列
先把问题简化一下,如果两行,每行 n个数,怎么选
把两行分别降序 sort,如果令第一行为数组 a,第二行为数组 b
则可得到最大值为 a[1]+b[1],并且得到 a[i]+b[j-1]>a[i]+b[j]>a[i]+b[j+1]且 a[i-1]+b[j]<a[i]+b[j]<a[i+1]+b[j]
所以可以把 a[1]+b[i]全部推入优先队列当中,并且标记对应的 ii,每次取出 top,再把 a[1]+b[i+1]推入优先队列当中, n次操作即可得到前 n大
复杂度 O(nlogn)
回到这个题目,由于是 n行,可以每次处理两行,并成一行新的, n-1次操作把 n行并成一行,复杂度 O(n^2 logn)

E.dd爱旋转

题目链接

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
inline int read() {
    int x = 0, f = 1;
    char c = getchar();
    while (c < '0' || c > '9') {
        if (c == '-')
            f = -1;
        c = getchar();
    }
    while (c >= '0' && c <= '9')
        x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
    return x * f;
}

int n, q, t;
int a[1002][1002];
void cao1() {
    if (n & 1) {
        for (int i = 1; i <= n / 2; ++i) {
            for (int j = 1; j <= n; ++j) {
                swap(a[i][j], a[n - i + 1][n - j + 1]);
            }
        }
        int len = n / 2 + 1;
        for (int i = 1; i <= n / 2; ++i) {
            swap(a[len][i], a[len][n - i + 1]);
        }
    } else {
        for (int i = 1; i <= n / 2; ++i) {
            for (int j = 1; j <= n; ++j) {
                swap(a[i][j], a[n - i + 1][n - j + 1]);
            }
        }
    }
}
void cao2() {
    for (int i = 1; i <= n / 2; ++i) {
        for (int j = 1; j <= n; ++j) {
            swap(a[i][j], a[n - i + 1][j]);
        }
    }
}
int main() {
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) {
            a[i][j] = read();
        }
    }
    cin >> q;
    int k1=0, k2=0;
    while (q--) {
        t = read();
        if (t == 1) {
            k1++;
        } else {
            k2++;
        }
    }
    if (k1 & 1) {
        cao1();
    }
    if (k2 & 1) {
        cao2();
    }
    for (int i = 1; i <= n; ++i) {
        for (int j = 1; j <= n; ++j) {
            cout << a[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

题解:因为cao1()表示旋转180度,cao2()表示行反转,我们用k1记录1操作了多少次,k2记录操作了多少次。众所周知,当旋转180度执行了两次之后会变成旋转360,即相当于不变,于是我们对k1进行验证,k1为奇数就执行cao1(),为偶数就不执行,k2也是同理操作。我当时脑残了,把k1和k2定义在了main函数里面,忘记赋初值为0,然后就wa了(定义在main函数里面的变量会被随机赋值)。

F.dd爱框框

题目链接
在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll x,a[10000005],w;
int n;
int main(){
	scanf("%d%lld",&n,&x);
	for (int i=1;i<=n;i++)scanf("%lld",&a[i]);
	int l,r;
	l=0;r=n+1;
	int s,t;
	s=1;t=0;
	w=0;
	for (int i=1;i<=n;i++){
		w+=a[i];t++;
		while (w-a[s]>=x)w-=a[s++];
		if (w>=x){
			if (t-s+1<r-l+1)r=t,l=s;
		}
	}
	printf("%d %d\n",l,r);
}

题解:
单调队列维护窗口移动,复杂度 O(n)

G.dd爱捣乱

题目链接
在这里插入图片描述
在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;
int n, ans;
char b[1000005];
int a[1000005];
int f[1000005][6][6];
int main() {
    scanf("%d", &n);
    scanf("%s", b + 1);
    for (int i = 1; i <= n; i++)
        a[i] = b[i] - 'a';
    memset(f, -1, sizeof(f));
    for (int i = -2; i <= 2; i++)
        for (int j = -2; j <= 2; j++)
            if ((a[1] + i + 26) % 26 != (a[2] + j + 26) % 26)
                f[2][i + 2][j + 2] = abs(i) + abs(j);
    for (int i = 3; i <= n; i++)
        for (int j = -2; j <= 2; j++)
            for (int k = -2; k <= 2; k++)
                if (f[i - 1][j + 2][k + 2] != -1)
                    for (int t = -2; t <= 2; t++)
                        if ((a[i] + t + 26) % 26 != (a[i - 2] + j + 26) % 26 &&
                            (a[i] + t + 26) % 26 != (a[i - 1] + k + 26) % 26) {
                            if (f[i][k + 2][t + 2] == -1)
                                f[i][k + 2][t + 2] =
                                    f[i - 1][j + 2][k + 2] + abs(t);
                            else
                                f[i][k + 2][t + 2] =
                                    min(f[i][k + 2][t + 2],
                                        f[i - 1][j + 2][k + 2] + abs(t));
                        }
    ans = 1e9;
    for (int i = 0; i <= 4; i++)
        for (int j = 0; j <= 4; j++)
            if (f[n][i][j] != -1)
                ans = min(ans, f[n][i][j]);
    printf("%d\n", ans);
}

题解:

首先讨论完美串满足条件,如果是奇串,则每一位前后两位不同,即 a[i-1]≠a[i+1]
如果是偶串,则相邻两位不同 a[i]≠a[i-1]
综上,只要任意连续三位都满足两两不同,就是一个完美串
那么一个很显然的想法,枚举每一位的情况,保证再枚举前两位情况,保证不同的情况下更新答案
复杂度 O(n×26^3)
复杂度显然有点偏大,常数没控制好极有可能 TLETLE
所以进一步想,每一位最多只会受前两位和后两位的影响,根据鸽巢原理,实际上最多只有五种情况: a[i]±2,a[i]±1,a[i],必有一种情况满足完美串
所以对于每一位只要枚举改变量就行了, f[i][j][k]表示把第 i-1位的改变量是 j,第 i位的改变量是 k的最小代价
f[i][j][k]→f[i+1][k][t]
最后枚举最后两位改变量 min(f[n][i][j])就是答案
复杂度 O(n×5^3)

H.dd爱整齐

题目链接
在这里插入图片描述
在这里插入图片描述

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,k;
ll a[1000005],g[1000005],f[1000005],b[1000005];
int main(){
	scanf("%d%d",&n,&k);
	k++;
	for (int i=1;i<=n;i++)scanf("%lld",&a[i]);
	for (int i=0;i<k;i++)g[i]=1000000001;
	memset(f,0,sizeof(f));
	memset(b,0,sizeof(b));
	for (int i=1;i<=n;i++)f[i%k]+=a[i];
	for (int i=1;i<=n;i++)g[i%k]=min(g[i%k],a[i]);
	for (int i=1;i<=n;i++)b[i%k]++;
	ll ss=a[1];
	for (int i=2;i<=n;i++)ss=min(ss,a[i]);
	ll ans=100000000000000000ll;
	ll pp=0;
	for (int i=1;i<=n;i++)pp+=a[i];
	for (int i=0;i<k;i++)
		ans=min(ans,f[i]-g[i]*b[i]+pp-f[i]-(1ll*n-b[i])*ss);
	printf("%lld\n",ans);	
} 

题解:
不管 k是多少,把位置 i对 k+1取余,余数相等的位置的值必然相等,通过简单贪心可知,肯定是把这些位置的数都改成余数相等的位置中值最小的那个数
因为给定了限制条件 a≥b,所以只要枚举 a所在的位置,可以直接利用前缀和计算出最小改变量,复杂度 O(n)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值