CF1436 (Div.2)
$\text{A}$
考虑到对于每一个排列, $\frac{a_i}{i}$ 会被算到 $i$ 次,所以这个式子就是 $\sum\limits_{i=1}^{n} a_i$ 直接判等不等于 $m$ 即可
$code$ :
#include<cstdio> #include<cctype> #define maxn 1001001 inline int read(){ int r=0,f=0; char c; while(!isdigit(c=getchar()))f|=(c=='-'); while(isdigit(c))r=(r<<1)+(r<<3)+(c^48),c=getchar(); return f?-r:r; } int n,m; inline void work(){ n=read(),m=read(); long long sum=0; for(int i=1;i<=n;i++) sum+=read(); if(sum==1ll*m)puts("YES"); else puts("NO"); } int main(){ int t=read(); while(t--)work(); return 0; }
$\text{B}$
首先对于矩阵这个限制很容易解掉,求出一行以后就直接第 $i$ 行从第 $i$ 列输出即可
考虑一行怎么构造,场上不知道 $0$ 也可以,所以就考虑先每个位置填 $1$ 然后第 $n$ 列填哪个合数可以使得和为质数,反正 $n$ 小于等于 $100$ ,乱搞就是了
$code$ :
#include<cstdio> #include<cctype> #define maxn 1001001 inline int read(){ int r=0,f=0; char c; while(!isdigit(c=getchar()))f|=(c=='-'); while(isdigit(c))r=(r<<1)+(r<<3)+(c^48),c=getchar(); return f?-r:r; } inline bool is_prime(int x){ if(x<=1)return 0; for(int i=2;i*i<=x;i++) if(!(x%i))return 0; return 1; } int n,ans[maxn]; inline void work(){ n=read(); for(int i=1;i<=n;i++)ans[i]=1; for(int i=0;i<=99999;i++){ if(is_prime(i+1))continue; if(is_prime(n+i)){ ans[n]+=i; break; } } for(int i=1;i<=n;i++,puts("")) for(int j=i;j<=n+i-1;j++) printf("%d ",ans[(j-1)%n+1]); } int main(){ int t=read(); while(t--)work(); return 0; }
$\text{C}$
直接按题目里给的二分模拟,如果 $mid$ 小于给定的 $pos$ ,就可以随便填一个比 $x$ 小的数
如果大于就填一个比 $x$ 大的数,等于就填自己,二分完了剩下的数随便填,乘个阶乘就好了
$code$ :
#include<cstdio> #include<cctype> #define maxn 1111 #define mod 1000000007 inline int read(){ int r=0,f=0; char c; while(!isdigit(c=getchar()))f|=(c=='-'); while(isdigit(c))r=(r<<1)+(r<<3)+(c^48),c=getchar(); return f?-r:r; } int n,x,pos; long long ans=1,frac[maxn]; void dfs(int l,int r,int L,int R){ if(L<0||R<0){ ans=0; return; } if(l==r){ (ans*=frac[L+R])%=mod; return; } int mid=(l+r)>>1; if(mid<pos)(ans*=L)%=mod,dfs(mid+1,r,L-1,R); else if(mid==pos)dfs(mid+1,r,L,R); else (ans*=R)%=mod,dfs(l,mid,L,R-1); } int main(){ n=read(),x=read(),pos=read()+1; frac[0]=1; for(int i=1;i<=n;i++)frac[i]=frac[i-1]*i%mod; dfs(1,n+1,x-1,n-x); printf("%lld\n",ans); return 0; }
$\text{D}$
相当于对于每个点都要分配到子树的叶子节点上,开一个 $Max_u$ 表示 $u$ 节点的叶子节点的最大值,再开个 $sum_u$ 表示 $u$ 子树内所有叶子节点的值与最大值的差距
然后直接分配就好了,如果 $a_u>sum_u$ 就说明 $Max_u$ 要变大,尽量让它均摊下去可以使值最小
$code$ :
#include<cstdio> #include<cctype> #define maxn 202202 inline int read(){ int r=0,f=0; char c; while(!isdigit(c=getchar()))f|=(c=='-'); while(isdigit(c))r=(r<<1)+(r<<3)+(c^48),c=getchar(); return f?-r:r; } inline long long max(long long a,long long b){ return a>b?a:b; } struct E{ int v,nxt; E() {} E(int v,int nxt):v(v),nxt(nxt) {} }e[maxn]; int n,s_e,head[maxn],a[maxn],size[maxn]; long long Max[maxn],sum[maxn]; inline void a_e(int u,int v){ e[++s_e]=E(v,head[u]); head[u]=s_e; } void dfs(int u){ if(!head[u]){ size[u]=1; Max[u]=a[u]; return; } for(int i=head[u];i;i=e[i].nxt){ int v=e[i].v; dfs(v); if(Max[u]<Max[v]){ sum[u]+=(Max[v]-Max[u])*size[u]; Max[u]=Max[v]; } else sum[u]+=(Max[u]-Max[v])*size[v]; sum[u]+=sum[v]; size[u]+=size[v]; } sum[u]-=a[u]; if(sum[u]<0){ long long x=-(sum[u]-size[u]+1)/size[u]; sum[u]+=x*size[u]; Max[u]+=x; } } int main(){ n=read(); for(int i=2;i<=n;i++){ int f=read(); a_e(f,i); } for(int i=1;i<=n;i++)a[i]=read(); dfs(1); printf("%lld\n",Max[1]); return 0; }
$\text{E}$
感觉这种题没有出过不太可能吧,不过是道好题
考虑从左往右扫计算答案
考虑若一个数是一个区间的 $\text{mex}$ ,肯定这个区间中没有它,所以对于讨论到了 $a_i$ ,就看 $(lst_{a_i},i)$ 这一段区间即可
然后另一个条件就是这个区间中 $x \in [1,a_i-1]$ 都出现过了,似乎不怎么好维护,其实可以转化为 $lst_x > lst_{a_i}$ (就是最后出现的在当前区间的左端点右边)
然后线段树维护一下这个就行了
最后注意一下,扫完以后,对于 $x \in [1,n+1]$ 都要再检查一下 $(lst_x,n]$ 这个区间
$code$ :
#include<cstdio> #include<cctype> #define maxn 101101 #define ls (p<<1) #define rs ((p<<1)|1) inline int read(){ int r=0,f=0; char c; while(!isdigit(c=getchar()))f|=(c=='-'); while(isdigit(c))r=(r<<1)+(r<<3)+(c^48),c=getchar(); return f?-r:r; } inline int min(int a,int b){ return a<b?a:b; } int n,a[maxn],lst[maxn],Min[maxn<<2]; bool app[maxn]; void change(int p,int l,int r,int x,int v){ if(l==r){ Min[p]=v; return; } int mid=(l+r)>>1; if(x<=mid)change(ls,l,mid,x,v); else change(rs,mid+1,r,x,v); Min[p]=min(Min[ls],Min[rs]); } int query(int p,int l,int r,int L,int R){ if(L<=l&&r<=R)return Min[p]; int mid=(l+r)>>1; if(R<=mid)return query(ls,l,mid,L,R); else if(L>mid)return query(rs,mid+1,r,L,R); return min(query(ls,l,mid,L,R),query(rs,mid+1,r,L,R)); } int main(){ n=read(); for(int i=1;i<=n;i++)a[i]=read(); for(int i=1;i<=n;i++){ if(a[i]>1&&query(1,1,n,1,a[i]-1)>lst[a[i]])app[a[i]]=1; lst[a[i]]=i; change(1,1,n,a[i],i); } for(int i=2;i<=n+1;i++) app[i]|=(query(1,1,n,1,i-1)>lst[i]); for(int i=1;i<=n;i++) app[1]|=(a[i]>1); for(int i=1;i<=n+2;i++) if(!app[i])return printf("%d\n",i),0; return 0; }
$\text{F}$
果然当时应该听 $\text{y}\color{red}{\text{ybyyb}}$ 的话:
你这种偷懒的莫反啊(指用 $\sum\limits_{d|n} \mu(d) = [n=1]$ ),以后有些题容斥你反演不出来的啊
我当时还觉得这种很牛逼,结果就栽在这道题上了(我这种根本就想不到正解吧 qwq)
设 $g(i)$ 为 $\gcd\{A\}=i$ 的答案,所以最终答案是 $g(1)$
但是由于限制太强了, $g(i)$ 不太好直接求,我们先考虑求点别的
设 $f(i)$ 为 $i \mid \gcd\{A\}$ 的答案,就是 $f(i)=\sum\limits_{i \mid j} g(j)$
所以反演一下就可以知道 $g(i)=\sum\limits_{i \mid j} f(j) \mu(\frac{j}{i})$
于是现在就变成了如何快速求出 $f$ 了,可以首先先枚举 $i$ ,对于每个 $f(i)$ 分别算答案
对于一个 $i$ ,我们可以将题目中那种贡献的拆开来算,首先记 $S$ 为 $x$ 的倍数的可重集,那么就相当于是在 $S$ 里面选 $A$ 集合了(注意 $A$ 也是可重集)
由于贡献是 $\sum\limits_{x \in A} x \sum\limits_{y \in B} y$ ,所以我们考虑对于 $S$ 集合中选出的每一对 $x$ 和 $y$ 来分开讨论
1、$x$ 和 $y$ 是同一个元素(不是指同一个值,就是 $x$ 和 $y$ 是 $S$ 中的同一个元素),那么这对 $x$ 和 $y$ 的贡献就是 $x^2 \times (|S|-1) \times 2^{|S|-2}$
(因为 $x$ 和 $y$ 是同一个元素,如果要能产生贡献,那么一定要在 $B$ 里面,所以对于剩下 $|S|-1$ 个数,它们都可以作为那个在 $A$ 但不在 $B$ 里面的元素,然后还有 $|S|-2$ 个元素,可以随便凑成一个集合,方案数为 $2^{|S|-2}$ )
2、$x$ 和 $y$ 不是同一个元素,那么贡献就是 $x \times y \times [ (|S|-2) \times 2^{|S|-3} + 2^{|S|-2}]$ 中括号里前面的那种和第一种类似,后面那个 $2^{|S|-2}$ 其实就是 $x$ 当那个不在 $B$ 里的数
对于每一对我们知道如何计算了,但是肯定不能直接枚举 $x$ 和 $y$ ,怎么办呢?
我们发现值域小于等于 $10^5$ ,直接枚举 $i$ 的倍数时间复杂度是允许的,就可以考虑对于在 $i$ 的每个倍数之间计算答案
于是有三种选法能对答案造成贡献:
1、$x$ 和 $y$ 值相同,且是同一个元素,那么贡献就是 $x^2 \times (|S|-1) \times 2^{|S|-2} \times cnt_x$ (乘上 $cnt_x$ 是因为等于 $x$ 的值都能这样造成贡献,每个都要算一下)
2、$x$ 和 $y$ 值相同,但不是同一个元素,那么贡献就是 $x^2 \times [ (|S|-2) \times 2^{|S|-3} + 2^{|S|-2}] \times cnt_x \times (cnt_x - 1)$ (后面那个就相当于在 $cnt_x$ 个选 $2$ 个,有顺序)
3、$x$ 和 $y$ 值不同,贡献是 $x \times y \times [ (|S|-2) \times 2^{|S|-3} + 2^{|S|-2}] \times cnt_x \times cnt_y$ ,问题是我们还是不能枚举 $x$ 和 $y$ /baojin,但是发现对于一种值 $x$ ,它能贡献给其它所有的 $i$ 的倍数,于是记个 $sum=\sum\limits_{i \mid j} j \times cnt_j$ ,于是每个值 $x$ 的贡献就是 $x \times cnt_x \times (sum - x \times cnt_x)$
然后直接统计答案即可
#include<cstdio> #include<cctype> #define maxn 202202 #define mod 998244353 template<class T> inline T read(){ T r=0,f=0; char c; while(!isdigit(c=getchar()))f|=(c=='-'); while(isdigit(c))r=(r<<1)+(r<<3)+(c^48),c=getchar(); return f?-r:r; } inline long long qpow(long long a,int b){ long long ans=1; for(;b;b>>=1){ if(b&1)(ans*=a)%=mod; (a*=a)%=mod; } return ans; } namespace P{ int n,s_p,p[maxn],mu[maxn]; bool n_p[maxn]; inline void init(int N){ n=N; mu[1]=1; for(int i=2;i<=N;i++){ if(!n_p[i])p[++s_p]=i,mu[i]=-1; for(int j=1;j<=s_p&&p[j]<=n/i;j++){ n_p[i*p[j]]=1; if(!(i%p[j]))break; mu[i*p[j]]=-mu[i]; } } } } int n,m,cnt[maxn]; long long ans; int main(){ m=read<int>(); for(int i=1;i<=m;i++){ int x=read<int>(); cnt[x]=read<int>(); } P::init(n=1e5); for(int x=1;x<=n;x++){ if(!P::mu[x])continue; long long tot=0; for(int i=x;i<=n;i+=x)tot+=cnt[i]; if(tot<2)continue; long long mult=qpow(2,(tot-2)%(mod-1)); long long mul1=0,mul2=0; mul1=(tot-1)%mod*mult%mod; if(tot>2)(mul2+=(tot-2)%mod*qpow(2,(tot-3)%(mod-1))%mod)%=mod; (mul2+=mult)%=mod; long long f=0,sum=0; for(int i=x;i<=n;i+=x){ (f+=1ll*i*i%mod*cnt[i]%mod*mul1%mod)%=mod; if(cnt[i]>1)(f+=1ll*i*i%mod*mul2%mod*cnt[i]%mod*(cnt[i]-1)%mod)%=mod; (sum+=1ll*i*cnt[i]%mod)%=mod; } for(int i=x;i<=n;i+=x) (f+=1ll*i*cnt[i]%mod*((sum+mod-1ll*i*cnt[i]%mod)%mod)%mod*mul2%mod)%=mod; (ans+=(mod+P::mu[x]*f)%mod)%=mod; } printf("%lld\n",ans); return 0; }