给一个数组 a a a,下标为奇数时,满足 a i > a i + 1 a_i>a_{i+1} ai>ai+1时,更改 a i 和 a i + 1 a_i和a_{i+1} ai和ai+1的位置, a n s + + ans++ ans++,并对所有下标为奇数的序列重复这种操作。下标为偶数时也是同样操作。操作顺序起点从 1 1 1到 n n n,直到整个序列是一个有序序列就停止,输出 a n s ans ans的值(如果序列已经有序则为 0 0 0)。这道题第一次做有好多地方没有理解清楚,开始打了个冒泡~~(第一印象确实很像)~~,莽错了好几发。
#include
using namespace std;
const int N=1e3+10;
int a[N];
int main()
{
int t;
cin>>t;
while(t--){
int n;
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
long long ans=0;
bool ok=0;
for(int i=1;i<n;i++){
if(a[i]>a[i+1]){
ok=1;
break;
}
}
while(ok){
ans++;
int p;
if(ans%2==1)p=1; //确定初始起点
else p=2;
for(int i=p;i<n;i+=2){ //进行替换
if(a[i]>a[i+1]){
swap(a[i],a[i+1]);
}
}
ok=0;
for(int i=1;i<n;i++){ //判断
if(a[i]>a[i+1]){
ok=1;
break;
}
}
}
cout<<ans<<endl;
}
}
首先本题有个概念需要理解——破发,对于一个球类游戏,A是发球局,但是B赢了本场比赛,那么称
B完成了破发。
然后给定 a a a, b b b,分别为A赢的局数和B赢的局数,开始谁发球都可以,但每局结束必须换发,问最后有多少个可能的破发局,并输出所有情况。
此题需要设一个方程,总局数 s u m = a + b sum=a+b sum=a+b,对于A来说, a = x + p a − p b , b = y + p b − p a a=x+p_a-p_b,b=y+p_b-p_a a=x+pa−pb,b=y+pb−pa,其中 x x x为 a a a的发球局数 = a + b 2 或 者 a + b + 1 2 [ 取 决 于 谁 先 发 球 ] =\frac{a+b}{2}或者\frac{a+b+1}{2}[取决于谁先发球] =2a+b或者2a+b+1[取决于谁先发球], p a p_a pa为 a a a赢的破发局数。 y y y为 b b b的发球局数 = s u m − x =sum-x =sum−x, p b p_b pb为 b b b赢的破发局数。两式相加可得到 a + b = x + y a+b=x+y a+b=x+y,这样,就可以枚举每一个 p a p_a pa,就可以得到 p b = b − y + p a p_b=b-y+p_a pb=b−y+pa。同时注意谁先发球都可以,如果 a a a先发球,那么 p a ∈ [ 0 , s u m + 1 2 ] p_a∈[0,\frac{sum+1}{2}] pa∈[0,2sum+1],否则 p a ∈ [ 0 , s u m 2 ] p_a∈[0,\frac{sum}{2}] pa∈[0,2sum],最后输出答案就可以了。
#include
using namespace std;
set<int> s;
//这里用集合的操作,既可以满足顺序,也可以去重。
int main()
{
int _;
scanf("%d",&_);
while(_--){
int a,b,p,q;
cin>>a>>b;
p=(a+b+1)/2;q=(a+b)/2;//a先手比b的发球局多以局
for(int i=0;i<=p;i++){
int y=(a+i-p);
if(y<=q&&y>=0){ //y的局数有限制
s.insert(i+y);
}
}
p=(a+b)/2;q=(a+b+1)/2; //a后手
for(int i=0;i<=p;i++){
int y=(a+i-p);
if(0<=y&&y<=q)s.insert(i+y);
}
cout<<s.size()<<endl;
for(auto i:s){
cout<<i<<" ";
}
cout<<endl;
s.clear();
}
}
有 n n n个洞口,每个洞口里有 k k k个怪物,排成一行,一个战士拥有一个初始值战斗力 p p p,当战斗力大于怪兽生命值时才能打过,每打过一个怪兽就加 1 1 1点战斗力,每个洞必须把所有怪物都打过才能进入下一个洞。问战士初始战斗力的最小值是多少?
其实这道题我第一次做的时候很清楚是二分,但是二分的值出了问题。对于每个洞 i i i可以简化成一个值 a i a_i ai,代表通过这个洞的最小战斗力,也就等于洞中的最大生命-位置+2,然后直接对最小战斗力的值进行二分, j u d g e judge judge函数是从所有洞最小战斗力从小到大进行判断,能通过就小一点,不能通过就大一点。相当于最大值最小的二分、
#include
using namespace std;
typedef unsigned long long ull;
const int N=1e5+10;
struct node{
ull st,ed;
bool operator < (const node& k){
if(st==k.st){
return ed>k.ed;
}
return st<k.st;
}
}s[N];
int n;
bool judge(ull k){
ull all=k;
for(int i=1;i<=n;i++){
if(all>=s[i].st){
all+=s[i].ed;
}
else return 0;
}
return 1;
}
int main()
{
int t;
cin>>t;
while(t--){
int p;
cin>>n;
for(int i=1;i<=n;i++){
cin>>p;
s[i].ed=p;
int x,mx=0;
for(int j=1;j<=p;j++){
cin>>x;
if(mx<x-j+2)mx=x-j+2;
}
s[i].st=mx;
}
sort(s+1,s+1+n);;
int l=1,r=1e9+10,mid;
while(l<=r){
mid=l+r>>1;
if(judge(mid)){
r=mid-1;
}
else l=mid+1;
}
cout<<l<<endl;
}
}
给定 n n n, m m m。从 1 1 1到 n n n,每个数可以减去一个数,但减完必须大于等于1。也可以除以一个数。步骤的执行顺序不同那么两方案也不同,问一共有多少种方法使得 n n n经过变换后成为1。考虑方案数很多,要求答案模 m m m。
对于这个题,可以很直接的写出暴力搜索算法, d p [ i ] = ∑ j = 1 i − 1 d p [ i − j ] + ∑ j = 2 i d p [ i / j ] dp[i]=\sum_{j=1}^{i-1}dp[i-j]+\sum_{j=2}^{i}dp[i/j] dp[i]=∑j=1i−1dp[i−j]+∑j=2idp[i/j] ,时间复杂度 O ( n 2 ) O(n^2) O(n2)考虑到 2 ≤ n ≤ 2 ⋅ 1 0 5 2\le n\le2·10^5 2≤n≤2⋅105,需要优化。第一个部分比较容易优化, d p [ i ] = d p [ i − 1 ] + d p [ i − 2 ] + d p [ i − 3 ] + . . . d p [ 1 ] dp[i]=dp[i-1]+dp[i-2]+dp[i-3]+...dp[1] dp[i]=dp[i−1]+dp[i−2]+dp[i−3]+...dp[1],可以提前维护前缀和 t o t + = d p [ i ] tot+=dp[i] tot+=dp[i],那么 d p [ i + 1 ] + = t o t dp[i+1]+=tot dp[i+1]+=tot,这样第一部分就优化成了 O ( n ) O(n) O(n)。
第二部分的优化有两种方式:
由于整除时向下取整的特性,就可以分块来优化。可以优化成 O ( n ) O(\sqrt{n}) O(n),这样总的复杂度就 O ( n n ) O(n\sqrt{n}) O(nn),对于 2 ⋅ 1 0 5 2·10^5 2⋅105的时间复杂度是可以通过的。
#include
using namespace std;
typedef long long ll;
const int N=2e5+10;
ll dp[N];
ll n,m,sum,tot=1;
int main()
{
int id=1;
cin>>n>>m;
dp[1]=1;
for(int i=2;i<=n;i++){
dp[i]=(dp[i]+tot)%m;
id=2;
while(i/id>=1){
int q=i/id;
int r=i/q;
dp[i]=((dp[i]+(dp[q]*(r-id+1))%m))%m;
//dp[i]=(dp[i]+dp[i/id])%m;
id+=(r-id+1);
}
tot=(tot+dp[i])%m;
}
cout<<dp[n]<<endl;
}
由于 i / j i/j i/j会向下取整,分析到这个范围是 [ i ∗ j , i ∗ j + j − 1 ] [i*j,i*j+j-1] [i∗j,i∗j+j−1],令 k ∈ [ i ∗ j , i ∗ j + j − 1 ] k∈[i*j,i*j+j-1] k∈[i∗j,i∗j+j−1],则每一个 d p [ k ] dp[k] dp[k]
都要加上 d p [ i ] dp[i] dp[i]。这样的区间操作可以在 O ( l o g n ) O(logn) O(logn)的时间内完成,那么从时间复杂度就是 O ( n l o g n ) O(nlogn) O(nlogn),可以通过 n ≤ 4 ∗ 1 0 6 n\le4*10^6 n≤4∗106的数据范围。不过实现上有两种方式,第一种是线段树,第二种是维护前缀和。维护前缀和就是设置前缀和数组 a [ i ] a[i] a[i],令 a [ i ∗ j ] + = d p [ i ] a[i*j]+=dp[i] a[i∗j]+=dp[i], a [ i ∗ j + j ] − = d p [ i ] a[i*j+j]-=dp[i] a[i∗j+j]−=dp[i],同时答案更新是 d p [ i ] + = a [ i ] dp[i]+=a[i] dp[i]+=a[i],再加上第一部分维护的前缀和 t o t tot tot就好了。那么维护前缀和时为什么是 O ( n l o g n ) O(nlog n) O(nlogn)的时间复杂度呢。对于每一个 i i i,更新时需要满足 i ∗ j ≤ n i*j\le n i∗j≤n,那么当 i = 1 i=1 i=1时,枚举次数为 n n n,当 i = 2 i=2 i=2,为 n / 2 n/2 n/2,当 i = 3 i=3 i=3,为 n / 3 n/3 n/3,当 i = n i=n i=n时,为 1 1 1,那么总和为:
( n + n / 2 + n / 3 + n / 4 + ⋅ ⋅ ⋅ 1 ) = n ( 1 + 1 / 2 + 1 / 3 + ⋅ ⋅ ⋅ 1 / n ) = n ∫ 1 x d x = n l o g n (n+n/2+n/3+n/4+···1)= n(1+1/2+1/3+···1/n) =n\int \frac{1}{x} dx = nlogn (n+n/2+n/3+n/4+⋅⋅⋅1)=n(1+1/2+1/3+⋅⋅⋅1/n)=n∫x1dx=nlogn.
#include
using namespace std;
const int N=4e6+10;
typedef long long ll;
ll dp[N],a[N],n,m,tot;
int main()
{
cin>>n>>m;
dp[1]=1;
for(ll i=1;i<=n;i++){
a[i]=(a[i]+a[i-1])%m;
dp[i]=((dp[i]+tot)%m+a[i])%m;
for(ll j=2;j*i<=n;j++){
ll l=j*i,r=j*i+j-1;
if(r>n+1)r=n+1;
//r=min(r,n+1);
a[l]=(a[l]+dp[i])%m;
a[r+1]=(a[r+1]-dp[i]+m)%m;
}
tot=(tot+dp[i])%m;
}
cout<<dp[n]<<endl;
}
给一个序列 a = [ a 1 , a 2 , ⋅ ⋅ ⋅ , a n ] a=[a_1,a_2,···,a_n] a=[a1,a2,⋅⋅⋅,an],保证 n n n为奇数, n ≤ 2021 n\le2021 n≤2021规定每次只能对奇数做一个反转操作,即设奇数为 i i i,那么 [ 1 , i ] [1,i] [1,i]要进行反转,并且反转次数不能超过 5 n 2 \frac{5n}{2} 25n。输出每个序列要进行怎么样的反转才能使得整个序列从小到大有序,不能的输出-1。
其实很像紫书上的一道构造题,介绍构造入门的时候用的例题。不过区别在与此题之只能反转奇数并且还有反转次数限制。首先判断是否可以完成反转,可以观察到再反转的时候,奇数还是跑到奇数的位置上,偶数还是在偶数的位置上,那么就可以断定,如果就必须满足对于每一个 i i i,都有 i % 2 = a [ i ] % 2 i\%2=a[i]\%2 i%2=a[i]%2,否则就没有答案。那么剩下都是有答案的,且要在反转到奇数的同时把相邻的偶数也反转到。首先要倒着来,找到等于 i i i的 a [ j ] a[j] a[j],将 [ 1 , j ] [1,j] [1,j]进行一次反转,第二步,找到等于 i − 1 i-1 i−1的 a [ p ] a[p] a[p],反转 p − 1 p-1 p−1,此时 a [ j ] , a [ p ] a[j],a[p] a[j],a[p]相邻,此时再把 [ 1 , i ] [1,i] [1,i]反转,有 a [ p ] , a [ j ] a[p],a[j] a[p],a[j]相邻,再将 [ 1 , j ] [1,j] [1,j]反转,有 a [ j ] , a [ p ] a[j],a[p] a[j],a[p]开头的序列,此时再反转 [ 1 , i ] [1,i] [1,i],就把 i i i和 i − 1 i-1 i−1都排好了。如果对每一个奇数从大到小都来一遍是可行的,但是答案还有一个要求,就是不能超过 5 n 2 \frac{5n}{2} 25n ,此时的次数为 5 ( n + 1 ) 2 \frac{5(n+1)}{2} 25(n+1),那就不能对奇数做这样的操作,而是对偶数,操作是一致的,不过次数操作数变成了 5 ( n − 1 ) 2 \frac{5(n-1)}{2} 25(n−1),就满足了。
#include
using namespace std;
const int N=2023;
int a[N];
vector<int> ans;
int n;
int findx(int x){
for(int i=1;i<=n;i++){
if(a[i]==x)return i;
}
}
int main()
{
int t;
cin>>t;
while(t--){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
bool ok=1;
for(int i=1;i<=n;i++){
if(a[i]%2!=i%2){
//cout<
ok=0;
break;
}
}
if(!ok){
cout<<-1<<endl;
continue;
}
for(int i=n-1;i>=1;i-=2){
int j=findx(i+1);
ans.push_back(j);
reverse(a+1,a+1+j);
int p=findx(i);
ans.push_back(p-1);
reverse(a+1,a+p);
ans.push_back(i+1);
reverse(a+1,a+2+i);
j=findx(i+1);
ans.push_back(j);
reverse(a+1,a+1+j);
ans.push_back(i+1);
reverse(a+1,a+2+i);
}
int len=ans.size();
cout<<len<<endl;
for(int i=0;i<len;i++){
cout<<ans[i]<<" ";
}
if(len!=0)cout<<endl;
ans.clear();
}
}