区间和2
题意简述
$T$ 组数据。
每次给定长度为 $n$ 的序列,$q$ 组询问,每次询问 $l,r$ ,表示询问 $l-r$ 小的区间和。
$Tleq 10,nleq 2 imes 10^5,qleq 20$。
时间限制 $7s$
$solution:$
这种题一看就知道会是差分后二分答案吧,所以现在的问题变为求前 $k$ 小区间和。
设 $S_k=sum_{i=1}^k a_i$ 。则现在要统计的是 $S_r-xleq S_{l-1}$ 的个数,和也这样统计即可。
树状数组优化即可,时间复杂度 $O(T imes nlog^2 n imes q)=TLE$ 。
思考哪步能优化,通过简单思考发现 $S_r-xleq S_{l-1}$ 这个式子 $S$ 是单调递增的,所以直接单调队列优化即可。
时间复杂度 $O(T imes nlog n imes q)$ 。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<climits> #include<deque> #define LL long long #define int long long using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=200011; int T,a[MAXN],n,q; deque<int> Quee; namespace force{ int s[500501]; LL S[500501]; inline void Solve(){ s[0]=0; for(int i=1;i<=n;i++) S[i]=S[i-1]+a[i]; for(int i=1;i<=n;i++) for(int j=i;j<=n;j++) s[++s[0]]=S[j]-S[i-1]; sort(s+1,s+s[0]+1); S[0]=0ll;for(int i=1;i<=s[0];i++) S[i]=S[i-1]+(LL)s[i]; while(q--){ int l=read(),r=read(); printf("%lld ",S[r]-S[l-1]); } return; } } namespace std{ int N,tmp[MAXN<<1]; LL S[MAXN]; int a1[MAXN],a2[MAXN],mid,E,tot; struct Que{ int opt,kth,id,L; LL ans; }que[41]; inline int Q(int x){ while(!Quee.empty()) Quee.pop_back(); int Ans=0; for(int i=1;i<=n;i++) a1[i]=S[i],a2[i]=S[i]-x; Quee.push_back(0); for(int i=1;i<=n;i++){ while(!Quee.empty()){ if(Quee.front()<a2[i]) Quee.pop_front(); else break; } Ans+=Quee.size(); Quee.push_back(a1[i]); }return Ans; } inline LL Qsum(int x){ while(!Quee.empty()) Quee.pop_back(); int Ans=0; for(int i=1;i<=n;i++) a1[i]=S[i],a2[i]=S[i]-x; int Sum=0; Quee.push_back(0); for(int i=1;i<=n;i++){ while(!Quee.empty()){ if(Quee.front()<a2[i]) Sum-=Quee.front(),Quee.pop_front(); else break; } Ans+=Quee.size()*a1[i]-Sum; Quee.push_back(a1[i]);Sum+=a1[i]; }return Ans; } inline LL Query(int x,int id){ if(!x) return 0; int l=que[id-1].L,r=INT_MAX,Minn=INT_MAX,K; while(l<=r){ mid=l+r>>1; E=Q(mid); if(E>=x){K=E;Minn=mid,r=mid-1;} else l=mid+1; } que[id].L=Minn; int Num1=K; return (LL)Qsum(Minn)-(Num1-x)*Minn; } bool cmp1(Que x1,Que x2){return x1.kth<x2.kth;} bool cmp(Que x1,Que x2){ if(x1.id==x2.id) return x1.opt<x2.opt; return x1.id<x2.id; } inline void Solve(){ tot=0;S[0]=0; for(int i=1;i<=n;i++) S[i]=S[i-1]+a[i]; for(int i=1;i<=q;i++) que[++tot].opt=-1,que[tot].kth=read()-1,que[++tot].opt=1,que[tot].kth=read(),que[tot].id=que[tot-1].id=i; sort(que,que+tot+1,cmp1); que[0].ans=0ll; for(int i=1;i<=tot;i++) que[i].ans=Query(que[i].kth,i); sort(que,que+tot+1,cmp); for(int i=2;i<=tot;i+=2) printf("%lld ",que[i].ans-que[i-1].ans);return; } } void solve(){ n=read(),q=read(); for(int i=1;i<=n;i++) a[i]=read(); if(n<=1000){force::Solve();return;} std::Solve();return; } signed main(){ freopen("sum.in","r",stdin); freopen("sum.out","w",stdout); T=read(); while(T--) solve(); }
安全路径
题意简述
给定一棵有 $n$ 个点的树,每条边有颜色。
求满足条件的三元组 $(u,v,w)$ ,满足 $u-v,u-w,v-w$ 之间都有红边经过。
$solution:$
想过容斥原理,但是没有想到可以这么草率。
通过容斥原理我们可以简单的将问题转化为求至少要一对只能走蓝边到达。
我们将只能用蓝边互相到达的放到一个连通块中,设当前考虑的联通块为 $i$ ,其里面数量为 $g$ 。
则 $Ans=dbinom{n}{3}-sum_{i=1} dbinom{g}{3}+(n-g) imes dbinom{g}{2}$ 。
时间复杂度 $O(n)$ 。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<climits> #define int long long #define mod 1000000007 using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=1000001; int Inv[MAXN],fac[MAXN],Infac[MAXN]; void init(){ fac[0]=1;for(int i=1;i<MAXN;i++) fac[i]=fac[i-1]*i,fac[i]%=mod; Inv[1]=1;for(int i=2;i<MAXN;i++) Inv[i]=((mod-mod/i)*Inv[mod%i])%mod; Infac[0]=1;for(int i=1;i<MAXN;i++) Infac[i]=Infac[i-1]*Inv[i],Infac[i]%=mod; return; } int n; char str[MAXN]; struct node{ int u,v,w,nex; }x[MAXN<<1]; int col[MAXN],head[MAXN],cnt,Num[MAXN]; void add(int u,int v,int w){ x[cnt].u=u,x[cnt].v=v,x[cnt].w=w,x[cnt].nex=head[u],head[u]=cnt++; } int C(int a,int b){ if(a<b) return 0; return (((fac[a]*Infac[b])%mod)*Infac[a-b])%mod; } void dfs(int u,int fath,int id){ col[u]=id; for(int i=head[u];i!=-1;i=x[i].nex){ if(x[i].v==fath) continue; if(x[i].w==1) continue; dfs(x[i].v,u,id); }return; } int Mod(int x){return ((x%mod)+mod)%mod;} signed main(){ freopen("path.in","r",stdin); freopen("path.out","w",stdout); memset(head,-1,sizeof(head));init(); n=read(); for(int i=1;i<n;i++){ int u=read(),v=read(),w; scanf("%s",str+1); if(str[1]=='b') w=0;else w=1; add(u,v,w),add(v,u,w); } for(int i=1;i<=n;i++) if(!col[i]) dfs(i,0,++col[0]); for(int i=1;i<=n;i++) Num[col[i]]++; int Ans=0; for(int i=1;i<=col[0];i++) Ans+=C(Num[i],3),Ans%=mod; for(int i=1;i<=col[0];i++) Ans+=C(Num[i],2)*(n-Num[i]),Ans%=mod; printf("%lld ",Mod(C(n,3)-Ans)); }
小朋友的笑话
$solution:$
题目告诉了我们说听过 $i$ 号笑话的人再听也不会在笑。
考虑同 $set$ 维护当前听过故事的人,每次只要将要加入的 $l,r$ 与 $set$ 中的元素查交集即可,因为每次将有交集的和为一个整体保证了每个信息只会最多出现两次。
利用线段树维护区间赋值。
时间复杂度 $O(nlog n)$ 。
#include<iostream> #include<cstring> #include<cstdio> #include<algorithm> #include<climits> #include<set> using namespace std; inline int read(){ int f=1,ans=0;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-'0';c=getchar();} return f*ans; } const int MAXN=200001; struct Segment{ int tag[MAXN<<2],sum[MAXN<<2]; void init(){memset(tag,-1,sizeof(tag));} void pushdown(int k,int l,int r){ int mid=l+r>>1; sum[k<<1]=(mid-l+1)*tag[k],sum[k<<1|1]=(r-mid)*tag[k]; tag[k<<1]=tag[k],tag[k<<1|1]=tag[k]; tag[k]=-1;return; } void Modify(int k,int l,int r,int x,int y,int w){ if(x<=l&&r<=y){ sum[k]=(r-l+1)*w; tag[k]=w;return; } if(tag[k]!=-1) pushdown(k,l,r); int mid=l+r>>1; if(x<=mid) Modify(k<<1,l,mid,x,y,w); if(mid<y) Modify(k<<1|1,mid+1,r,x,y,w); sum[k]=sum[k<<1]+sum[k<<1|1]; return; } int Query(int k,int l,int r,int x,int y){ if(x<=l&&r<=y) return sum[k]; if(tag[k]!=-1) pushdown(k,l,r); int mid=l+r>>1,res=0; if(x<=mid) res+=Query(k<<1,l,mid,x,y); if(mid<y) res+=Query(k<<1|1,mid+1,r,x,y); sum[k]=sum[k<<1]+sum[k<<1|1];return res; } }segment; int n,m; set<pair<int,int> > s[MAXN]; set<pair<int,int> > ::iterator it,it1; void Modify(int x,int id,int k){ int L=max(1,x-k),R=min(n,x+k); if(s[id].empty()){ segment.Modify(1,1,n,L,R,1); s[id].insert(make_pair(L,R));return; } it=s[id].lower_bound(make_pair(L,0)); if(it!=s[id].begin()){ it--; if(it->second<L) it++; } if(it==s[id].end()){ segment.Modify(1,1,n,L,R,1); s[id].insert(make_pair(L,R));return; } segment.Modify(1,1,n,L,R,1); int nowl=min(L,it->first),nowr=R; while(it!=s[id].end()&&it->first<=R){ nowr=max(nowr,it->second); segment.Modify(1,1,n,max(L,it->first),min(R,it->second),0); it1=it; it++; s[id].erase(it1); } s[id].insert(make_pair(nowl,nowr));return; } int main(){ freopen("joke.in","r",stdin); freopen("joke.out","w",stdout); segment.init(); n=read(),m=read(); for(int i=1;i<=m;i++){ int opt=read(); if(opt==1){ int x=read(),id=read(),k=read(); Modify(x,id,k); }else{ int l=read(),r=read(); printf("%d ",segment.Query(1,1,n,l,r)); } }return 0; }