做题情况:
A. New World, New Me, New Array
模拟+贪心
题意:给一个有个全
元素的数组,可以将任一元素修改为
,求最少操作次数使得数组元素总和为
。
思路:每一个数都尽可能改成,符号与
相同即可。如果全部元素都改完还不行即
就输出-1。
时间复杂度:
代码如下:
#include<bits/stdc++.h>
using namespace std;
void solve(){
int n,k,p;
cin>>n>>k>>p;
int res=abs(k)/p;
if(abs(k)%p) res+=1;
if(abs(res)>n) cout<<"-1"<<endl;
else
cout<<res<<endl;
}
signed main(void){
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
int t;
cin>>t;
while(t--)
solve();
return 0;
}
B. Having Been a Treasurer in the Past, I Help Goblins Deceive
模拟
题意:给一个字符串,任意重排这个字符串,输出字串哥布林脸的最大个数,哥布林的脸如
。
思路:把’-‘对称的放在两边,’_'放在中间。结果就是。
这里由于固定,利用和一定相同积最大的性质。
时间复杂度:
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
void solve(){
int n;
cin>>n;
string s;
cin>>s;
int num1=0,num2=0;
for(int i=0;i<n;i++){
if(s[i]=='-') num1++;
if(s[i]=='_') num2++;
}
int ans=(num1/2)*num2*(num1-(num1/2));
cout<<ans<<endl;
}
signed main(void){
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
int t;
cin>>t;
while(t--)
solve();
return 0;
}
C. Creating Keys for StORages Has Become My Main Skill
构造
题意:给定数组长度,要求数组中所有元素或运算之后结果为
,定义
,可以知道
即从
开始逐一递增遍历,数组中第一次未出现的数。输出使
最大的数组。
思路:考虑的特性我们只需要从
开始往数组中填数,每次保证
,因为或运算可以交换顺序,数组中所有元素或运算之后结果为
的必要条件为
。如果一旦不能填数了那么就终止循环,后面全填
即可因为
。
需要特别注意的是循环结束条件为
。
是因为可能存在比
大的数也满足
;留两个数以防所有数组中的数异或不能等于
,即保证充分性。
时间复杂度:
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
void solve(){
int n,x;
cin>>n>>x;
int res=0;
int cnt=0;
int i;
int before=0;
for(i=0;i<=2*x && cnt<n-1;i++){
if((x|i) == x) {
cnt++;
cout<<i<<' ';
res=res|i;
if(i>1 && (i-before)!=1) break;
before=i;
}
else{
if(i>1 && (i-before)!=1) break;
continue;
}
}
while(cnt!=n){
cnt++;
if((res|i)==x) cout<<i<<' ';
else
cout<<x<<' ';
}
cout<<endl;
}
signed main(void){
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
int t;
cin>>t;
while(t--)
solve();
return 0;
}
D. For Wizards, the Exam Is Easy, but I Couldn't Handle It
变相前缀和+贪心
题意:给定长度的数组,要求进行一次操作:对子数组
,将
移动到
后面。求如何操作才能使原数组逆序对数目尽可能少。
思路:考虑将将移动到
后面,如果
,会增加一组逆序对,如果
,会减少一组逆序对。用
记录答案的优劣程度即逆序对减少的个数,越大越优。两次循环即可,分别枚举左边界与右边界,不断维护前缀和
。
时间复杂度:
代码如下:
#include <bits/stdc++.h>
using namespace std;
int a[2010];
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
int best_l = 1, best_r = 1;
int min_inv=0;
// 枚举所有可能的子数组 [l, r]
for (int l = 1; l <= n; l++) {
int inv=0;
for (int r = l+1; r <= n; r++) {
if(a[r]>a[l]) inv++;
if(a[r]<a[l]) inv--;
if (inv < min_inv) {
min_inv = inv;
best_l = l;
best_r = r;
}
}
}
cout<<best_l<<' '<<best_r<<endl;
}
signed main() {
ios::sync_with_stdio(false);
cin.tie(nullptr), cout.tie(nullptr);
int t;
cin>>t;
while(t--)
solve();
return 0;
}
E. Do You Love Your Hero and His Two-Hit Multi-Target Attacks?
数学+贪心
题意:给定,要求在一个平面坐标系输出
个点,使得有
组点满足
。
思路:当且仅当两个点在同一行或者同一列,才满足条件。
一次尽可能多的在同一行放点,如在同一行放了个点,则会为答案贡献
。不断模拟尽可能在同一行放即可,放不了去下一行放。需要注意的是不要使这些点在同一列,否则也会增加贡献而且会变得很难想了。由于棋盘足够大边界有
,所以只在行上做文章即可。
时间复杂度:
代码如下:
#include <bits/stdc++.h>
using namespace std;
// 计算C(n,2)
int C(int n) {
return n*(n - 1) / 2;
}
void solve() {
int k;
cin >> k;
vector<int> p;
int remain=k;
while (remain>0) {
int n=1;
while (C(n+1) <= remain)
n++;
p.push_back(n);
remain -= C(n);
}
int total=accumulate(p.begin(),p.end(),0);
cout<<total<<endl;
int x=0;
for (int i=0; i<p.size(); i++) {
for (int j=0; j<p[i]; j++) {
cout<<x<<" "<<i<<endl;
x++;
}
}
}
int main() {
int t;
cin >> t;
while(t--)
solve();
return 0;
}
F. Goodbye, Banker Life
数学
lucas、杨辉三角、异或的性质
题意:给定满足上述公式的”杨辉三角“,求第行的所有元素,其中每一行首元素和末元素为
。
思路:由异或的性质:,容易知道每一行的元素非
即
,并且异或操作是加法操作在模2意义上的体现(
)。
回顾杨辉三角(也叫帕斯卡三角)的性质是:。
可以知道对于每一行的中间元素,其等于
。转换为加法再模2即可。
即。
由lucas定理,则可以得到
,输出每一行的元素即可。
注意:当时还有个特别的性质
。
证明如下:
时间复杂度:
代码如下:
#include <bits/stdc++.h>
using namespace std;
int C(int n,int m){
int res=1;
for (int i=m;i>=1;i--) {
res*=n;
res/=i;
n--;
}
return res;
}
bool lucus(int n, int k) {
if(k==0) return 1;
return ((C(n%2,k%2))*lucus(n/2,k/2))%2;
}
void solve(int n, int k) {
vector<int> result(n);
for (int i = 0; i < n; ++i) {
if (lucus(n-1, i))
result[i] = k;
else
result[i] = 0;
}
for (int i = 0; i < n; ++i)
cout << result[i]<<" ";
cout<<endl;
}
int main() {
int t;
cin >> t;
while (t--) {
int n, k;
cin >> n >> k;
solve(n, k);
}
return 0;
}
G. I've Been Flipping Numbers for 300 Years and Calculated the Sum
根号分治 整除分块
题意:给定整数 ,称
为
在
进制下反转后的值。
如6在2进制下为110反转后为011即3,。现在给定上限
,要求
。由于这个值可能非常大,对答案取模
。
思路:题意非常明了,关注数据量,
,
,显然想到要开
并且时间复杂度要优化到
级别,由于答案是个求和式,
显然想到根号分治。
1. 对于 的部分
直接模拟就好, 注意字符串反转即相应位的次放倒过来了。第 位本来要乘
,现在乘
,一个循环就可以搞定。如下:
int rev(int n, int p) {
int res = 0;
while (n > 0){
res=res*p+n%p;
res%=mod;
n/=p;
}
return res;
}
....
for(int p=2;p<=k;p++){
ans=(ans+rev(n,p))%mod;
}
2. 对于 的部分
可以知道此时 在
进制下最多只有两位,则
,但是我们不能直接用这个式子为什么呢?
如果这样模拟 的部分,时间复杂度会达到
那么就会TLE了,
本人亲测orz。
如果对整除分块熟悉的话,利用,就会想到对上式变形为:
, 然后把这个式子带入求和项
, 得到式子
。
然后再利用 和
,将其化简为最简式:
。
是不是很简单orz 不熟悉整除分块的请去度娘
int sum2(int l,int r) {
int res=r-l+1;
return res%mod;
}
int sum3(int x) {
int res=x*(x+1)*(2*x+1)/6;
return res%mod;
}
....
if(k>s){
ans=ans+n*(k+start)%mod*(k-start+1)/2;
ans%=mod;
for(int l=start;l<=k;){
int r=min(n/(n/l),k);
ans=ans+(n/l)*(sum2(l,r)+2*mod-sum3(r)+sum3(l-1));
ans%=mod;
l=r+1;
}
k=s;
}
3. 对于 的部分
可以知道 在
进制下反转后还是
因为
只有一位。答案加上
即可。
if(k>n) {
ans+=(k-n)%mod*n%mod;
k=n;
}
需要注意的一个小细节:为了防止溢出,可以将 设置为题目的若干倍,这里我设的
正好是10倍,最后输出
的时候再
即可,可以避免不必要的麻烦。
我就是这样寄了好多次
程序跑得飞快~
时间复杂度:
代码如下:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e10+70;
int rev(int n, int p) {
int res = 0;
while (n > 0){
res=res*p+n%p;
res%=mod;
n/=p;
}
return res;
}
int sum2(int l,int r) {
int res=r-l+1;
return res%mod;
}
int sum3(int x) {
int res=x*(x+1)*(2*x+1)/6;
return res%mod;
}
void solve(){
int n,k;
cin>>n>>k;
int ans=0;
if(k>n) {
ans+=(k-n)%mod*n%mod;
k=n;
}
int s=sqrt(n),start=sqrt(n);
if(s*s<=n) start=s+1;
if(k>s){
ans=ans+n*(k+start)%mod*(k-start+1)/2;
ans%=mod;
for(int l=start;l<=k;){
int r=min(n/(n/l),k);
ans=ans+(n/l)*(sum2(l,r)+2*mod-sum3(r)+sum3(l-1));
ans%=mod;
l=r+1;
}
k=s;
}
for(int p=2;p<=k;p++){
ans=(ans+rev(n,p))%mod;
}
ans=ans%mod;
ans=ans%(long long)(1e9+7);
cout<<ans<<endl;
}
signed main(void){
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
int t;
cin>>t;
while(t--)
solve();
return 0;
}