A. Simply Strange Sort
模拟
题意:
给你一个长度为
n
n
n (
n
n
n<=1000 且
n
n
n是奇数) 的排列;
有一种操作,定义第
i
i
i次操作:
如果
i
i
i是奇数,对所有奇数位置
p
o
s
pos
pos(
p
o
s
pos
pos<n),如果 第
p
o
s
pos
pos位大于第
p
o
s
pos
pos+1位,那么交换。
如果
i
i
i是偶数,对所有偶数位置进行上面那个操作;
求在第几次操作之后,整个序列有序。
思路:
因为给的
n
n
n很小,对于一个数,他回到原本的位置,最多需要
n
n
n次操作,那么
n
n
n个数,最多
n
²
n²
n²次(虽然不可能会达到)。
复杂度:O( T * n * n );
#include<bits/stdc++.h>
using namespace std;
int n,m;
int s[1001];
int main(){
int t;
scanf("%d",&t);
while(t--){
int ans=0;
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&s[i]);
}
while(1){
int f=1;
for(int i=1;i<=n;i++)
if(s[i]!=i)f=0;
if(f)break;
ans++;
if(ans%2==1){
for(int i=1;i<n;i+=2){
if(s[i]>s[i+1])swap(s[i],s[i+1]);
}
}else for(int i=2;i<n;i+=2){
if(s[i]>s[i+1])swap(s[i],s[i+1]);
}
}
printf("%d\n",ans);
}
return 0;
}
B. Charmed by the Game
题意:
一个游戏,两个人轮流先手,不告诉你谁在第一次游戏先手。如果玩家赢得了一局就会加一分。定义一个状态是,先手的玩家输掉了比赛。
告诉你最后两个玩家的得分
a
a
a和
b
b
b,求有多少个可能的上述状态存在,输出所有可能。
思路:
考虑枚举两个人第一局先手与否的不同情况,我们知道了谁在第一局先手,我们就知道了两个人先手的总局数,如果a先手但是得分比先手局数少,那么我们就直接知道了,a起码要在自己先手的情况下输几局。然后我们可以通过两个人在自己先手的局输,分别让对面赢,这样保证了在得分不变的情况下,对状态数贡献+2;
特殊情况:假赛的前提是,我让的分比他的得分少。
然后排序、去重、输出即可。
#include<bits/stdc++.h>
using namespace std;
int n,m;
int s[1001];
vector<int>v;
int main(){
int t;
scanf("%d",&t);
while(t--){
int a,b;scanf("%d%d",&a,&b);
n=a+b;
v.clear();
if(a==b){
for(int i=0;i<=n;i+=2)
v.push_back(i);
}else{
int p=n/2;
int q=abs(p-a);
int cnt=0;
for(int i=q;i<=n;i+=2)
{
v.push_back(i);
cnt++;
if(cnt>min(a,b))break;
}
cnt=0;
q=abs(p-b);
for(int i=q;i<=n;i+=2)
{
v.push_back(i);
cnt++;
if(cnt>min(a,b))break;
}
}
sort(v.begin(),v.end());
v.erase(unique(v.begin(),v.end()),v.end());
printf("%d\n",v.size());
for(int i=0;i<v.size();i++){
printf("%d ",v[i]);
}
printf("\n");
}
return 0;
}
C. Deep Down Below
题意:
你是一名英雄,你有一个属性值:力量。怪物有一个属性值:护甲。
对于一个关卡,英雄有n个洞穴可以选择。要通过关卡,英雄必须以一定的顺序进入所有洞穴,每个洞穴只进入一次,并安全地离开每个洞穴。当英雄进入第一洞时,他会按顺序地和怪兽战斗:每个怪兽会有护甲值
a
i
ai
ai;
当且仅当英雄的力量严格大于怪物的护甲时,英雄才能打败怪物。如果英雄不能打败他正在战斗的怪物,玩家输。
每次英雄击败怪物,英雄的力量增加1。
求最小的初始力量,使能够以某种顺序进入所有洞穴并击败所有怪物。
思路:
我们可以先预处理出所有洞穴能安全通过的最小力量。
然后排序并且合并。(如果两个洞穴的初始力量一样,那么能获得的力量增长是两个洞穴的怪物数之和)。
然后从对力量要求高的洞穴向要求低的洞穴覆盖即可。
最后答案对0取max。
#include<bits/stdc++.h>
using namespace std;
int n,m;
int s[100040];
int k[100040];
struct node{
int w,num;
}p[100040];
struct no{
int val,cnt;
}w[100040];
int cnt=0;
map<int,int>mp;
bool cmp(node a,node b){
return a.w<b.w;
}
int main(){
int t;
scanf("%d",&t);
while(t--){
int ans=0;
cnt=0;
mp.clear();
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&k[i]);
int res=0;
for(int j=1;j<=k[i];j++){
scanf("%d",&s[j]);
res=max(res,s[j]-j+2);
}
p[i].w=res;p[i].num=k[i];
}
sort(p+1,p+1+n,cmp);
for(int i=1;i<=n;i++){
if(!mp[p[i].w])mp[p[i].w]=++cnt;
w[mp[p[i].w]].cnt+=p[i].num;
w[mp[p[i].w]].val=p[i].w;
}
for(int i=cnt;i>1;i--){
w[i-1].val=max(w[i-1].val,w[i].val-w[i-1].cnt);
}
ans=max(ans,w[1].val);
printf("%d\n",ans);
for(int i=1;i<=cnt;i++)w[i].cnt=0;
}
return 0;
}
D1. Up the Strip (simplified version)
题意:
一个1*n 的网格,我们初始在n的位置,每一步有两个走法:
假设当前在x位置
1.向前走1 ~ x-1步;
2.选择一个z( 2 <= z <= x),走到 x / z (向下取整);
求走到1位置的方案数,对m取模;(对于第二个操作,选的数不同也认为是不同的操作);
思路:
很明显的 dp题。对于第一种操作,很明显,我们可以从所有
x
+
1
x+1
x+1 ~
n
n
n转移过来,可以用 后缀和 优化 dp;
复杂的是第二个操作,暴力的话复杂度是
n
2
n^2
n2级别的,做不了。
但是对于
x
/
i
x/i
x/i (
2
<
=
i
<
=
x
2<=i<=x
2<=i<=x) 它具有很优美的性质,那就是
x
/
i
x/i
x/i向下取整在 1~ 根号x 是连续的,那么我们解不等式可以很简单的得到 取1 ~ 根号x 的范围;对于其他大于根号 x 的情况,我们再暴力的取2 ~ 根号 x 来转移即可。
ops;
害,很早就想到了这个做法,可惜中间有个变量没开 long long ,判断有没有大于 根号x 的时候炸了,气啊!好想上分。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll n,m;
ll dp[200050];
ll sum=0;
int main(){
scanf("%lld%lld",&n,&m);
dp[n]=1;
for(int i=n;i>=1;i--){
dp[i]+=sum;
dp[i]%=m;
ll l=2,r=i;
ll cnt=2;
while(l<r){
ll pos=i/cnt;
while(pos>=r){
cnt++;
pos=i/cnt;
}
cnt++;
pos++;
while(pos<r&&((i/pos) != (i/r) ))
pos++;
dp[i/r]+=1LL*(r-pos+1)*dp[i];
dp[i/r]%=m;
r=pos-1;
if(r*r<=i)break;
}
for(int j=2;j<=r;j++){
dp[i/j]+=dp[i];
dp[i/j]%=m;
}
sum+=dp[i];
sum%=m;
}
printf("%lld\n",dp[1]);
return 0;
}
D2. Up the Strip
题意:
这道题是上一道的加强版,
n
<
=
4
e
6
n<=4e6
n<=4e6
思路:
算了下复杂度,这道题对于第二种操作的转移是限制在 log 级的,现在还不是很有思路,明天补;
updated:
原来枚举一个数的倍数次,复杂度是
n
l
o
g
n
nlogn
nlogn的,赛时朝这个方向想了一下,但是不会算复杂度的我放弃了这个想法。
首先,D1我们的处理方式是枚举因子然后向下转移。
即
i
<
=
x
/
j
<
i
+
1
i<=x/j<i+1
i<=x/j<i+1;
我们从下向上的方式进行思考。
首先对于第一种操作,我们还是利用后缀和优化;
变换不等式,就变成了
i
∗
j
<
=
x
<
i
∗
j
+
j
i*j<=x<i*j+j
i∗j<=x<i∗j+j;
对于第二种操作,我们发现对于一个
i
i
i我们可以枚举它的倍数
j
j
j,这样确定了
i
i
i和
j
j
j,我们就知道了他能转移过来的区间。因为他能从
i
∗
j
到
i*j到
i∗j到
i
∗
j
+
j
i*j+j
i∗j+j转移过来,我们用个后缀和数组就能实现这样的操作。
记得后端点和
n
+
1
n+1
n+1取min;
欢乐ac。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll n,m;
ll dp[4000050];
ll sum[4000050];
int main(){
scanf("%lld%lld",&n,&m);
dp[n]=1;
sum[n+1]=0;
for(ll i=n;i>=1;--i){
dp[i]+=sum[i+1];
dp[i]%=m;
for(ll j=2;j*i<=n;++j){
ll l=j*i;
ll r=min(j*i+j,n+1);
dp[i]+=(sum[l]-sum[r]);
dp[i]%=m;
}
sum[i]=sum[i+1]+dp[i];
sum[i]%=m;
}
printf("%lld\n",dp[1]);
return 0;
}
E. Bottom-Tier Reversals
题意:
给你一个排列,每次你可以选择一个奇数的结束位置 pos ,令 1 到 pos 位置翻转。问是否能在
5
∗
n
/
2
5*n/2
5∗n/2的次数内使数列有序。可以的话输出方案,不行的话输出 -1。
思路:
粗粗看了眼题目,感觉是要相邻奇数和偶数捆绑考虑的,因为我们翻转的右端点只能是奇数,那就意味着,一个偶数想去偶数位,那么就必须要保证他和他的下一位是贴贴的才行。明天补(有机会的话
ops:
第一眼看到还以为是平衡树裸题,可恶,区间翻转不是随便维护,可惜有限制。