T1
一句话题意:有一个数值为
且递增的序列,任取
个数得到一个新序列,使每个新序列上的数的数值与其对应的位置数奇偶性相同,问有多少种取法。
思路:由奇偶性相同不难发现,假如将新序列中每个数所对应的位置和数值相加,得到的一定是一个偶数,且新序列中的得到的偶数必定两两互不相同。于是令序列
,新序列中的
与原序列中的
一一对应。
则问题转化为:从
~
中选择
个偶数,问有多少种方案。等价于:从
中选择
个数有多少种方案。
预处理出组合数及其逆元即可
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int Mod=998244353;
const int N=1e6+5;
int n,m;
int fac[N],inv[N];
inline int read(){
int cnt=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
while(isdigit(c)){cnt=(cnt<<3)+(cnt<<1)+(c^48);c=getchar();}
return cnt*f;
}
void write(int x){
if(!x){puts("0");return;}
char ch[20];int tot=0;
for(;x;x/=10){ch[++tot]=x%10+'0';}
for(int i=tot;i>=1;i--){putchar(ch[i]);}
puts("");
}
int qpow(int a,int b){
int ans=1;
for(;b;b>>=1){if(b&1)ans=(ans*a)%Mod;a=(a*a)%Mod;}
return ans%Mod;
}
void pre_w(int N){
fac[0]=1;for(int i=1;i<=N;i++){fac[i]=fac[i-1]*i%Mod;}
inv[N]=qpow(fac[N],Mod-2);for(int i=N-1;i>=0;i--){inv[i]=inv[i+1]*(i+1)%Mod;}
}
int C(int m,int n){return fac[m]*inv[m-n]%Mod*inv[n]%Mod;}
signed main(){
int T;T=read();pre_w(1e6);
while(T--){
n=read(),m=read();
write(C((n+m)/2,m)%Mod);
}
return 0;
}
T2
T2本来暴力30pts愣是因为语文问题爆零,不过赛后总结大概也是对线段树本质了解的还不清楚吧TAT,前路漫漫,还要继续努力吖
注意:
,有交集的地方交集相交处是不能取的
代码:为了避免计算过程中出现分数,先统一
再最后
#include<bits/stdc++.h>
#define mid ((l+r)>>1)
using namespace std;
inline long long read(){
long long res=0,f=1;char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') f=-f;ch=getchar();}
while(isdigit(ch)) {res=(res<<1)+(res<<3)+(ch^48);ch=getchar();}
return res*f;
}
const int N=2e6+5;
int siz[N];
long long cnt[N],lmx[N],rmx[N],cur[N],sum;
inline void pushup(int k){
siz[k]+=siz[k<<1]+siz[k<<1|1];
cnt[k]+=cnt[k<<1]+cnt[k<<1|1];cur[k]+=cur[k<<1]+cur[k<<1|1];
lmx[k]+=lmx[k<<1]+lmx[k<<1|1];rmx[k]+=rmx[k<<1]+rmx[k<<1|1];
}
void build(int k,int l,int r){//是对每个节点都要处理一次系数
siz[k]=1;lmx[k]=(2*l+1);rmx[k]=(2*r-1);cnt[k]-=(long long)l*(l+1);cnt[k]-=(long long)r*(r-1);//lmx:ql项的系数,rmx:qr项的系数,cnt:ql*qr项的系数
if(l==r) return;
cnt[k]+=(long long)4*(l+1)*(r-1);cur[k]=4;lmx[k]-=4*(r-1);rmx[k]-=4*(l+1);
build(k<<1,l,mid);build(k<<1|1,mid+1,r);
pushup(k);
}
long long query(int k,int l,int r,int ql,int qr){
if(ql<=l&&r<=qr){//查询区间完全包含线段树节点区间
long long tmp=cnt[k];
tmp+=(sum-(long long)ql*ql-(long long)qr*qr)*siz[k];tmp+=lmx[k]*ql;tmp+=rmx[k]*qr;tmp+=cur[k]*ql*qr;//ql*ql,qr*qr的系数都是-1
return tmp;
}
long long tmp=sum;
//不完全包含的情况
//注意:端点是不能取到的,减去的区间数是从(ql+1)~l,qr~(r+1)
if(ql<=l) tmp-=(long long)(l-ql)*(l-ql+1);
if(r<=qr) tmp-=(long long)(qr-r)*(qr-r+1);
if(ql<=mid) tmp+=query(k<<1,l,mid,ql,qr);
if(qr>mid) tmp+=query(k<<1|1,mid+1,r,ql,qr);
return tmp;
}
int main(){
int n=read(),q=read(),op=read();
build(1,1,n);
long long ans=0;
while(q--){
long long l=((ans*op)^read())%n+1,r=((ans*op)^read())%n+1;
if(l>r) swap(l,r);
sum=(long long)(r-l+1)*(r-l+2);//先计算出总区间数,后面直接用总个数减去对其没有贡献的个数
ans=query(1,1,n,l,r)/2;
printf("%lld\n",ans);
}
return 0;
}
T3
CF878E Numbers on the blackboard
计数类问题:主要在于讨论每一个数对于答案的贡献
有
时直接赋为
即可
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
inline int read(){
int cnt=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=-f;c=getchar();}
while(isdigit(c)){cnt=(cnt<<3)+(cnt<<1)+(c^48);c=getchar();}
return cnt*f;
}
const int N=5e5+5,Mo=1e9+7;
int n,q,a[N],fa[N],pre[N];
ll sum[N],suf[N],pw[N],S[N],an[N];
struct Que{int l,r,id;}ask[N];
bool cmp(Que a,Que b) {return a.r<b.r;}//询问离线按右端点进行排序
int get(int x) {return fa[x]?fa[x]=get(fa[x]):x;}//并查集维护连通
void merge(int x,int y) {//合并两个块
fa[x]=y;pre[y]=pre[x];
int len=x-pre[x];//len当前块的长度 -> k值 sum当前块的值
if ((len>30&&sum[y]>0)||(sum[x]+(sum[y]<<len))>Mo) sum[y]=Mo;//直接赋为最大值
else sum[y]=sum[x]+(sum[y]<<len);
}
ll query(int l,int r) {return (suf[l]-suf[r+1]*pw[r-l+1]%Mo+Mo)%Mo;}//[l,块末尾]:1,2,4,8,16... [r+1,块末尾]:1,2,4... *当前pw的值,相当于乘一个系数,使后面的都被减掉
int main() {
n=read();q=read();
for(int i=1;i<=n;i++) a[i]=read();
for(int i=1;i<=q;i++) ask[i].l=read(),ask[i].r=read(),ask[i].id=i;//处理每个询问,使其离线
sort(ask+1,ask+q+1,cmp);//询问区间按右端点排序
for(int i=1;i<=n;i++) pre[i]=i-1,fa[i]=0,sum[i]=a[i];//赋初值
for(int i=n;i>=1;i--) suf[i]=((suf[i+1]<<1)+a[i]+Mo)%Mo;//处理每个后缀
pw[0]=1;for(int i=1;i<=n;i++) pw[i]=(pw[i-1]<<1)%Mo;//预处理每个k值
int j=0;
for(int i=1;i<=n;i++) {
while (pre[i]&&sum[i]>=0) merge(pre[i],i);//发现让块连通更优,于是合并这两个块
S[i]=(S[pre[i]]+(query(pre[i]+1,i)<<1))%Mo;//S 前缀和
while (ask[j+1].r==i) {//重新开始记录
int x=get(ask[++j].l);
an[ask[j].id]=(S[i]-S[x]+query(ask[j].l,x)+Mo)%Mo;
}
}
for(int i=1;i<=q;i++) printf("%lld\n",an[i]);
return 0;
}