Cf #829 Div.2
A. Technical Support
-
题意
- 给定一个以Q开头的只含有’Q’&'A’的字符串,每一个问题后面必须有回答
- 问给定的字符串是否符合要求
-
题解
- 贪心,Q后面必须有A,那么直接倒着遍历,用计数器cnt来记录A的数量,如果出现了cnt为负数,即某个Q没有回答,那么不符合要求
-
代码
#include <iostream>
using namespace std;
bool solve() {
int n,a=0; cin>>n;
string s;
cin>>s;
for(int i=n-1;i>=0;i--) {
if(s[i]=='A') a++;
else a--;
if(a<0) return 0;
}
return 1;
}
int main() {
int t; cin>>t;
while(t--) cout<<(solve() ? "Yes":"No")<<'\n';
return 0;
}
B. Kevin and Permutation
-
题意
- 构造一个n的排列,使得所有两两相邻的元素的差值的最小值最大,即使得下列式子最大
m i n i = 1 i = n ∣ p i − p i − 1 ∣ min_{i=1}^{i=n}|p_i-p_{i-1}| mini=1i=n∣pi−pi−1∣
- 构造一个n的排列,使得所有两两相邻的元素的差值的最小值最大,即使得下列式子最大
-
题解
- 贪心+构造,首位思考所有的差值如何保证最大,如果能保证所有两个数的差值都相等(平均不等式),那么此差值最大。
- 差值是两两之间的事,我们先以2个数化成一块来构造,跨过中间数区组成一个块可以保证最大,即1和n/2+1构成一个块,后面的数同理,n个数可以构造成n/2块,块内的差值为n/2。
- 块间的数如果顺着放那么块之间逆着放,即含有大的数的块放前面,这样可以保证块之间的差值不会导致最小差值变小。相反的,如果块内的数逆着放那么块就顺着放
-
代码
//块内正着放
#include <iostream>
using namespace std;
int solve() {
int n,cnt=0;
cin>>n;
int x=n/2;
for(int i=0;i<x;i++)
cout<<n-i-x<<' '<<n-i<<' ';
if(n&1) cout<<1;//注意奇数时,1当读成块
puts("");
}
int main() {
int t; cin>>t;
while(t--) solve();
return 0;
}
//块内逆着放
#include <iostream>
using namespace std;
void solve() {
int n; cin>>n;
int x=n/2;
for(int i=1;i<=x;i++)
cout<<i+x<<' '<<i<<' ';
if(n&1) cout<<n;
puts("");
}
int main() {
int t; cin>>t;
while(t--) solve();
return 0;
}
C1. Make Nonzero Sum (easy version)
-
题意
- 给定一个长度为n的只含有-1和1的数组a,可以将数组分成k个区间,每个区间的贡献为sum[ (-1)^(区间的第几个数,从0开始)*对应的数值 ]
- 求能使得k个区间的和为0的区间划分情况
-
题解
- 数学+思维,若数组长度为奇数必然无法通过划分区间使得答案为0,证明:现将所有的数都看成一个区间,那么此时的答案即为数组的和,且此时答案必然不为零且为奇数;考虑区划分区间使得答案为0,如果区间合并,那么原来是-1变为1,原来是1变为-1,对于答案的变化都是偶数,奇数±偶数依然为奇数,不可能变为0,所以奇数长度的数组不可能有答案,得证
- 若长度为偶数,那么直接每隔两项看是合并成一个区间,还是直接单独两个区间,这样必然能够构造出答案为0的区间划分。证明:对于某两项来说,如果a[i]=a[i+1]那么直接合并,此时该区间为0;如果a[i]!=a[i+1],那么划分为两个区间si+si+1=a[i]+a[i+1]=0,即这样划分构造一定可以使得此区间对于答案的贡献为0。且由于n为偶数,所以一定可以划分成整数个含两数的区间,使得每隔两项都能有对答案贡献为0,所以最终的答案为0,得证
-
代码
#include <iostream>
#include <vector>
using namespace std;
const int N=2e5+10;
typedef pair<int,int> PII;
int n,a[N];
void solve() {
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
if(n&1) {cout<<-1<<'\n'; return ;}
vector<PII> ans;
int sum=0;
for(int i=1;i<=n;i+=2) {
if(i==n) {ans.push_back({n,n}); sum+=a[i]; continue;}
if(a[i]==a[i+1]) ans.push_back({i,i+1});
else ans.push_back({i,i}),ans.push_back({i+1,i+1});
}
if(sum) {cout<<-1<<'\n';return ;}
cout<<ans.size()<<'\n';
for(auto x:ans) cout<<x.first<<' '<<x.second<<'\n';
}
int main() {
int t; cin>>t;
while(t--) solve();
return 0;
}
C2. Make Nonzero Sum (hard version)
-
题意
- 给定一个长度为n的只含有-1和1和0的数组a,可以将数组分成k个区间,每个区间的贡献为sum[ (-1)^(区间的第几个数,从0开始)*对应的数值 ]
- 求能使得k个区间的和为0的区间划分情况
-
题解
-
与简单版本一致,如果不为0的位置的数量为奇数,一定无解
-
不为0的位置数量为偶数,那么也和简单版本一致,把不为0的位置每隔两项组队去看,因为中间0的数量不会给答案做贡献,与简单版本不同是符号±与0有关,要多一点点分析奇偶性,以下用不为0的位置p1,p2进行分析
1.a[p1]=a[p2] 1)p1,p2的奇偶性不同,那么和简单版本一致,区间区间划分为[p1,p2] 2)p1,p2的奇偶性相同,有一种构造区间的方式,[p1,p2-2],[p2-1,p2] 如 : 1 0 0 |0 1 2.a[p1]!=a[p2],只需要把[p1,p2]的每一个元素都独自划分成一个区间即可, 此时一定有[p1,p2]对答案的贡献为所有元素的和,即为0
-
-
代码
#include <iostream>
#include <vector>
using namespace std;
const int N=2e5+10;
typedef pair<int,int> PII;
int a[N];
void solve() {
int n; cin>>n;
vector<PII> ans;
vector<int> pos;
for(int i=1;i<=n;i++) {
cin>>a[i];
if(a[i]) pos.push_back(i);
}
if(pos.size() & 1) {cout<<-1<<'\n'; return ;}//无解
//注意全为0的状况,pos没有记录,所以需要特判
if(pos.size()==0){cout<<1<<'\n'<<1<<' '<<n<<'\n'; return ;}
//与简单版本不同,这里要注意两项与两项之间还有0存在的可能,其间的0只需化为一段即可
if(pos[0]>1) ans.push_back({1,pos[0]-1});
for(int i=0;i<pos.size();i+=2) {
//与简单版本不同,这里要注意两项与两项之间还有0存在的可能,其间的0只需化为一段即可
if(i && pos[i]>pos[i-1]+1) ans.push_back({pos[i-1]+1,pos[i]-1});
if(a[pos[i]]==a[pos[i+1]]) {//a[p1]=a[p2]
if((pos[i]+pos[i+1])%2) ans.push_back({pos[i],pos[i+1]});//p1,p2奇偶不同
else ans.push_back({pos[i],pos[i+1]-2}),//p1,p2奇偶相同
ans.push_back({pos[i+1]-1,pos[i+1]});
}
else {//a[p1]!=a[p2]
for(int j=pos[i];j<=pos[i+1];j++) ans.push_back({j,j});
}
}
//注意尾部0也要输出
if(pos[pos.size()-1]<n) ans.push_back({pos[pos.size()-1]+1,n});
cout<<ans.size()<<'\n';
for(auto x:ans) cout<<x.first<<' '<<x.second<<'\n';
}
int main() {
int t; cin>>t;
while(t--) solve();
return 0;
}
D. Factorial Divisibility
-
题意
- 给n个数,以及一个k,问这n个数的阶乘的和是否能整除k的阶乘
-
题解
- 思维+数学,因为是看阶乘的和是否能整除,那么对于每一个数的阶乘看成一个整体,同时可以得知数若有x+1个x!,那么可以合成一个(x+1)!。如 3 3 3 2 2 2,其中有3个2!合成一个3!,此时相当于有4个3!所以合成一个4!,若k=4,那么可以整除。
- 但是若x!不能全部用于合成(x+1)!,那么不能整除,例如 3 3 3 2 2 2 2,有4个2!,合成1个3!的同时,剩余一个2!,最后n个数的阶乘和为4!+2!,此时不能整除k=4
- 所以思路就是map记录数出现的次数,再验证从1到k-1看是否能依次向上合成,据此判断是否能整除
-
代码
#include <iostream>
#include <map>
using namespace std;
map<int,int> h;
bool solve() {
int n,k,x; cin>>n>>k;
for(int i=0;i<n;i++) cin>>x,h[x]++;
for(int i=1;i<k;i++) {
if(h[i]%(i+1)) return 0;
h[i+1]+=h[i]/(i+1);
}
return 1;
}
int main() {
cout<<(solve() ? "Yes":"No")<<'\n';
return 0;
}