noi.ac上的一套(假)NOI题

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\)

扫描二维码关注公众号,回复: 3203224 查看本文章

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;
}

咕咕咕?

猜你喜欢

转载自www.cnblogs.com/zhoushuyu/p/9650578.html
今日推荐