A. Dalton the Teacher
思路
找到元素与其下标不匹配的个数,答案即为 (个数 / 2 ) (个数/2) (个数/2)的上取整。
int a[N];
void solve()
{
int n;
cin>>n;
int cnt = 0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
if(i == a[i]) cnt++;
}
cout<<(cnt+1)/2<<endl;
}
B. Longest Divisors Interval
思路
假设
n
n
n不是
x
x
x的倍数,那么答案区间长度为
1
1
1~
x
−
1
x-1
x−1,显然从
1
1
1
开始是最优的,可以手玩小样例感受一下,那么枚举
i
i
i,从
1
1
1开始枚举即可。注意特判。
void solve()
{
int n;
cin>>n;
if(n <= 2)
{
cout<<n<<endl;
return ;
}
for(int i=1;i<=n;i++)
{
if(n%i!=0)
{
cout<<i-1<<endl;
return ;
}
}
}
C2 - Dual (Hard Version)
思路
这是一种可行的方法,将序列变为全负或全正,全负数的话,求后缀和,因为为负数, a [ i ] a[i] a[i] + + + a [ i + 1 ] a[i+1] a[i+1] < = <= <= a [ i + 1 ] a[i+1] a[i+1],符合条件。全正数同理,只不过是求前缀和。
那么如何构造出全负或全正的呢?
设数组正数个数为 c n t cnt cnt,非正数个数为 c n t t cntt cntt(将 0 0 0视作负数)
如果 c n t < = 7 cnt<=7 cnt<=7,这样我们将一个负数不断加和,加和 5 5 5次,那么其值必然小于 − 20 -20 −20,可以将任意一个正数变为负数,最大需要操作 5 + 7 = 12 5+7=12 5+7=12次,此时序列变为全负数,求后缀和,操作需要 19 19 19次,总共 31 31 31次。
如果 c n t t < = 7 cntt<=7 cntt<=7,同理即可,此时构造的序列为全正数。
其他情况,显然两者的个数的最大值小于等于 12 12 12,我们找出绝对值最大的数,假设为负数,让其他正数加上这个数,最多操作 12 12 12次,构造出全负数序列,求后缀和,操作 19 19 19次,总共 31 31 31次。
代码很多部分粘贴复制即可,带有注释。
int a[N];
void solve()
{
int n;
cin>>n;
// 正数的个数 非正数的个数
int cnt = 0 ,cntt = 0;
int minv = 1e18, maxv = -1e18;
for(int i=1;i<=n;i++)
{
cin>>a[i];
if(a[i] > 0) cnt++;
else cntt++;
minv = min(minv,a[i]), maxv = max(maxv,a[i]);
}
int res = 0; // 记录操作次数
vector<pii> s; // 记录操作序列
if(minv >= 0)
{
for(int i=2;i<=n;i++)
{
res++;
a[i] += a[i-1];
s.push_back({i,i-1});
}
cout<<res<<endl;
for(auto [x,y] : s) cout<<x<<" "<<y<<endl;
return ;
}
if(maxv <= 0)
{
for(int i=n-1;i>=1;i--)
{
res++;
a[i] += a[i+1];
s.push_back({i,i+1});
}
cout<<res<<endl;
for(auto [x,y] : s) cout<<x<<" "<<y<<endl;
return ;
}
// 正数的个数小,考虑把全部的正数变为负数
// 最大操作次数: 5 + 7 + 19 最小负数不断加和=5, 将正数变为负数=7, 求后缀和=19
if(cnt <= 7)
{
int id;
for(int i=1;i<=n;i++) if(a[i] == minv) id = i;
// 只需要最小的负数累加5次,它的值一定会小于 -20 的,可以将任意正数变为负
for(int i=1;i<=5;i++)
{
res++;
a[id] += a[id];
s.push_back({id,id});
}
// 让正数变为负数
for(int i=1;i<=n;i++)
{
if(a[i]>0)
{
res++;
a[i] += a[id];
s.push_back({i,id});
}
}
// 倒序求后缀和即可
// 因为序列是非正的,a[i] + a[i+1] <= a[i+1]
for(int i=n-1;i>=1;i--)
{
res++;
a[i] += a[i+1];
s.push_back({i,i+1});
}
}
// 粘贴复制即可
// 负数的个数小,考虑把全部的负数变为正数
else if(cntt <= 7)
{
int id;
for(int i=1;i<=n;i++) if(a[i] == maxv) id = i;
for(int i=1;i<=5;i++)
{
res++;
a[id] += a[id];
s.push_back({id,id});
}
for(int i=1;i<=n;i++)
{
if(a[i]<0)
{
res++;
a[i] += a[id];
s.push_back({i,id});
}
}
for(int i=2;i<=n;i++)
{
res++;
a[i] += a[i-1];
s.push_back({i,i-1});
}
}
// cnt > 7 && cntt > 7
// 即 max(cnt,cntt) <= 12
// 我们需要找出绝对值最大的数,假如为负数,那么需要将其余的正数全部变为负
// 最大操作次数: 12 + 19
else
{
int id;
// 目标序列 全负数
if(abs(minv) > abs(maxv))
{
for(int i=1;i<=n;i++) if(a[i] == minv) id = i;
for(int i=1;i<=n;i++)
if(a[i] > 0)
{
res++;
a[i] += a[id];
s.push_back({i,id});
}
for(int i=n-1;i>=1;i--)
{
res++;
a[i] += a[i+1];
s.push_back({i,i+1});
}
}
else
{
for(int i=1;i<=n;i++) if(a[i] == maxv) id = i;
for(int i=1;i<=n;i++)
if(a[i] < 0)
{
res++;
a[i] += a[id];
s.push_back({i,id});
}
for(int i=2;i<=n;i++)
{
res++;
a[i] += a[i-1];
s.push_back({i,i-1});
}
}
}
cout<<res<<endl;
for(auto [x,y] : s) cout<<x<<" "<<y<<endl;
}
D. Earn or Unlock
思路
发现这样一个性质
:
:
:当最后一张解锁的牌是
i
i
i时,此时的收益是
s
u
m
sum
sum
[
[
[
i
i
i
]
]
]
−
-
−
(
(
(
i
i
i
−
-
−
1
1
1
)
)
)
。
。
。
这样,我们考虑每个位置是否可以到达,用
d
p
dp
dp来求解。常规的方法是
n
2
n^2
n2的复杂度,会超时。可以用
b
i
e
s
e
t
bieset
bieset进行优化,由于最后一次用来解锁其他牌的数字可能会很大,因此数组开到
2
n
。
2n。
2n。
int a[N], s[N];
bitset<N> f;
void solve()
{
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i], s[i] = s[i-1] + a[i];
int ans = 0;
f[1] = 1;
for(int i=1;i<=n;i++)
{
if(s[i] < i) break;
f |= (f<<a[i]);
if(f[i]) ans = max(ans,s[i]-(i-1)), f[i] = 0;
}
for(int i=n+1;i<=2*n;i++)
{
if(f[i]) ans = max(ans,s[n]-(i-1)), f[i] = 0;
}
cout<<ans<<endl;
}