1606C. Banknotes
题意:
定义 f ( x ) f(x) f(x) 为数 x x x 能被 1 0 a i 10^{a_i} 10ai 表示所用的最小个数。
例如: a i = a_i= ai={ 0 , 1 , 2 0,1,2 0,1,2 } 时, f ( 59 ) = 14 。 ( 59 = 5 ∗ 1 0 1 + 9 ∗ 1 0 0 , 5 + 9 = 14 f(59) = 14。(59=5*10^1 + 9*10^0,5+9=14 f(59)=14。(59=5∗101+9∗100,5+9=14)
给定一个数 k k k,求数 x x x 的最小值使得 f ( x ) > k f(x)>k f(x)>k。
思路:
让 f ( x ) > k f(x)>k f(x)>k,也就是说 f ( x ) = k + 1 f(x)=k+1 f(x)=k+1。
为了让数 x x x 尽量小,就要疯狂消耗前面较小的 1 0 a i 10^{a_i} 10ai。就让前面的 1 0 a i 10^{a_i} 10ai的系数尽量大。
但是不能太大,因为如果太大的话, f ( x ) f(x) f(x) 就要用后面较大的 1 0 a i 10^{a_i} 10ai表示了。
那对于一个ai,当前 1 0 a i 10^{a_i} 10ai 的系数最大为多少呢?
例如:
a i = a_i = ai={
0 , 1 , 3 0,1,3 0,1,3},那么 1 0 a i = 10^{a_i}= 10ai={
1 , 10 , 1000 1,10,1000 1,10,1000}。
- 对于第一个数1,其最大贡献值为9,构成的值为9,因为如果是10的话, f ( x ) f(x) f(x) 就要用10来表示,就不用1了。
- 对于第二个数10,其最大贡献值为99,构成的值为990,因为如果是1000的话, f ( x ) f(x) f(x)就要用1000来表示,就不用10了。
所以对于 a i a_i ai,其最大贡献值 cnt 为 1 0 a i + 1 / 1 0 a i − 1 10^{a_{i+1}}/ 10^{a_i}-1 10ai+1/10ai−1,能够构成的值为 c n t ∗ 1 0 a i cnt*10^{a_i} cnt∗10ai。
x 减去该值,继续下一位置。
如果 x 比 c n t ∗ 1 0 a i cnt*10^{a_i} cnt∗10ai 小,那么就让 cnt 为 x。
如果遍历完所有位置最后有剩余,全部用最后一个位置来贡献。
Code:
const int N = 200010, mod=1e9 + 7;
int T, n, m, a[N];
signed main(){
Ios;
cin>>T;
while(T--)
{
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>a[i];
int ans=0;
m++;
for(int i=1;i<n;i++)
{
int x=pow(10,a[i]),y=pow(10,a[i+1]);
int t=y/x-1;
if(t<=m){
ans+=t*x;
m-=t;
}
else{
ans+=m*x;
m=0;
break;
}
}
if(m) ans+=m*pow(10,a[n]);
cout<<ans<<endl;
}
return 0;
}
B. XOR Specia-LIS-t(思维)
题意:
给定一个数列 {a}。
可以将该数列分割为若干个子数列,每个子数列得到一个 b i b_i bi 值:为当前子数列中的最长上升子序列的长度。
问,这个 {b} 数组的总异或值能否为0?
思路:
当时看到“最长上升子序列”,完全就慌掉了。。心里还想,这tm才是B题!!
用到异或的一个性质:偶数个相同的数异或值为0。
bi 为当前子数列最长上升子序列的长度:
- 如果数列长度为偶数,我们可以将整个数列分割为n份,每个子数列中只有一个数,那么bi值便都是1,总的异或值为0。
- 否则,长度为奇数。偶数好考虑,所以要尽量往偶数上靠。把当前的奇数长度转为偶数。遍历一遍数组,如果存在相邻的两个位置,后面的数比前面的数大,那么可以将这两个位置分到同一个数组,贡献值仍为1。其他都挨个分割。
这样,就有偶数个bi,每个都是1,总的异或值为0。
这样分奇偶判断一下就好。
比赛时还在想,最长上升子序列怎么处理。。sb了。
Code:
#define Ios ios::sync_with_stdi
const int N = 200010, mod = 1e9 + 7;
int T, n, m, a[N];
int main(){
Ios;
cin>>T;
while(T--)
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
if(n%2==0) cout<<"Yes\n";
else
{
bool flag=0;
for(int i=1;i<n;i++){
if(a[i+1]<=a[i]) flag=1;
}
if(flag) cout<<"Yes\n";
else cout<<"No\n";
}
}
return 0;
}
1604C. Di-visible Confusion(思维)
题意:
给定一个数组,可以进行若干次下述操作,问最终数组长度能否变为0?
操作:选择一个位置 i,如果满足 a[i] % (i+1) 不为0,就可以将该位置删掉。后面的位置往前递补。
思路:
从前往后遍历每个位置 i,判断2~i+1位置,是否存在位置 j,使得 a[i] % (j+1) 不为0。
如果存在,说明这个位置能删得去,遍历下一位置。
否则,当前位置删不去,数组长度不能变为0,break。
疑问1:如果对于两个位置x,y (x<y),y满足的位置 j 在 x 满足的位置 k 前面怎么办?
因为是从前往后遍历,所以遍历到后面位置时,前面的位置可以删掉了。前面的位置都可以删,所以 后面 y 的位置可以任意确定。
疑问2:两重循环不超时?
第二重循环在满足条件时就会及时break掉,而从前往后找到一个非当前数的因数是很快的,所以总的复杂度不高。
官方题解说明,第二重循环最多循环不超过22次。
Code:
const int N = 200010, mod = 1e9 + 7;
int T, n, m, a[N];
int main(){
Ios;
cin>>T;
while(T--)
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
bool ff=0;
for(int i=1;i<=n;i++)
{
bool flag=0;
for(int j=2;j<=i+1;j++)
{
if(a[i]%j!=0){
flag=1;break;
}
}
if(flag) continue;
else{
ff=1;break;
}
}
if(ff) cout<<"No\n";
else cout<<"Yes\n";
}
return 0;
}
反思:
当时比赛的时候,这两道题都被卡了。原因是什么?当时看到题的时候,一下子没有思路,就慌掉了,总觉得这题有多复杂多复杂,一直没想怎么去解决,不敢想!
加上有队友在旁边。。
这两道题有个特点,都是判断题,判断所给数据能否有解。看来这样的题要发散思路,不能用常规的思路去想,要大胆想!大胆假设,小心求证!