D. Bash and a Tough Math Puzzle
gcd线段树
注意点:询问剪枝
#include <bits/stdc++.h>
using namespace std;
const int N = 5e6+10000;
#define int long long
#define ls p<<1
#define rs p<<1|1
#define mid (l+r)/2
int con=0;
int tree[N<<2],a[N];
void up(int p)
{
tree[p]=std::__gcd(tree[ls],tree[rs]);
}
void bulid(int l,int r,int p)
{
if(l==r)
{
tree[p]=a[l];
return ;
}
bulid(l,mid,ls);
bulid(mid+1,r,rs);
up(p);
}
void fix(int l,int r,int pos,int k,int p)
{
if(l==r)
{
tree[p]=k;
return ;
}
if(mid>=pos)
{
fix(l,mid,pos,k,ls);
}
if(mid<pos)
{
fix(mid+1,r,pos,k,rs);
}
up(p);
}
void query(int l,int r,int nl,int nr,int p,int k)
{
if(con>1)
return ;
if(l==r)
{
con++;
return ;
}
if(mid>=nl&&tree[ls]%k)
{
query(l,mid,nl,nr,ls,k);
}
if(mid<nr&&tree[rs]%k)
{
query(mid+1,r,nl,nr,rs,k);
}
}
signed main()
{
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(0);
int n;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
bulid(1,n,1);
int m;
cin>>m;
for(int i=1;i<=m;i++)
{
int c;
cin>>c;
if(c==1)
{
con=0;
int l,r,k;
cin>>l>>r>>k;
query(1,n,l,r,1,k);
if(con>=2)
{
cout<<"NO"<<endl;
}
else
{
cout<<"YES"<<endl;
}
}
else
{
int pos,k;
cin>>pos>>k;
fix(1,n,pos,k,1);
}
}
return 0;
}
/*
gcd,如果有大于1个取模于k不为0,那么必然是符合方案的
*/
二分答案
这题妙
我的思路:二分出感染次数然后贪心,
最少感染次数就是所有有子节点的节点数+1,然后用一个数组b记录每个节点的儿子们在感染次数执行过程中最多能有多少个被感染,如果原本的儿子数大于b数组中的数目,那么用sum加上差值,如果sum+最少感染次数要大于二分答案,那么就返回false,否则返回true
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6+10000;
int rd[N];
int b[N];
int n;
bool cmp(int a,int b)
{
return a>b;
}
bool check(int mid)
{
for(int i=1;i<=n;i++)
b[i]=0;
int cnt=0;
for(int i=1;i<=mid;i++)
{
if(rd[i])
{
b[i]=mid-cnt;
cnt++;
}
}
if(cnt>mid)
{
return false;
}
int sum=0;
for(int i=1;i<=n;i++)
{
if(rd[i]&&rd[i]>b[i])
{
sum+=rd[i]-b[i];
}
}
if(cnt+sum>mid)
return false;
return true;
}
int main()
{
int t;
for(cin>>t;t;t--)
{
cin>>n;
for(int i=1;i<=n+1;i++)
rd[i]=0;
rd[1]++;
for(int i=1;i<n;i++)
{
int temp;
cin>>temp;
rd[temp+1]++;
}
n++;
sort(rd+1,rd+n+1,cmp);
int l=0,r=n;
while(l<=r)
{
int mid=(l+r)/2;
if(check(mid))
{
r=mid-1;
}
else
{
l=mid+1;
}
}
cout<<l<<endl;
}
return 0;
}
前缀和
发现如果区间长度为区间和就是符合题意的情况
sum[i]-sum[j-1]=i-j+1就是符合的
移项后
sum[i]-i=sum[j-1]-j+1
不难看出问题转换为了统计sum[i]-i出现的次数
#include <iostream>
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ls p<<1
#define rs p<<1|1
#define mid (l+r)/2
const int mod=1e9+7;
const int N = 1e6+100;
int a[N];
int sum[N];
int pre[N];
signed main()
{
int t;
for(cin>>t;t;t--)
{
memset(pre,0,sizeof pre);
int n;
cin>>n;
char ch;
int ans=0;
pre[100000]++;
for(int i=1;i<=n;i++)
{
cin>>ch;
a[i]=ch-'0';
sum[i]=sum[i-1]+a[i];
ans+=pre[sum[i]-i+100000];
pre[sum[i]-i+100000]++;
}
cout<<ans<<endl;
for(int i=1;i<=n;i++)
{
a[i]=sum[i]=0;
}
}
return 0;
}