SPOJ D-query
相同题目:
题意:
求区间内不同数字的数量
分析:
最简单题,开一个cnt数组记录数字出现的数量就行了
code:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define ll long long
using namespace std;
const int maxm=3e6+5;
struct Node{
int l,r;
int id;
}q[maxm];
int a[maxm],cnt[maxm],res[maxm];
int block;
int ans;
bool cmp(Node a,Node b){
if(a.l/block==b.l/block)return a.r<b.r;
return a.l<b.l;
}
void add(int x){
cnt[a[x]]++;
if(cnt[a[x]]==1)ans++;
}
void del(int x){
cnt[a[x]]--;
if(cnt[a[x]]==0)ans--;
}
int main(){
int n;
scanf("%d",&n);
block=sqrt(1.0*n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
int m;
scanf("%d",&m);
for(int i=1;i<=m;i++){
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
}
sort(q+1,q+1+m,cmp);
ans=0;
int l=1,r=0;
for(int i=1;i<=m;i++){
int ql=q[i].l,qr=q[i].r;
while(l<ql)del(l++);;
while(l>ql)add(--l);
while(r<qr)add(++r);
while(r>qr)del(r--);
res[q[i].id]=ans;
}
for(int i=1;i<=m;i++){
printf("%d\n",res[i]);
}
return 0;
}
P2709 小B的询问
分析:
更新的时候可以直接减去旧的a2然后a++再加上a2
也可以直接增加或者减少a2关于(a+1)2或(a-1)2的差值(这个还要算,不够无脑)
code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
typedef long long ll;
using namespace std;
const int maxm=5e4+5;
struct Node{
int l,r,id,b;
bool operator < (const Node& x)const{
if(b==x.b)return r<x.r;
return b<x.b;
}
}q[maxm];
int cmp(Node a,Node b){
if(a.b==b.b)return a.r<b.r;
return a.b<b.b;
}
int a[maxm];
int res[maxm];
int sum[maxm];
int block;
int ans;
void add(int x){
ans-=sum[a[x]]*sum[a[x]];
sum[a[x]]++;
ans+=sum[a[x]]*sum[a[x]];
}
void del(int x){
ans-=sum[a[x]]*sum[a[x]];
sum[a[x]]--;
ans+=sum[a[x]]*sum[a[x]];
}
int main(){
int n,m,k;
scanf("%d%d%d",&n,&m,&k);
block=sqrt(n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=m;i++){
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
q[i].b=(q[i].l-1)/block+1;
}
sort(q+1,q+1+m,cmp);
ans=0;
int l=1,r=0;
for(int i=1;i<=m;i++){
int ql=q[i].l;
int qr=q[i].r;
while(l<ql)del(l++);
while(l>ql)add(--l);
while(r<qr)add(++r);
while(r>qr)del(r--);
res[q[i].id]=ans;
}
for(int i=1;i<=m;i++){
cout<<res[i]<<endl;
}
return 0;
}
NBUT-1457 Sona
分析:
和上一题差不多,不过需要统计的数字大到了1e9,不能直接开这么大数组
但是n最大只有1e5,所以可以把数据离散化到1e5以内
另外这题很坑,sqrt函数里面放int会编译失败,必须放double,而且不提示在哪里错(不要问我最后怎么找到的,心态爆炸)。
CodeForces - 220B Little Elephant and Array
分析:
离散化+判断,其实和前面的也差不多
code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxm=1e5+5;
struct Node{
int l,r,id,b;
bool operator < (const Node a){
if(b==a.b)return r<a.r;
return l<a.l;
}
}q[maxm];
int res[maxm];
int a[maxm];
int xx[maxm];
int sum[maxm];
int ans;
void add(int x){
if(sum[a[x]]==xx[a[x]]){
ans--;
}
sum[a[x]]++;
if(sum[a[x]]==xx[a[x]]){
ans++;
}
}
void del(int x){
if(sum[a[x]]==xx[a[x]]){
ans--;
}
sum[a[x]]--;
if(sum[a[x]]==xx[a[x]]){
ans++;
}
}
int main(){
ios::sync_with_stdio(0);
int n,m;
cin>>n>>m;
int block=sqrt(n*1.0);
int cnt=0;
for(int i=1;i<=n;i++){
cin>>a[i];
xx[cnt++]=a[i];
}
sort(xx,xx+cnt);
cnt=unique(xx,xx+cnt)-xx;
for(int i=1;i<=n;i++){
a[i]=lower_bound(xx,xx+cnt,a[i])-xx;
}
for(int i=1;i<=m;i++){
cin>>q[i].l>>q[i].r;
q[i].id=i;
q[i].b=q[i].l/block;
}
sort(q+1,q+1+m);
int l=q[1].l,r=l-1;
for(int i=1;i<=m;i++){
int ql=q[i].l,qr=q[i].r;
while(l>ql)add(--l);
while(l<ql)del(l++);
while(r>qr)del(r--);
while(r<qr)add(++r);
res[q[i].id]=ans;
}
for(int i=1;i<=m;i++){
cout<<res[i]<<endl;
}
return 0;
}
CodeForces-86D Powerful array
题意:
求区间内:每种数的个数的平方乘上这个数(有点绕)的和(晕)
例如:
假设区间内有一个数x,他的个数为k,则ans+=k2x
每种数都加一遍
分析:
其实和开始求平方的差不多
code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<queue>
#include<algorithm>
typedef long long ll;
using namespace std;
const int maxm=1e6+5;
struct Node{
int l,r,id,b;
bool operator <(const Node &aa)const {
if(b==aa.b)return r<aa.r;
return l<aa.l;
}
}q[maxm];
ll ans;
int a[maxm],cnt[maxm];
ll res[maxm];
void add(int x){
ans-=(ll)cnt[a[x]]*cnt[a[x]]*a[x];
cnt[a[x]]++;
ans+=(ll)cnt[a[x]]*cnt[a[x]]*a[x];
}
void del(int x){
ans-=(ll)cnt[a[x]]*cnt[a[x]]*a[x];
cnt[a[x]]--;
ans+=(ll)cnt[a[x]]*cnt[a[x]]*a[x];
}
int main(){
int n,m,block;
scanf("%d%d",&n,&m);
block=sqrt(1.0*n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=m;i++){
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
q[i].b=q[i].l/block;
}
sort(q+1,q+1+m);
ans=0;
int l=q[1].l,r=q[1].l-1;
for(ll i=1;i<=m;i++){
int ql=q[i].l,qr=q[i].r;
while(l<ql)del(l++);
while(l>ql)add(--l);
while(r<qr)add(++r);
while(r>qr)del(r--);
res[q[i].id]=ans;
}
for(int i=1;i<=m;i++){
printf("%I64d\n",res[i]);
}
return 0;
}
P1494 [国家集训队]小Z的袜子
分析:
假设区间[L,R]内数量的几种袜子的数量为a,b
则概率为 (a*(a-1)/2+b*(b-1)/2) / ((R-L+1)*(R-L)/2)
(a,b如果等于1答案没有影响,因为乘上a-1就变成0了)
分子为取到两件相同的可能方案
分母为随机两件的所有可能方案
上下同时乘2去掉分母2之后变成(a*(a-1)+b*(b-1)) / ((R-L+1)*(R-L))
分子去括号变成(a2+b2-(a+b),而a+b又等于(R-L+1)
所以最后式子为(a2+b2-(R-L+1))/((R-L+1)*(R-L))
(R-L+1)可以直接算出来的,剩下的就只有和之前题目一样的平方了
记得特判0
约分的话再写一个gcd函数就行了
code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
typedef long long ll;
using namespace std;
const int maxm=1e5+5;
struct Node{
int l,r,id,b;
bool operator <(const Node &aa)const {
if(b==aa.b)return r<aa.r;
return l<aa.l;
}
}q[maxm];
int n,m,block;
ll ans;
int a[maxm],cnt[maxm];
ll x[maxm],y[maxm];
ll gcd(ll a,ll b){
return b==0?a:gcd(b,a%b);
}
void add(int x){
ans-=cnt[a[x]]*cnt[a[x]];
cnt[a[x]]++;
ans+=cnt[a[x]]*cnt[a[x]];
}
void del(int x){
ans-=cnt[a[x]]*cnt[a[x]];
cnt[a[x]]--;
ans+=cnt[a[x]]*cnt[a[x]];
}
int main(){
scanf("%d%d",&n,&m);
block=sqrt(1.0*n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
}
for(int i=1;i<=m;i++){
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
q[i].b=q[i].l/block;
}
sort(q+1,q+1+m);
ans=0;
int l=1,r=0;
for(int i=1;i<=m;i++){
int ql=q[i].l,qr=q[i].r;
while(l<ql)del(l++);
while(l>ql)add(--l);
while(r<qr)add(++r);
while(r>qr)del(r--);
int id=q[i].id;
if(ans==0){
x[id]=0,y[id]=1;
continue;
}
ll len=q[i].r-q[i].l+1;
x[id]=ans-len;
y[id]=len*(len-1);
ll d=gcd(x[id],y[id]);
x[id]/=d;
y[id]/=d;
}
for(int i=1;i<=m;i++){
printf("%lld/%lld\n",x[i],y[i]);
}
return 0;
}
P3709 大爷的字符串题
题意:
求区间内数字最多的数的个数
输出它的相反数
(原题题面太恐怖了根本看不懂,我怀疑出题人就是想恶心人。。)
分析:
开一个数组sum[i]记录数字i出现的个数
再开一个数组num[i]记录出现i次的数字的个数
能想到这两个数组就好做了
这题数据很大,需要离散化
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
typedef long long ll;
const int inf=0x3f3f3f3f;
const int inn=0x80808080;
using namespace std;
const int maxm=2e5+5;
struct Node{
int l,r,id,b;
}q[maxm];
int a[maxm];
int res[maxm];
int temp[maxm];
int sum[maxm];//数字i出现了几次
int num[maxm];//有多少个出现i次的数字
int block;
int ans;
bool cmp(Node a,Node b){
if(a.b==b.b)return a.r<b.r;
return a.l<b.l;
}
void add(int x){
num[sum[a[x]]]--;
sum[a[x]]++;
num[sum[a[x]]]++;
ans=max(ans,sum[a[x]]);//更新最大值
}
void del(int x){
if(num[sum[a[x]]]==1&&ans==sum[a[x]])ans--;
num[sum[a[x]]]--;
sum[a[x]]--;
num[sum[a[x]]]++;
}
int main(){
int n,m;
scanf("%d%d",&n,&m);
block=sqrt(n*1.0);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
temp[i]=a[i];
}
sort(temp+1,temp+1+m);
int cnt=unique(temp+1,temp+1+m)-temp;
for(int i=1;i<=n;i++){
a[i]=lower_bound(temp+1,temp+cnt,a[i])-temp;
}
for(int i=1;i<=m;i++){
scanf("%d%d",&q[i].l,&q[i].r);
q[i].id=i;
q[i].b=(q[i].l-1)/block+1;
}
sort(q+1,q+1+m,cmp);
int l=1,r=0;
ans=1;
num[1]=inf;
for(int i=1;i<=m;i++){
int ql=q[i].l,qr=q[i].r;
while(l<ql)del(l++);
while(l>ql)add(--l);
while(r<qr)add(++r);
while(r>qr)del(r--);
res[q[i].id]=ans;
}
for(int i=1;i<=m;i++){
printf("%d\n",-res[i]);
}
return 0;
}
CodeForces - 617E XOR and Favorite Number
题意:
给n,m,k,
一个长度为n的数列,m个询问,
每个询问给一个区间,问区间内有多少个数对(i,j),满足区间[i,j]异或和(a[i]^a[i+1]…a[j])=k
分析:
题目问区间异或和
显然要用到异或前缀和,区间异或和就是sum[r]^sum[l-1]
这样之后题目就变成求数对(i,j),满足sum[j]^sum[i-1]=k
根据异或的性质我们可以把这个式子变成:
sum[i-1]^k=sum[j],
或者sum[j]^l=sum[i-1]
假设更新的位置上的数为x,其实就是找区间内sum[x]^k的个数(看不懂就看代码,打字说不太明白)
开一个数组记录数字出现的个数就行了(统计个数是最简单的操作)
code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#define ll long long
using namespace std;
const int maxm=2e6+5;
struct Node{
int l,r,id,b;
bool operator < (const Node &a)const{
if(b==a.b)return r<a.r;
return l<a.l;
}
}q[maxm];
ll res[maxm];
ll ans;
int a[maxm],sum[maxm];
int n,m,k;
void add(int x){
ans+=sum[a[x]^k];
sum[a[x]]++;
}
void del(int x){
sum[a[x]]--;
ans-=sum[a[x]^k];
}
int main(){
scanf("%d%d%d",&n,&m,&k);
int block=sqrt(1.0*n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
a[i]^=a[i-1];
}
for(int i=1;i<=m;i++){
scanf("%d%d",&q[i].l,&q[i].r);
q[i].l--;
q[i].id=i;
q[i].b=q[i].l/block;
}
sort(q+1,q+1+m);
int l=q[1].l,r=l-1;
for(int i=1;i<=m;i++){
int ql=q[i].l,qr=q[i].r;
while(l<ql)del(l++);
while(l>ql)add(--l);
while(r<qr)add(++r);
while(r>qr)del(r--);
res[q[i].id]=ans;
}
for(int i=1;i<=m;i++){
printf("%lld\n",res[i]);
}
return 0;
}
CodeForces - 375D.Tree and Queries
题意:
n个数,col[i]对应第i个数的颜色,并给你他们之间的树形关系(以1为根),有m次询问,每次给出vi,ki,要求找出以点vi为根的子树上出现超过ki次的颜色数。
思路:
子树问题利用dfs序转化为序列区间问题,然后就可以上莫队了
开个数组num[x]表示出现超过x次的颜色数
code:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxm=2e5+5;
struct Node{
int l,r,id,b,k;
bool operator <(const Node &a)const{
if(b==a.b)return r<a.r;
return l<a.l;
}
}q[maxm];
int a[maxm],res[maxm],col[maxm];
int cnt[maxm];
int num[maxm];
vector<int>g[maxm];
int L[maxm],R[maxm];
int n,m;
void dfs(int x,int &id){
L[x]=++id;
a[L[x]]=col[x];
for(int i=0;i<(int)g[x].size();i++){
int v=g[x][i];
if(!L[v])dfs(v,id);
}
R[x]=id;
}
void add(int x){
cnt[a[x]]++;
num[cnt[a[x]]]++;
}
void del(int x){
num[cnt[a[x]]]--;
cnt[a[x]]--;
}
int main(){
cin>>n>>m;
int block=sqrt(1.0*n);
for(int i=1;i<=n;i++){
cin>>col[i];
}
for(int i=1;i<=n-1;i++){
int a,b;
cin>>a>>b;
g[a].push_back(b);
g[b].push_back(a);
}
int id=0;
dfs(1,id);
for(int i=1;i<=m;i++){
int vj,kj;
cin>>vj>>kj;
q[i].l=L[vj];
q[i].r=R[vj];
q[i].k=kj;
q[i].id=i;
q[i].b=q[i].l/block;
}
sort(q+1,q+1+m);
int l=q[1].l,r=l-1;
for(int i=1;i<=m;i++){
int ql=q[i].l,qr=q[i].r;
while(l<ql)del(l++);
while(l>ql)add(--l);
while(r<qr)add(++r);
while(r>qr)del(r--);
res[q[i].id]=num[q[i].k];
}
for(int i=1;i<=m;i++){
cout<<res[i]<<endl;
}
return 0;
}