noi.ac上的一套(假)NOI题
本来想着可以刷点通过量的,结果发现好像并不是这样的。
整数
description
给你\(n,p\),要你求\(\sum_{k=1}^n\sum_{i=1}^k\sum_{j=1}^k\gcd(i,j,k) \mod p\)。
\(n\le3\times10^8\)
sol
\[\sum_{k=1}^n\sum_{i=1}^k\sum_{j=1}^k\gcd(i,j,k)=\sum_{d=1}^nd\sum_{k=1}^n\sum_{i=1}^k\sum_{j=1}^k[\gcd(i,j,k)=d]\\=\sum_{d=1}^nd\sum_{k=1}^{n/d}\sum_{i=1}^k\sum_{j=1}^k[\gcd(i,j,k)=1]\\=\sum_{d=1}^nd\sum_{i=1}^n\mu(i)\sum_{j=1}^{n/id}j^2\\=\sum_{d=1}^nd\sum_{i=1}^n\mu(i)\frac{\lfloor\frac n{id}\rfloor(\lfloor\frac n{id}\rfloor+1)(2\lfloor\frac n{id}\rfloor+1)}{6}\\=\sum_{T=1}^n\frac{\lfloor\frac nT\rfloor(\lfloor\frac nT\rfloor+1)(2\lfloor\frac nT\rfloor+1)}{6}\sum_{d|T}d\mu(\frac Td)\\=\sum_{T=1}^n\frac{\lfloor\frac nT\rfloor(\lfloor\frac nT\rfloor+1)(2\lfloor\frac nT\rfloor+1)}{6}\varphi(T)\]
前半部分数论分块,后半部分用杜教求\(\varphi(n)\)的前缀和。
code
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N = 1e6+5;
int n,mod,inv,zhi[N],pri[N],tot,phi[N],Phi[N],ans;
int fastpow(int a,int b){
int res=1;
while (b) {if (b&1) res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}
return res;
}
int S(int x){
return 1ll*x*(x+1)%mod*(x+x+1)%mod*inv%mod;
}
int P(int x){
if (x<N) return phi[x];
if (~Phi[n/x]) return Phi[n/x];
int res=0;
for (int i=2,j;i<=x;i=j+1){
j=x/(x/i);
res=(res+1ll*(j-i+1)*P(x/i))%mod;
}
res=((1ll*x*(x+1)>>1)-res+mod)%mod;
return Phi[n/x]=res;
}
int main(){
scanf("%d%d",&n,&mod);inv=fastpow(6,mod-2);
phi[1]=1;
for (int i=2;i<N;++i){
if (!zhi[i]) pri[++tot]=i,phi[i]=i-1;
for (int j=1;i*pri[j]<N;++j){
zhi[i*pri[j]]=1;
if (i%pri[j]) phi[i*pri[j]]=phi[i]*(pri[j]-1);
else {phi[i*pri[j]]=phi[i]*pri[j];break;}
}
}
for (int i=1;i<N;++i) phi[i]=(phi[i]+phi[i-1])%mod;
memset(Phi,-1,sizeof(Phi));
for (int i=1,j;i<=n;i=j+1){
j=n/(n/i);
ans=(ans+1ll*(P(j)-P(i-1)+mod)*S(n/i))%mod;
}
printf("%d\n",ans);return 0;
}
蚯蚓排队
description
有一棵\(n\)个点的树和\(m\)只蚯蚓,你需要给条蚯蚓在树上找一个节点住下来,存在\(q\)条限制,每条形如蚯蚓\(a\)跟蚯蚓\(b\)在树上居住的节点间的简单路径经过点\(c\)。求一组合法解。
\(n,m\le250,q\le5\times10^4\)
sol
\(\mbox{2-sat}\)。是HiddenRabbits这个题的弱化版。
建立\(2nm\)个变量表示第\(i\)只蚯蚓在/不在\(j\)的子树中。需要预处理一些连边。对于每条限制,可以O(度数)地连边。总边数大概是\(O(mn^2+qn)\)级别的。
code
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 2e5+5;
const int M = 3e7+5;
int n,m,q,TO[N],NXT[N],HD[N],tot,fa[N],dep[N],st[N],ed[N];
int S[255][255],to[M],nxt[M],head[N],cnt,dfn[N],low[N],tim,vis[N],s[N],bel[N],scc;
void dfs(int u,int f){
fa[u]=f;dep[u]=dep[f]+1;st[u]=++tim;
for (int e=HD[u];e;e=NXT[e])
if (TO[e]!=f) dfs(TO[e],u);
ed[u]=tim;
}
bool in(int u,int v){return st[u]>=st[v]&&st[u]<=ed[v];}
void link(int u,int v){
to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
u^=1;v^=1;swap(u,v);
to[++cnt]=v;nxt[cnt]=head[u];head[u]=cnt;
}
void Tarjan(int u){
dfn[u]=low[u]=++tim;vis[s[++s[0]]=u]=1;
for (int e=head[u];e;e=nxt[e])
if (!dfn[to[e]]) Tarjan(to[e]),low[u]=min(low[u],low[to[e]]);
else if (vis[to[e]]) low[u]=min(low[u],dfn[to[e]]);
if (dfn[u]==low[u]){
++scc;int x;
do x=s[s[0]--],bel[x]=scc,vis[x]=0;while (x^u);
}
}
int main(){
n=gi();m=gi();q=gi();
for (int i=1;i<n;++i){
int u=gi(),v=gi();
TO[++tot]=v;NXT[tot]=HD[u];HD[u]=tot;
TO[++tot]=u;NXT[tot]=HD[v];HD[v]=tot;
}
dfs(1,0);tot=-1;
for (int i=1;i<=m;++i)
for (int j=1;j<=n;++j)
S[i][j]=++tot,++tot;
for (int i=1;i<=m;++i){
link(S[i][1]^1,S[i][1]);
for (int j=2;j<=n;++j)
link(S[i][j],S[i][fa[j]]);
for (int j=2;j<=n;++j)
for (int k=j+1;k<=n;++k)
if (!in(j,k)&&!in(k,j)) link(S[i][j],S[i][k]^1);
}
while (q--){
int a=gi(),b=gi(),c=gi();
for (int e=HD[c];e;e=NXT[e])
if (TO[e]!=fa[c])
link(S[a][TO[e]],S[b][TO[e]]^1);
if (c>1) link(S[a][c]^1,S[b][c]);
}
for (int i=tim=0;i<=tot;++i) if (!dfn[i]) Tarjan(i);
for (int i=1;i<=m;++i){
int res=0;
for (int j=1;j<=n;++j)
if (bel[S[i][j]]<bel[S[i][j]^1]) res=dep[j]>dep[res]?j:res;
printf("%d ",res);
}
return puts(""),0;
}
泳池
description
有一个\(n\times m\)的矩形,每个地方可以填一个非负整数。要求使得第\(i\)行的最大值为\(a_i\),第\(i\)列的最大值为\(b_i\)。求方案数模\(10^9+9\)。
\(n,m\le200\)
sol
可以先考虑一个子问题:有一个\(n\times m\)的矩形,每个地方可以填\([0,h]\)中的一个数,要求每行每列的最大值都恰好是\(h\)。求方案数。
考虑容斥。枚举\(i\)行\(j\)列强制没有达到最大值,则有
\[Ans=\sum_{i=0}^n\sum_{j=0}^m(-1)^{i+j}\binom ni\binom mjh^{ij}(h+1)^{nm-ij}\]
回到原题。把最大值相同的那些行与列放在一起考虑,可以得到一个类似上式的结论。
code
#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 205;
const int mod = 1e9+9;
int n,m,C[N][N],a[N],b[N],Ans=1;
int fastpow(int a,int b){
int res=1;
while (b) {if (b&1) res=1ll*res*a%mod;a=1ll*a*a%mod;b>>=1;}
return res;
}
bool cmp(int i,int j){return i>j;}
int main(){
n=gi();m=gi();
for (int i=C[0][0]=1;i<N;++i)
for (int j=C[i][0]=1;j<=i;++j)
C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
for (int i=1;i<=n;++i) a[i]=gi();sort(a+1,a+n+1,cmp);
for (int i=1;i<=m;++i) b[i]=gi();sort(b+1,b+m+1,cmp);
a[n+1]=b[m+1]=-1;
for (int x=0,y=0;x<n||y<m;){
int h=max(a[x+1],b[y+1]),xx=x,yy=y,ans=0;
while (a[x+1]==h) ++x;while (b[y+1]==h) ++y;
for (int i=0;i<=x-xx;++i)
for (int j=0;j<=y-yy;++j){
int S1=(x-i)*(y-j)-xx*yy,S2=x*y-S1-xx*yy;
int res=1ll*C[x-xx][i]*C[y-yy][j]%mod*fastpow(h+1,S1)%mod*fastpow(h,S2)%mod;
if ((i+j)&1) ans=(ans-res+mod)%mod;
else ans=(ans+res)%mod;
}
Ans=1ll*Ans*ans%mod;
}
printf("%d\n",Ans);return 0;
}
游戏
description
给你一个数列\(\{a_i\}\),每次选出两个不相交的区间\([l_1,r_1],[l_2,r_2]\),要求\(r_1<l_2\),此时获得的收益是两个区间的区间最小值的最小值。求所有不同的选法的收益之和模\(10^9+7\) 。
\(n\le2\times10^5\)
sol
可以视作不存在原序列中相同的值,若存在则按下标为第二关键字排序即可。
考虑枚举一个位置\(p\),计算其作为最小值的方案数。
找到\(p\)左右两边第一个小于\(p\)的位置\(l,r\),那么两个区间的选取就有三种方式:
1、\(l_1\in[l,p],r_1\in[p,r],l_2,r_2\in[p+1,r]\);
2、\(r_2\in[p,r],l_2\in[l,p],l_1,r_1\in[l,p-1]\);
3、一个区间的左端点\(\in[l,p]\)右端点\(\in[p,r]\),另一个区间不包含于\([l,r]\),且最小值不大于\(a_p\)。
前两种好算,最后一种在计算时需要维护“另一个区间”的个数。按照权值从小到大处理即可。
复杂度\(O(n\log n)\)。
code
#include<cstdio>
#include<algorithm>
using namespace std;
int gi(){
int x=0,w=1;char ch=getchar();
while ((ch<'0'||ch>'9')&&ch!='-') ch=getchar();
if (ch=='-') w=0,ch=getchar();
while (ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar();
return w?x:-x;
}
const int N = 2e5+5;
const int mod = 1e9+7;
int n,a[N],b[N],S[N],top,L[N],R[N],sum[N],num,ans;
bool cmp(int i,int j){return a[i]==a[j]?i<j:a[i]<a[j];}
int main(){
n=gi();num=(1ll*n*(n+1)>>1)%mod;
for (int i=1;i<=n;++i) a[i]=gi(),b[i]=i;
sort(b+1,b+n+1,cmp);
for (int i=1;i<=n;++i){
while (top&&a[S[top]]>a[i]) --top;
L[i]=S[top]+1;S[++top]=i;
}
S[top=0]=n+1;
for (int i=n;i;--i){
while (top&&a[S[top]]>=a[i]) --top;
R[i]=S[top]-1;S[++top]=i;
}
for (int i=1;i<=n;++i) sum[i]=(sum[i-1]+(1ll*i*(i+1)>>1))%mod;
for (int i=1;i<=n;++i){
int p=b[i],l=L[p],r=R[p],res=(1ll*(r-l+1)*(r-l+2)>>1)%mod;
res=1ll*(p-l+1)*(r-p+1)%mod*(num-res+mod)%mod;
res=(res+1ll*(p-l+1)*sum[r-p])%mod;
res=(res+1ll*(r-p+1)*sum[p-l])%mod;
ans=(ans+1ll*res*a[p])%mod;
num=(num-1ll*(p-l+1)*(r-p+1)%mod+mod)%mod;
}
printf("%d\n",ans);return 0;
}
咕咕咕?