一、亮灯时长(后缀和)
题目链接
自习室内有一个智能灯。
在 0 时刻,管理员会将打开电闸,并将灯点亮。
在 M 时刻,管理员会直接拉下电闸,此时,如果灯处于点亮状态,则会因为断电而熄灭。
在 0∼M 之间有 n 个不同时刻,不妨用 a1,a2,…,an 表示,其中 0<a1<a2<…<an<M。
在这 n 个时刻中的每个时刻,管理员都会拨动一次智能灯的开关,使灯的状态切换(亮变灭、灭变亮)。
现在,你可以最多额外指定一个时刻(也可以不指定),让管理员在此时刻也拨动开关一次。注意选定的时刻不能与 a1,a2,…,an 相等。
你的目的是让亮灯的总时长尽可能长。
输出这个最大亮灯总时长。
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据,第一行包含两个整数 n 和 M。
第二行包含 n 个整数 a1,a2,…,an。
输出格式
输出一个整数,表示最大亮灯总时长。
数据范围
1≤T≤30,
1≤n≤105,
2≤M≤109,
0<a1<a2<…<an<M。
同一测试点内所有 n 的和不超过 105。
输入样例:
3
3 10
4 6 7
2 12
1 10
2 7
3 4
输出样例:
8
9
6
假定我们按的是ai:
奇数区间:ai之前的奇数加上ai+1之后的偶数区间和ai+1-ai-1这个长度,是利用前缀和技巧
偶数区间:ai前的奇数区间和ai+1后的偶数区间和ai+1-ai
所以不分奇偶
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<string.h>
using namespace std;
const int N=100010;
int n,m;
int a[N],s1[N],s2[N];
int main()
{
int T;
cin>>T;
while(T--)
{
int n,m;
cin>>n>>m;
memset(a,0,sizeof(a));
memset(s1,0,sizeof(s1));
memset(s2,0,sizeof(s2));
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
a[++n]=m;
s1[N]=s2[N]=0;
for(int i=n-1;i>=0;i--)
{
s1[i]=s1[i+1],s2[i]=s2[i+1];
if(i%2==0)s1[i]+=a[i+1]-a[i];
else s2[i]+=a[i+1]-a[i];
}
int res=s1[0];
for(int i=0;i<n;i++)
{
int t=a[i+1]-a[i];
if(t==1)continue;
res=max(res,s1[0]-s1[i]+s2[i+1]+t-1);
}
printf("%d\n",res);
}
return 0;
}
二、兔子跳
题目链接
一只兔子位于二维平面的原点 (0,0) 处,它想通过一系列的跳跃,跳到点 (x,0) 处。
给定一个长度为 n 的数组 a1,a2,…,an。
兔子能从一个点跳到另一个点,当且仅当两点之间的距离等于上述数组中的某个元素的值。
请问,兔子从 (0,0) 到 (x,0) 最少需要跳几次?
注意,兔子可以跳到非整数坐标的点上。
例如,当 x=4,a={1,3} 时,(0,0)→(1,0)→(4,0) 和 (0,0)→(2,5√)→(4,0) 均为合理最佳方案之一。
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据第一行包含两个整数 n 和 x。
第二行包含 n 个整数 a1,a2,…,an。
输出格式
每组数据输出一行结果,表示最少跳跃次数。
数据范围
1≤T≤1000,
1≤n≤105,
1≤x≤109,
1≤ai≤109,ai 各不相同。
保证同一测试点内所有 n 的和不超过 105。
输入样例:
4
2 4
1 3
3 12
3 4 5
1 5
5
2 10
15 4
输出样例:
2
3
1
2
当x等于a,那么一次可以跳到
当x小于a,那么一定存在一个等腰三角形,使之两次跳到
当x大于a,那么x/a为整数的时候,那就是那个值,如果不为整数,那么除以之后下取整,如果剩下的长度<2a,那么就可以再构造一次等腰三角形了,综上,此时的情况就是a/x或者a/x下取整+1,那么可以统一表示为a/x上取整
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
int a[N];
int main()
{
int T;
cin>>T;
while(T--)
{
int n,x;
cin>>n>>x;
bool flag=false;
int a=0;
while(n--)
{
int t;
scanf("%d",&t);
if(t==x)flag=true;
a=max(a,t);
}
if(flag)puts("1");
else if(x<a)puts("2");
else printf("%d\n",(x+a-1)/a);//上取整
}
return 0;
}
三、数组补全(环图,要多回顾)
题目链接
给定一个 1∼n 的排列 f1,f2,…,fn。
已知,对于 1≤i≤n,fi≠i 始终成立。
现在,因为一些原因,数组中的部分元素丢失了。
请你将数组丢失的部分补全,要求数组在补全后仍然是一个 1∼n 的排列,并且对于 1≤i≤n, fi≠i 均成立。
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据第一行包含一个整数 n。
第二行包含 n 个整数 f1,f2,…,fn。如果 fi=0,则表示 fi 已经丢失,需要补全。
输出格式
每组数据一行,输出补全后的 f 数组,整数之间空格隔开。
如果方案不唯一,则输出任意合理方案即可。
数据范围
1≤T≤100,
2≤n≤2×105,
0≤fi≤n,至少两个 fi 为 0。
同一测试点内所有 n 的和不超过 2×105。
数据保证有解。
输入样例:
3
5
5 0 0 2 4
7
7 0 0 1 4 0 6
7
7 4 0 3 0 5 1
输出样例:
5 3 1 2 4
7 3 2 1 4 5 6
7 4 2 3 6 5 1
i和fi构成环图,每个点的入度和出度均为1,把没有出现过的数加入缺口环的后面,剩下就直接封口,如果所有的环没有缺口,那么就把没出现过的数字自己连成一个环即可
#include<iostream>
#include<algorithm>
#include<stack>
#include<string.h>
using namespace std;
const int N=200010;
int n;
int p[N],q[N];
bool st[N];
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
memset(q,0,sizeof(q));
memset(st,0,sizeof(st));
for(int i=1; i<=n; i++)
{
scanf("%d",&p[i]);
q[p[i]]]=i;
}
bool flag=false;
for(int i=1; i<=n; i++)
{
if(st[i]||!p[i])//没有处理过或者p[i]为0
continue;
st[i]=true;
int x=i,y=i;
while(p[x]&&!st[p[x]])
{
x=p[x];
st[x]=true;
}
while(q[y]&&!st[q[y]])
{
y=q[y];
st[y]=true;
}
if(p[x]==y)continue;//孤立点
if(!flag)
{
flag=true;
for(int j=1; j<=n; j++)
if(!p[j]&&!q[j])
{
st[j]=true;
p[x]=j;
x=j;
}
}
p[x]=y;
}
if(!flag)
{
int x=0,y=0;
for(int i=1;i<=n;i++)
if(!p[i])
{
if(!x&&!y)x=y=i;
else
{
p[x]=i;
x=i;
}
}
p[x]=y;
}
for(int i=1;i<=n;i++)
printf("%d",p[i]);
puts("");
}
return 0;
}
四、水果拼盘
某水果店以出售水果拼盘盈利。
该店铺共提供两种拼盘:
拼盘 1:包含 1 个苹果,1 个香蕉。
拼盘 2:包含 1 个梨,1 个橙子,1 个香蕉。
已知该店铺共有 a 个苹果,b 个梨,c 个橙子,d 个香蕉。
拼盘 1 的利润为 e 元,拼盘 2 的利润为 f 元。
水果店的生意很好,所有拼好的拼盘都会销售一空。
请问,利用现有水果拼装水果拼盘,能够获得的最大利润是多少?
注意,也许会有一些水果用不完,但这并不重要。
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据占一行,包含 6 个整数,依次为 a,b,c,d,e,f。
输出格式
每组数据输出一行结果,表示最大利润。
数据范围
1≤T≤10。
1≤a,b,c,d≤105,
1≤e,f≤103。
输入样例:
3
4 5 6 3 1 2
12 11 13 20 4 6
17 14 5 21 15 17
输出样例:
6
102
325
会发现两个套餐,无论怎么样都会有一个香蕉拿出去,所以就看香蕉的数量来判断总共会有多少种情况
当e>=f的时候,尽可能的分给第一个套餐,那就取min(a,d),y=min(b,c,d-x)
当e<f的时候,尽可能分给第二个套餐,y=min(b,c,d),x=min(a,d-x)
#include<iostream>
#include<algorithm>
#include<stack>
#include<string.h>
using namespace std;
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
int a,b,c,d,e,f;
cin>>a>>b>>c>>d>>e>>f;
if(e>=f)
{
int x,y;
x=min(a,d);
y=min(b,min(c,d-x));
cout<<x*e+y*f<<endl;
}
else {
int x,y;
y=min(b,min(c,d));
x=min(a,d-y);
cout<<x*e+y*f<<endl;
}
}
return 0;
}
五、砖块(递推)
题目链接
n 个砖块排成一排,从左到右编号依次为 1∼n。
每个砖块要么是黑色的,要么是白色的。
现在你可以进行以下操作若干次(可以是 0 次):
选择两个相邻的砖块,反转它们的颜色。(黑变白,白变黑)
你的目标是通过不超过 3n 次操作,将所有砖块的颜色变得一致。
输入格式
第一行包含整数 T,表示共有 T 组测试数据。
每组数据第一行包含一个整数 n。
第二行包含一个长度为 n 的字符串 s。其中的每个字符都是 W 或 B,如果第 i 个字符是 W,则表示第 i 号砖块是白色的,如果第 i 个字符是 B,则表示第 i 个砖块是黑色的。
输出格式
每组数据,如果无解则输出一行 −1。
否则,首先输出一行 k,表示需要的操作次数。
如果 k>0,则还需再输出一行 k 个整数,p1,p2,…,pk。其中 pi 表示第 i 次操作,选中的砖块为 pi 和 pi+1 号砖块。
如果方案不唯一,则输出任意合理方案即可。
数据范围
1≤T≤10,
2≤n≤200。
输入样例:
4
8
BWWWWWWB
4
BWBB
5
WWWWW
3
BWB
输出样例:
3
6 2 4
-1
0
2
2 1
跟翻硬币和开关灯(费解的开关)很像,一个一维,一个二维。
本质考察的是递推,每一个位置操作2次就等于没有操纵,所有每个位置就考虑是操作一次还是操作两次
这道题也可以用高斯消元法来求解,就是会难一些O(n^3)
y1=x1^x1,就是当数据范围很大的时候用高斯消元法来写
#include<iostream>
#include<algorithm>
#include<vector>
#include<string.h>
using namespace std;
int n;
void updata(char &c)
{
if(c=='W')c='B';
else c='W';
}
bool check(string s,char c)
{
vector<int>res;
for(int i=0;i+1<n;i++)
{
if(s[i]!=c)
{
updata(s[i]);
updata(s[i+1]);
res.push_back(i);
}
}
if(s.back()!=s[0])return false;
cout<<res.size()<<endl;
for(auto x:res)cout<<x+1<<" ";
if(res.size())cout<<endl;
return true;
}
int main()
{
int T;
cin>>T;
while(T--)
{
string s;
cin>>n>>s;
if(!check(s,'W')&&!check(s,'B'))
puts("-1");
}
return 0;
}