CUSTACM Summer Camp 2022 Training 8
A. Yet Another Counting Problem
题意
给定两个数a、b。有q次询问,每次给出l、r,问满足条件的x个数。
x应满足的条件:
l i ≤ x ≤ r i 并且 ( ( x % a ) % b ) ≠ ( ( x % b ) % a ) l_i \leq x \leq r_i并且((x\%a)\%b)\neq((x\%b)\%a) li≤x≤ri并且((x%a)%b)=((x%b)%a)
数据范围: 1 ≤ a , b ≤ 200 , 1 ≤ q ≤ 500 , 1 ≤ l i ≤ r i ≤ 1 0 18 1 \leq a,b \leq 200,1 \leq q \leq 500,1 \leq l_i \leq r_i \leq 10^{18} 1≤a,b≤200,1≤q≤500,1≤li≤ri≤1018
思路
-
可以知道当 0 ≤ x ≤ b − 1 0 \leq x\leq b-1 0≤x≤b−1时,一定不满足上述条件
- 当 x < a 时, ( ( x % a ) % b ) = x 并且 ( ( x % b ) % a ) = x 当x<a时,((x\%a)\%b)=x并且((x\%b)\%a)=x 当x<a时,((x%a)%b)=x并且((x%b)%a)=x
- 当 x = a 时, ( ( x % a ) % b ) = 0 并且 ( ( x % b ) % a ) = x % a = 0 当x=a时,((x\%a)\%b)=0并且((x\%b)\%a)=x\%a=0 当x=a时,((x%a)%b)=0并且((x%b)%a)=x%a=0
- 当 a < x < b 时 , ( ( x % a ) % b ) = x % a 并且 ( ( x % b ) % a ) = x % a 当a<x<b时,((x\%a)\%b)=x\%a并且((x\%b)\%a)=x\%a 当a<x<b时,((x%a)%b)=x%a并且((x%b)%a)=x%a
-
当 x ≥ b 时, ( ( x % a ) % b ) = x % a , 但是 ( ( x % b ) % a ) = ? x \geq b时,((x\%a)\%b)=x\%a,但是((x\%b)\%a)=? x≥b时,((x%a)%b)=x%a,但是((x%b)%a)=?
假设 x = i ∗ a + c = j ∗ b + d , 要使 x % a = ( ( x % b ) % a ) 成立,就需要 c = d % a 假设x=i*a+c=j*b+d,要使x\%a=((x\%b)\%a)成立,就需要c=d\%a 假设x=i∗a+c=j∗b+d,要使x%a=((x%b)%a)成立,就需要c=d%a
可以发现x=k*ab的最小公倍数+d,而d的取值范围为[0,b-1]
对于计算可以用前缀和思想,求出solve®和solve(l-1)的满足条件的个数,答案就为solve®-solve(l-1)
一开始直接取硬算solve(l到r)有点写,看了题解才意思到前缀和
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a,b,q;
ll gcd(ll a,ll b){
return b==0?a:gcd(b,a%b);
}
ll solve(ll x,ll g){
ll cnt=x/g;
ll res=x%g+1;//注意+1,因为当x%g==0时的x也时不符合条件的
return x-cnt*b-min(res,b);
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;cin>>t;
while(t--){
cin>>a>>b>>q;
if(a>b)swap(a,b);
ll x=a*b/gcd(a,b);
while(q--){
ll l,r;
cin>>l>>r;
cout<<solve(r,x)-solve(l-1,x)<<endl;
}
}
}
B. Nastya and Scoreboard动态规划,思维好题
题意
给你n个数码位(字符串形式:1代表数码管亮、0代表数码管不亮),问恰好点亮k根数码管,可以得到的最大的n位数是多少。(可以有前导零)
数据范围: 1 ≤ n ≤ 2000 , 0 ≤ k ≤ 2000 1 \leq n \leq 2000,0 \leq k \leq 2000 1≤n≤2000,0≤k≤2000
tags:动态规划,思维
思路
- DP抽象意义:
dp[i][j]表示从最后一位数码位往前构造到第i位数码位时恰好点亮j根数码管能否构造出数字
- 状态转移方程:
- 先判断第i位数码位可以转化成的0-9中的数字
- 求出该转化需要点亮cnt个数码管
for(int m=cnt;m<=k;m++)dp[i][m]|=dp[i+1][m-cnt];
,其中m是从n到i已点亮的数码管数量,所以m取值可以是cnt到k,第i位数码需要点亮cnt个,那么i+1位数码恰点亮m-cnt个
- 遍历顺序:从最后一位数码位开始遍历,即从n到1
- 边界条件:
dp[n+1][0]=1;
构造第n+1位数码位时没用数码管
遍历完dp之后,判断dp[1][k]
是否为1,若不是,说明当点亮第1位数码时不能只点亮k根数码管来构造出数字,输出-1
否则每一位都可以构造数数字,从最高位、最大的数字开始遍历,相应数码位可以转换成相应数字则输出
代码有详细解释
代码
复杂度: O ( n ∗ k ) O(n*k) O(n∗k)
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e3+5;
int n,k;
string num[10]={"1110111","0010010","1011101","1011011","0111010","1101011","1101111","1010010","1111111","1111011"};
int val[maxn];//0-9的十进制形式
int st[maxn];//输入字符串的十进制形式
bool dp[maxn][maxn];
void change(){
for(int i=0;i<10;i++){
for(int j=0;j<7;j++){
if(num[i][j]-'0')val[i]|=(1<<(7-j-1));
}
}
// for(int i=0;i<10;i++)cout<<val[i]<<' ';cout<<endl;
for(int i=1;i<=n;i++){
string in;cin>>in;
for(int j=0;j<7;j++)
if(in[j]-'0')st[i]|=(1<<(7-j-1));
}
// cout<<st[1]<<endl;
}
void solve(){
//定义b[i][j]表示从最后一位数码位往前构造到第i位数码位时恰好点亮j根数码管能否构造出数字
dp[n+1][0]=1;//构造第n+1位数码没有点亮数码管
for(int i=n;i>=1;i--){
for(int j=0;j<=9;j++){
if((st[i]&val[j])==st[i]){//第i位数码可以通过点亮若干根数码管得到数码j
// cout<<"i="<<i<<" j="<<j;
int a=st[i]^val[j],cnt=0;
for(int m=0;m<7;m++)if(a&(1<<m))cnt++;//cnt就是所需点亮的数码管的数量
// cout<<" cnt="<<cnt<<endl;
for(int m=cnt;m<=k;m++)dp[i][m]|=dp[i+1][m-cnt];//注意m是从n到i已点亮的数码管数量,所以m取值是cnt到k,第i位数码需要点亮cnt个,那么i+1位数码需要点亮m-cnt个
}
}
}
if(dp[1][k]==0){//当点亮第1位数码不能只点亮k根数码管来构造处数字时,输出-1
cout<<"-1\n";
return;
}
for(int i=1;i<=n;i++){//从最高位开始
for(int j=9;j>=0;j--){//注意从最大的数开始
if((st[i]&val[j])==st[i]){
int a=st[i]^val[j],cnt=0;
for(int m=0;m<7;m++)if(a&(1<<m))cnt++;
if(dp[i+1][k-cnt]){
cout<<j;
k-=cnt;
break;
}
}
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n>>k;
change();
solve();
}
C. Circle of Monsters
题意
给你n个怪围成一圈,每个怪有ai的血并且死亡后对下一个怪造成bi点伤害(若无第i+1个怪不会对第i+2个怪造成),你每次攻击对怪造成1点伤害,问最少多少次攻击可以杀死全部怪
数据范围: 2 ≤ n ≤ 3 ∗ 1 0 5 , 1 ≤ a i , b i ≤ 1 0 12 2 \leq n \leq 3*10^5,1 \leq a_i,b_i \leq 10^{12} 2≤n≤3∗105,1≤ai,bi≤1012
tags:思维,前缀和
思路
- 最终结果取决于第一个杀死的怪:因为杀死一个怪后最优策略是带着其爆炸伤害继续杀后面的怪,若去杀前面的怪那么爆炸伤害就每完全利用好。所以选择好第一个怪那么结果也就确定了
- 枚举每个怪作为第一个杀死的怪,计算其结果去最小值
- 计算结果不能在去暴力,不然时间复杂度为
O
(
n
2
)
O(n^2)
O(n2),利用前缀和预处理
sum[i]表示杀死前i个怪需要的攻击次数
- 注意
if(a[1]-b[n]>0)sum[1]=a[1]-b[n];else sum[1]=0;
,因为在实际计算中i=1会受到i=n的爆炸伤害 - 若选择第i个怪作为第一个杀死的,那么结果为:
ans=a[i]+sum[n]-sum[i]+sum[i-1]
:(本身的血量a[i])+(杀死i+1到n的怪的攻击次数sum[n]-sum[i+1-1])+(杀死1到i-1的怪的攻击次数sum[i-1]-sum[1-1]=sum[i-1]) - 注意若第一个怪为1或n时需要特判
代码
复杂度: O ( n ) O(n) O(n)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=3e5+5;
int n;
ll h[maxn],b[maxn];
ll sum[maxn];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
for(cin>>t;t;t--){
cin>>n;
for(int i=0;i<=n;i++)sum[i]=0;
for(int i=1;i<=n;i++)cin>>h[i]>>b[i];
if(h[1]-b[n]<0)sum[1]=0;
else sum[1]=h[1]-b[n];
for(int i=2;i<=n;i++){
ll x=h[i]-b[i-1];
if(x<0)sum[i]=sum[i-1];
else sum[i]=x+sum[i-1];
}
ll mn=0x3f3f3f3f3f3f3f3f;//开大点,若只开0x3f3f3f3f会WA
for(int i=1;i<=n;i++){
ll ans=h[i];
if(i>1)ans+=sum[i-1];
if(i<n)ans+=sum[n]-sum[i];
mn=min(ans,mn);
}
cout<<mn<<endl;
}
}
D. Carousel构造好题
题意
给你n个数,其中n与1相邻。给n种数染色(用了k种颜色),相邻的不同种数必须颜色不同,问k的最小值以及染色方法(用1到k的数字表示颜色)
数据范围: 3 ≤ n ≤ 2 ∗ 1 0 5 , 1 ≤ t i ≤ 2 ∗ 1 0 5 3 \leq n \leq 2*10^5,1 \leq t_i \leq 2*10^5 3≤n≤2∗105,1≤ti≤2∗105
tags:思维,构造
思路
-
若只有一种数,
那么只用一种颜色即可
(可以用set来判断是否只有一种数) -
若n为偶数,
那么只用2种颜色,染色方法是1、2循环染色。
(因为1、2循环输出一定保证了相邻两数之间不同色) -
若n为奇数:
-
若n和n-1是同种数:
那么只需2种颜色,1到n-1依然1、2循环染色,最后n染成2
(这样保证了n(染成2)与1(染成1)一定不同色) -
若n和1是同种数:
那么只需2种颜色:1到n-1依然1、2循环染色,最后n染成1
(这样保证了n(染成1)与n-1(染成2)一定不同色) -
若t[n]!=t[n-1]&&t[n]!=t[1]:那就要想办法使n的颜色与n-1和1都不同
-
如果延续前面的染色方法:
那么需要3种颜色,1到n-1循环染色,最后n染成3
(这样保证了n(染成3)与n-1(染成2)和1(染成1)一定不同色) -
但是,比起上面那种方法还有更优解!!!:
改变1、2的循环染色
比如先1、2循环染色,中间变为2、1循环染色,最后n-1染色成为1,1染色成为1,那么只需把n染色成2就行了。这样只用了2种颜色
而为了实现改变循环:就需要要中间出现重复连续的数,在那边改变循环
-
-
思路很清晰了,但是细节有很多需要考虑(wa了蛮多次😭),下面代码可能会有点难看懂,按上述思路自己写就行
代码
复杂度: O ( n ) O(n) O(n)
#include<bits/stdc++.h>
using namespace std;
const int maxn=2e5+5;
int n;
int a[maxn];
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
for(cin>>t;t;t--){
cin>>n;
set<int>s;
int x=0,y=0,judge=0;
for(int i=1;i<=n;i++){
cin>>a[i];
s.insert(a[i]);
if(i!=1&&i!=n&&a[i]==a[i-1])x=i-1,y=i,judge=1;
}
if(s.size()==1){
cout<<1<<endl;
for(int i=0;i<n;i++)cout<<1<<' ';
cout<<endl;
continue;
}
if((n&1)==0){
cout<<2<<endl;
for(int i=0;i<n/2;i++)cout<<1<<' '<<2<<' ';
cout<<endl;
}
else{
if(a[n]==a[n-1]){
cout<<2<<endl;
for(int i=0;i<n/2;i++)cout<<1<<' '<<2<<' ';
cout<<2<<endl;
}
else if(a[n]==a[1]){
cout<<2<<endl;
for(int i=0;i<n/2;i++)cout<<1<<' '<<2<<' ';
cout<<1<<endl;
}
else if(judge){
cout<<2<<endl;
for(int i=0;i<x/2;i++)cout<<1<<' '<<2<<' ';
if(x&1){
cout<<1<<' '<<1<<' ';
for(int i=0;i<(n-y)/2;i++)cout<<2<<' '<<1<<' ';
cout<<2<<endl;
}
else {
for(int i=0;i<(n-x)/2;i++)cout<<2<<' '<<1<<' ';
cout<<2<<endl;
}
}
else {
cout<<3<<endl;
for(int i=0;i<n/2;i++)cout<<1<<' '<<2<<' ';
cout<<3<<endl;
}
}
}
}
E. Game with Chips
题意
给你一个n*m网格,有k个物品放入(sxi,syi)中,经过一系列移动,要求第i个物品经过(exi,eyi)
- 移动:所有物品上下左右UDLR移动
- 遇到墙壁原地不动
- 最终位置不必在(exi,eyi),经过就行
- 一个格子可以装多个物品
- 最多可以平移2*n*m次
数据范围: 1 ≤ n , m , k ≤ 200 1\leq n,m,k \leq 200 1≤n,m,k≤200
tags:思维,构造
思路
以前做过类似全部物品平移的题,很难去分别控制每个物品经过给定点,所有想法一般都是把所有点集中起来移在一起移动
- 集中:最终的集中的位置(绝大情况)一定在四个角落,为了方便起见移到左上角
- 那么就统计最大横纵坐标xx、yy:需要向L移xx-1步,向U移yy-1步
- 最大可能移动n+m-2次
- 因为允许的总次数为2*n*m,所以很自然的发现接下来从左上角绕S型遍历全地图就行
- 注意向D移动了n-1次
- 每一层会移动(LR交替)m-1次,n层的话就是n*m-n次
- 总移动n*m-1次
- 最大可能移动n*m+n+m-3次,一定会在2*n*m之内
代码
复杂度: O ( n ∗ m ) O(n*m) O(n∗m)
#include<bits/stdc++.h>
using namespace std;
const int maxn=2005;
int n,m,k;
int main(){
cin>>n>>m>>k;
int xx=0,yy=0;
for(int i=0;i<k;i++){
int x,y;cin>>x>>y;
xx=max(xx,x-1);
yy=max(yy,y-1);
}
for(int i=0;i<k;i++){
int x,y;
cin>>x>>y;
}
cout<<n*m-1+xx+yy<<endl;
for(int i=0;i<xx;i++)cout<<'U';
for(int i=0;i<yy;i++)cout<<'L';
for(int i=1;i<=n;i++){
if(i&1)for(int j=1;j<m;j++)cout<<'R';
else for(int j=1;j<m;j++)cout<<'L';
if(i!=n)cout<<'D';
}
}
F. Primitive Primes数论(多项式)好题
题意
给一个n个项的多项式,系数都是整数,且未知数项为[0,n-1]次,保证所有的n个系数的gcd为1。再同理给出另一个m个项的多项式。再给一个质数p,求他们两个的多项式乘积的系数中,第几次项的系数不是p(p是质数)的倍数,题目保证有解,写出其中任意一个解。
数据范围: 1 ≤ n , m ≤ 1 0 6 , 2 ≤ p ≤ 1 0 9 , 1 ≤ a i , b i ≤ 1 0 9 1 \leq n,m \leq 10^6,2 \leq p \leq 10^9,1 \leq a_i,b_i \leq 10^9 1≤n,m≤106,2≤p≤109,1≤ai,bi≤109
tags:数论(本多项式)
思路
本多项式及高斯引理
由上述可知,只用找到第一个不能倍p整除的aibj,结果为i+j
代码
复杂度: O ( n ) O(n) O(n)
#include<bits/stdc++.h>
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int n,m,p;
cin>>n>>m>>p;
int x=-1,y=-1;
for(int i=0;i<n;i++){
int in;cin>>in;
if(in%p!=0&&x==-1)x=i;
}
for(int i=0;i<m;i++){
int in;cin>>in;
if(in%p!=0&&y==-1)y=i;
}
cout<<x+y<<endl;
}
G. Moving Points树状数组好题
题意
在OX坐标轴上有n个点,每个点的坐标和速度分别为xi,vi,在任意时间之后,第i、j两点的距离最小值为d(i,j),问 ∑ 1 ≤ i , j ≤ n d ( i , j ) \sum \limits_{1 \leq i,j \leq n}d(i,j) 1≤i,j≤n∑d(i,j)
数据范围: 2 ≤ n ≤ 2 ∗ 1 0 5 , 1 ≤ x i ≤ 1 0 8 , − 1 0 8 ≤ v i ≤ 1 0 8 2 \leq n \leq 2*10^5,1 \leq x_i \leq 10^8,-10^8 \leq v_i \leq 10^8 2≤n≤2∗105,1≤xi≤108,−108≤vi≤108
tags:思维,树状数组
思路
对于x[i]<x[j]并且v[i]<=v[j]的情况,那么d(i,j)=x[j]-x[i],否则d(i,j)=0,于是就用树状数组维护坐标<x[i]并且速度<=x[i]的点(我感觉这个树状树状维护的有点玄学,想不到)
若知道有cnt个点(x[1],x[2]…x[cnt])满足坐标<x[i]并且速度<=x[i],于是有:
∑ 1 ≤ j ≤ n d ( i , j ) = ∑ 1 ≤ j ≤ c n t d ( i , j ) = ( x [ i ] − x [ 1 ] ) + ( x [ i ] − x [ 2 ] ) + . . . + ( x [ i ] − x [ c n t ] ) = c n t ∗ x [ i ] − ∑ i = 1 c n t x [ i ] = c n t ∗ x [ i ] − s u m \sum \limits_{1 \leq j \leq n}d(i,j)=\sum \limits_{1 \leq j \leq cnt}d(i,j)=(x[i]-x[1])+(x[i]-x[2])+...+(x[i]-x[cnt])=cnt*x[i]-\sum \limits_{i=1}^{cnt}x[i]=cnt*x[i]-sum 1≤j≤n∑d(i,j)=1≤j≤cnt∑d(i,j)=(x[i]−x[1])+(x[i]−x[2])+...+(x[i]−x[cnt])=cnt∗x[i]−i=1∑cntx[i]=cnt∗x[i]−sum
所以我们需要用树状数组维护的东西有cnt(满足上述条件的点的个数),sum(满足…点的坐标和)
需要的功能能为单点修改,区间插叙
速度树状数组bitv[]
- 首先需要对所有点按坐标从小到大排序*(这样就保证了坐标<x[i],下面就可以只对速度进行讨论了)*
- 对速度数组进行离散化(排序+去重),离散化后的速度数组大小就为树状数组大小
- 遍历所有的点,用二分法找出该点速度在树状树组中的位置:
int index=lower_bound(v+1,v+1+len,a[i].second)-v;
- (*区间1到index)查询(坐标<它并且速度<=它)*的点的个数:
ll cnt=query(index,bitv);
- *(单点index)*修改速度为a[i].second的个数:
update(index,1,bitv);
坐标树状数组bita[]
- 在上述步骤二得到index之后
- (区间1到index)查询(满足…点的坐标和):
ll sum=query(index,bita);
- *(单点index)*修改速度为a[i].second的点的坐标和:
update(index,a[i].first,bita);
第一次写树状数组的题目,有点难
代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
typedef pair<int,int>P;
const int maxn=2e5+5;
int n;
P a[maxn];
int v[maxn];
int len;//树状数组大小
ll bitv[maxn],bita[maxn];
int lowbit(int x){
return (x&(-x));
}
void update(int x,int val,ll*bit){
for(int i=x;i<=len;i+=lowbit(i)){
bit[i]+=val;
}
}
ll query(int x,ll*bit){
ll ans=0;
for(int i=x;i>=1;i-=lowbit(i)){
ans+=bit[i];
}
return ans;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i].first;
for(int i=1;i<=n;i++){
cin>>a[i].second;
v[i]=a[i].second;
}
sort(a+1,a+1+n);
sort(v+1,v+1+n);
len=unique(v+1,v+1+n)-v-1;
// cout<<len<<endl;
ll ans=0;
for(int i=1;i<=n;i++){
int index=lower_bound(v+1,v+1+len,a[i].second)-v;
ll cnt=query(index,bitv);
update(index,1,bitv);
ll sum=query(index,bita);
update(index,a[i].first,bita);
ans+=cnt*a[i].first-sum;
}
cout<<ans<<endl;
}
H. Perfect Keyboard思维好题
题意
给你一个字符串s,现在要求你构造一个26个小写字母的一个排列t,使得给出的字符串s中,相邻字母在你构造的排列t中也是相邻的 有就输出YES 并输出构造的排列 没有就输出NO。
数据范围: 1 ≤ t ≤ 1000 , 1 ≤ ∣ s ∣ ≤ 200 1\leq t \leq 1000,1 \leq |s| \leq 200 1≤t≤1000,1≤∣s∣≤200
tags:思维,图论
思路
在字符串s中,字符相邻说明这两字符之间有一条无向边,只要建立一个邻接矩阵就行*(注意可能多次出现相同的边,因此邻饥接矩阵比较方便)*
- 统计每个点的度,当有点的度>2时一定不能成功
- 注意样例三,当出现环时也不能成功:步骤1会排除一些环,剩下只需判断是否有“一个整个大环(首尾相连的环)”,只需判断有无度为1的点就行
判断好有解后,从度为1的点开始遍历所有边并统计字符
题解
复杂度: O ( t ∗ n ) O(t*n) O(t∗n)
#include<bits/stdc++.h>
using namespace std;
string s;
int a[30][30];
int vis[30];
vector<char>out;
void dfs(int x){
if(vis[x])return;
vis[x]=true;
out.push_back(x+'a');
for(int j=0;j<26;j++)
if(a[x][j])dfs(j);
}
void solve(){
out.clear();
memset(vis,0,sizeof(vis));
memset(a,0,sizeof(a));
map<int,int>m;
for(int i=0;i<s.length()-1;i++){
a[s[i]-'a'][s[i+1]-'a']=1;
a[s[i+1]-'a'][s[i]-'a']=1;//无向边
}
for(int i=0;i<26;i++){
for(int j=0;j<26;j++){
if(a[i][j])m[i]++;//统计度
}
}
for(int i=0;i<26;i++)if(m[i]>2){//如果有度>2不可能成功
cout<<"NO"<<endl;
return;
}
for(int i=0;i<26;i++)if(m[i]<2)dfs(i);//这里相当于判断了是否有度为1的点,若没有那么就会出现一个大环(度全为2),就会出现一些字符没用统计,在下面if判断下就行
if(out.size()<26)cout<<"NO"<<endl;
else{
cout<<"YES"<<endl;
for(int i=0;i<out.size();i++)cout<<out[i];
cout<<endl;
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
for(cin>>t;t;t--){
cin>>s;
solve();
}
}
I. Ayoub’s function
题意
给你长度为n的由01组成的字符串,其中由m个1,问含有1的字串的总数的最小值
数据范围: 1 ≤ t ≤ 1 0 5 , 1 ≤ n ≤ 1 0 9 , 0 ≤ m ≤ n 1 \leq t \leq 10^5,1 \leq n \leq 10^9,0 \leq m \leq n 1≤t≤105,1≤n≤109,0≤m≤n
tags:组合数学,思维
思路
首先数据范围很大(t组输入也包含在内),肯定是可以在O(1)时间的结果
若m=0,那么总数一定为sum=(n+1)*n/2
当m=n-1时,会产生1个不含1的子串*(就是它本身)*
当m=n-2时,如何放这两个0很关键:
- 若2个0连着,那么会产生
(2+1)*2/2
个不含1的子串 - 但是若两个0之间有1隔着,那么只会产生2个不含1的子串
于是可以发现:有1分隔的0不会相互影响,而连续的0(假设有x个)会产生(x+1)*x/2个不含1的字串
于是就要让m个1尽可能的去分隔n-m个0
代码
复杂度: O ( t ) O(t) O(t)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
for(cin>>t;t;t--){
ll n,m;
cin>>n>>m;
ll x=(n-m)/(m+1);
ll y=(n-m)%(m+1);
ll ans=(n+1)*n/2;
ans-=(x+1)*x/2*(m+1-y)+(x+2)*(x+1)/2*y;
cout<<ans<<endl;
}
}
J. Infinite Prefixes
题意
给你一个长度为n的由01组成的字符串s,令字符串t=ssswss…,也就是说t无限长
问题字符串前缀的balance=num(0)-num(1)=x的数量,若有无限个输出-1
数据范围: 1 ≤ n ≤ 1 0 5 , − 1 0 9 ≤ x ≤ 1 0 9 1 \leq n \leq 10^5,-10^9 \leq x \leq 10^9 1≤n≤105,−109≤x≤109
tags:思维
思路
看样例010010:
首先分析出前n个前缀字符串的balance
1 0 1 2 1 2
继续进行t=ss的分析
3 2 3 4 3 4 ⇨⇨⇨ 3=1+2,2=0+2,3=1+2,4=2+2,3=1+2,4=2+2:前n个balance+第一轮末尾balance2
继续进行t=sss的分析
5 4 5 6 5 6 ⇨⇨⇨ 5=1+4,4=0+4,5=1+4,6=2+4,5=1+4,6=2+4:前n个balance+第二轮末尾balance4
可以发现**前n个balance和第n个balance(令其为b[n])是关键,后面几轮的迭代都是前n个balance+(轮数-1)***b[n]
知道了大致方向就来讨论了,令ans为最终结果
- **注意:若x为0,那么空字符串也符合条件哦!!!**此时初始ans=1
- 判断前n个balance是否有等于x,更新ans
- 特别判断:若第n个balance为0,那么后面几轮的balance都是第一轮的balance
- 若ans!=0,说明前n个balance中有x,那么后面无限轮中都会有符合条件的,那么应输出-1
- 若ans=0,那么后面无限轮中都没用符合条件的,那么应该输出0
- 判断若干轮是否会出现x,只需对前n个balance(b[i])进行循环
- 若能,那么说明b[i]+(轮数-1)*b[n]=x,也就是说 ( x − b [ i ] ) % b [ n ] = = 0 (x-b[i])\%b[n]==0 (x−b[i])%b[n]==0
- 否则不能
- 注意:还需要x!=b[i]的条件,因为x==b[i]是在前面也就是第一轮已经计算完了
代码
复杂度: O ( n ) O(n) O(n)
#include<bits/stdc++.h>
using namespace std;
int n,x;
string s;
void solve(){
vector<int>v;
long long ans=0,cnt=0;
for(int i=0;i<n;i++){
if(s[i]=='0')cnt++;
else cnt--;
v.push_back(cnt);
if(cnt==x)ans++;
}
if(x==0)ans++;
if(v[n-1]==0){
if(ans!=0)cout<<-1<<endl;
else cout<<ans<<endl;
return;
}
for(int i=0;i<n;i++){
int temp=x;
temp-=v[i];
if((temp>0&&v[n-1]<0)||(temp<0)&&v[n-1]>0)continue;
if(temp%v[n-1]==0&&x!=v[i])ans++;
}
cout<<ans<<endl;
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);cout.tie(0);
int t;
for(cin>>t;t;t--){
cin>>n>>x>>s;
solve();
}
}