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)