2021.11.14考试总结[冲刺NOIP模拟30]

T1 构造字符串

用并查集维护强制相同和强制不同的关系即可。

\(code:\)

T1
#include<bits/stdc++.h>
using namespace std;

namespace IO{
    char Ch[50];
    int read(){
        int x=0,f=1; char ch=getchar();
        while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
        while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return x*f;
    }
    void write(int x,char sp){
        int len=0;
        if(x<0) putchar('-'),x=-x;
        do{ Ch[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(Ch[i]); putchar(sp);
    }
} using namespace IO;

const int NN=1010;
int n,m,x[NN],y[NN],z[NN],res[NN];

namespace DSU{
    int fa[NN];
    bitset<NN>dif[NN],now[NN],no[NN];
    int getf(int x){ return fa[x]==x?x:fa[x]=getf(fa[x]); }
    void merge(int x,int y){
        x=getf(x); y=getf(y);
        if(x==y) return;
        dif[x]|=dif[y]; now[x]|=now[y]; fa[y]=x;
        if((dif[x]&now[x]).any()) puts("-1"),exit(0);
    }
} using namespace DSU;

signed main(){
    freopen("str.in","r",stdin);
    freopen("str.out","w",stdout);
    n=read(); m=read();
    memset(res,-1,sizeof(res));
    for(int i=1;i<=m;i++){
        x[i]=read(),y[i]=read(),z[i]=read();
        dif[x[i]+z[i]].set(y[i]+z[i]);
        dif[y[i]+z[i]].set(x[i]+z[i]);
    }
    for(int i=1;i<=n;i++) now[i].set(i),fa[i]=i;
    for(int i=1;i<=m;i++)
        for(int j=0;j<z[i];j++)
            merge(x[i]+j,y[i]+j);
    for(int i=1;i<=n;i++) if(res[i]<0){
        int f=getf(i),tmp=0;
        while(no[f][tmp]) ++tmp;
        for(int j=1;j<=n;j++) if(now[f][j]) res[j]=tmp;
        for(int j=1;j<=n;j++) if(dif[f][j])
            no[getf(j)].set(tmp);
    }
    for(int i=1;i<=n;i++) write(res[i],' ');
    return puts(""),0;
}

T2 寻宝

并查集缩点,之后 floydtarjan 缩DAG或直接重新编号爆搜就行。 \(k=100\) 怎么搞都能过。

\(code:\)

T2
#include<bits/stdc++.h>
using namespace std;

namespace IO{
    char Ch[50];
    int read(){
        int x=0,f=1; char ch=getchar();
        while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
        while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return x*f;
    }
    void write(int x,char sp){
        int len=0;
        if(x<0) putchar('-'),x=-x;
        do{ Ch[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(Ch[i]); putchar(sp);
    }
    void ckmax(int& x,int y){ x=x>y?x:y; }
    void ckmin(int& x,int y){ x=x<y?x:y; }
} using namespace IO;

const int NN=50010;
int n,m,k,q,ext,u[NN],v[NN];
bool o[NN];
char ch[NN];
int id(int x,int y){ return (x-1)*m+y; }

namespace DSU{
    int ff[NN];
    int getf(int x){ return ff[x]==x?x:ff[x]=getf(ff[x]); }
    void merge(int x,int y){
        x=getf(x); y=getf(y);
        if(x==y) return;
        ff[y]=x;
    }
} using namespace DSU;

namespace Graph{
    int idx,Idx,head[NN],Head[NN];
    struct edge{ int to,nex; }e[NN],E[NN];
    void Add(int a,int b){ E[++Idx]=(edge){b,Head[a]}; Head[a]=Idx; }
    void add(int a,int b){ e[++idx]=(edge){b,head[a]}; head[a]=idx; }
    int cnt,top,tmp,low[NN],dfn[NN],stk[NN];
    bool vis[NN];
    void tarjan(int s){
        low[s]=dfn[s]=++cnt; vis[s]=1; stk[++top]=s;
        for(int v,i=Head[s];i;i=E[i].nex)
            if(!dfn[(v=E[i].to)]) tarjan(v), ckmin(low[s],low[v]);
            else if(vis[v]) ckmin(low[s],dfn[v]);
        if(low[s]==dfn[s])
            do{
                tmp=stk[top--]; vis[tmp]=0;
                merge(tmp,s);
            }while(tmp!=s);
    }
    bool search(int s,int t){
        if(s==t) return 1;
        for(int i=head[s];i;i=e[i].nex)
            if(search(e[i].to,t)) return 1;
        return 0;
    }
} using namespace Graph;

signed main(){
    freopen("treasure.in","r",stdin);
    freopen("treasure.out","w",stdout);
    n=read(); m=read(); k=read(); q=read(); ext=n*m;
    for(int i=1;i<=n;i++){
        scanf("%s",ch+1);
        for(int j=1;j<=m;j++){
            ff[id(i,j)]=id(i,j);
            o[id(i,j)]=(ch[j]=='.');
        }
    }
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++) if(o[id(i,j)]){
            if(i>1&&o[id(i-1,j)]) merge(id(i,j),id(i-1,j));
            if(i<n&&o[id(i+1,j)]) merge(id(i,j),id(i+1,j));
            if(j>1&&o[id(i,j-1)]) merge(id(i,j),id(i,j-1));
            if(j<m&&o[id(i,j+1)]) merge(id(i,j),id(i,j+1));
        }
    for(int a,b,c,d,i=1;i<=k;i++){
        a=read(); b=read(); c=read(); d=read();
        u[i]=id(a,b); v[i]=id(c,d);
        if(getf(u[i])!=getf(v[i])) Add(getf(u[i]),getf(v[i]));
    }
    for(int i=1;i<=ext;i++) if(o[i]&&getf(i)==i&&!dfn[i]) tarjan(i);
    for(int i=1;i<=k;i++) if(getf(u[i])!=getf(v[i])) add(getf(u[i]),getf(v[i]));
    while(q--){
        int a=read(),b=read(),c=read(),d=read();
        write(search(getf(id(a,b)),getf(id(c,d))),'\n');
    }
    return 0;
}

T3 序列

不难看出对于点 \(p\) ,它左侧和右侧的最优区间互不影响,因此可以分别考虑再求和。

可以发现这个式子能看作斜率式,求 \(p\) 左侧最优区间即为求 \(max_i\leq p\left\{sufa_i-k\times sufb_i\right\}-sufa_p+k\times sufb_p\) ,其中 \(sufa,sufb\)\(a,b\) 的后缀和。

\(sufa\) 看作截距, \(sufb\) 看作斜率,就是给出若干直线,求同一横坐标最大值。离线后李超线段树即可。只插入直线时间复杂度单\(\log\)

\(code:\)

T3
#include<bits/stdc++.h>
using namespace std;

namespace IO{
    typedef long long LL;
    #define int LL
    char Ch[50];
    int read(){
        int x=0,f=1; char ch=getchar();
        while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
        while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return x*f;
    }
    void write(LL x,char sp){
        int len=0;
        if(x<0) putchar('-'),x=-x;
        do{ Ch[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(Ch[i]); putchar(sp);
    }
} using namespace IO;

const int NN=1000010,L=0,R=2e6,bs=1e6;
int n,m,k[NN],a[NN],b[NN],ans[NN];
int prea[NN],preb[NN],sufa[NN],sufb[NN];
vector<int>q[NN];

namespace  LC_Segment_Tree{
    #define ld rt<<1
    #define rd (rt<<1)|1
    struct seg{
        int k,b;
        seg(){}
        seg(int _k,int _b){ k=_k; b=_b; }
        int calc(int pos){ return k*pos+b; }
    }s[NN<<3];
    void build(int rt,int l,int r){
        s[rt]=seg(0,-1e18);
        if(l==r) return;
        int mid=l+r>>1;
        build(ld,l,mid);
        build(rd,mid+1,r);
    }
    void insert(seg val,int rt=1,int l=L,int r=R){
        int mid=l+r>>1;
        if(l==r){ if(s[rt].calc(-bs+l)<val.calc(-bs+l)) s[rt]=val; return; }
        if(s[rt].calc(-bs+l)<=val.calc(-bs+l)&&s[rt].calc(-bs+r)<=val.calc(-bs+r)){ s[rt]=val; return; }
        if(s[rt].calc(-bs+l)>=val.calc(-bs+l)&&s[rt].calc(-bs+r)>=val.calc(-bs+r)) return;
        if(s[rt].calc(-bs+mid)<val.calc(-bs+mid)) swap(s[rt],val);
        if(val.calc(-bs+l)>s[rt].calc(-bs+l)) insert(val,ld,l,mid);
        else insert(val,rd,mid+1,r);
    }
    int query(int pos,int rt=1,int l=L,int r=R){
        if(l==r) return s[rt].calc(-bs+pos);
        int mid=l+r>>1,res=s[rt].calc(-bs+pos);
        if(pos<=mid) return max(res,query(pos,ld,l,mid));
        else return max(res,query(pos,rd,mid+1,r));
    }
} using namespace LC_Segment_Tree;

signed main(){
    freopen("seq.in","r",stdin);
    freopen("seq.out","w",stdout);
    n=read(); m=read();
    for(int i=1;i<=n;i++)
        a[i]=read(),b[i]=read();
    for(int p,i=1;i<=m;i++){
        p=read(),k[i]=read(),q[p].push_back(i);
        ans[i]=a[p]-k[i]*b[p];
    }
    for(int i=1;i<=n;i++) prea[i]=prea[i-1]+a[i], preb[i]=preb[i-1]+b[i];
    for(int i=n;i>=1;i--) sufa[i]=sufa[i+1]+a[i], sufb[i]=sufb[i+1]+b[i];
    build(1,L,R);
    for(int i=1;i<=n;i++){
        insert(seg(-sufb[i],sufa[i]));
        for(int id:q[i]) ans[id]+=query(k[id]+bs)-sufa[i]+k[id]*sufb[i];
    }
    build(1,L,R);
    for(int i=n;i;i--){
        insert(seg(-preb[i],prea[i]));
        for(int id:q[i]) ans[id]+=query(k[id]+bs)-prea[i]+k[id]*preb[i];
    }
    for(int i=1;i<=m;i++) write(ans[i],'\n');
    return 0;
}

T4 构树

大离谱题。

\(Cayley\) 公式:一个大小为 \(n\) 的完全图有 \(n^{n-2}\) 种不同的生成树。可以用 \(prufer\) 序列证明。

扩展 \(cayley\) 公式:一个完全图被确定(强制存在)边分为大小为 \(a_1,a_2,a_3,\ldots,a_m\)\(m\) 个联通块,则生成树方案数为 \(n^{m-2}\prod {a_i}\) 。不会证...

于是可以通过强制一条边是否出现来统计联通块大小乘积的加和,然后再利用上面的公式计算方案,最后通过二项式反演得到最终答案。

\(res_i\) 为至少存在 \(i\) 条相同边的方案,则最终答案 \(ans_i\)

\[ans_i=\sum_{j\geq i}(-1)^{j-i}\binom{j}{i}res_j \]

考虑树形DP。一个暴力思路是设 \(f_{u,s,e}\)\(u\) 子树中, \(u\) 所在联通块大小为 \(s\) ,强制断掉 \(e\) 条边得到若干联通块,得到上述定义的 \(\sum\prod_i{a_i}\) ,最后再乘上相应的 \(n\) 幂次算方案。

这样时间复杂度肯定不对。一个经典的维度优化是将联通块大小连乘看作在每个联通块中选择一个点的方案数。那么可以设 \(f_{u,0/1,e}\) 表示 \(u\) 子树中, \(u\) 所在联通块是否选了点,强制断掉 \(e\) 条边得到若干联通块,得到的 \(\sum\prod_i{a_i}\) 。那么有转移:

\(u\to v\) 强制断掉:

  • \(f_{u,0,i}\times f_{v,1,j}\to f_{u,0,i+j}\)
  • \(f_{u,1,i}\times f_{v,1,j}\to f_{u,1,i+j}\)

\(u\to v\) 强制存在:

  • \(f_{u,0,i}\times f_{v,0,j}\to f_{u,0,i+j-1}\)
  • \(f_{u,1,i}\times f_{v,0,j}+f_{u,0,i}\times f_{v,1,j}\to f_{u,1,i+j-1}\)

直接开数组空间复杂度不对,貌似可以直接插值,但不会。。用vector一直resize卡过了。

\(code:\)

T4
#include<bits/stdc++.h>
using namespace std;

namespace IO{
    #define int long long
    int read(){
        int x=0,f=1; char ch=getchar();
        while(ch<'0'||ch>'9'){ if(ch=='-') f=-1; ch=getchar(); }
        while(ch>='0'&&ch<='9'){ x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
        return x*f;
    } char Ch[50];
    void write(int x,char sp){
        int len=0;
        if(x<0) putchar('-'),x=-x;
        do{ Ch[len++]=x%10+'0'; x/=10; }while(x);
        for(int i=len-1;~i;i--) putchar(Ch[i]); putchar(sp);
    }
} using namespace IO;

const int NN=8010,mod=1e9+7;
int n,idx,base,res[NN],siz[NN],head[NN],tmp[NN][2];
vector<int>clr,f[NN][2];
struct edge{ int to,nex; }e[NN<<1];

void pls(int& a,int b){ a+=b; a=a<0?(a+mod):(a>=mod?a-mod:a); }
void add(int a,int b){
    e[++idx]=(edge){b,head[a]}; head[a]=idx;
    e[++idx]=(edge){a,head[b]}; head[b]=idx;
}

namespace Combinatorics{
    int fac[NN],inv[NN];
    int C(int x,int y){ return x<0||y<0||x<y?0:fac[x]*inv[y]%mod*inv[x-y]%mod; }
    int qpow(int a,int b,int res=1){
        for(;b;b>>=1,a=a*a%mod)
            if(b&1) res=res*a%mod;
        return res;
    }
    void prework(){
        fac[0]=inv[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;i--) inv[i]=inv[i+1]*(i+1)%mod;
    }
} using namespace Combinatorics;

void dfs(int u,int p){
    f[u][0].resize(2); f[u][1].resize(2);
    siz[u]=f[u][0][1]=f[u][1][1]=1;
    for(int v,i=head[u];i;i=e[i].nex) if((v=e[i].to)!=p){
        dfs(v,u);
        for(int i=0;i<=siz[u]+siz[v];i++) tmp[i][0]=tmp[i][1]=0;
        for(int i=1;i<=siz[u];i++) for(int j=1;j<=siz[v];j++){
            pls(tmp[i+j][0],f[u][0][i]*f[v][1][j]%mod);
            pls(tmp[i+j][1],f[u][1][i]*f[v][1][j]%mod);
            pls(tmp[i+j-1][0],f[u][0][i]*f[v][0][j]%mod);
            pls(tmp[i+j-1][1],f[u][0][i]*f[v][1][j]%mod+f[u][1][i]*f[v][0][j]%mod-mod);
        }
        siz[u]+=siz[v];
        f[u][0].resize(siz[u]+1); swap(clr,f[v][0]); clr.clear();
        f[u][1].resize(siz[u]+1); swap(clr,f[v][1]); clr.clear();
        for(int i=0;i<=siz[u];i++) f[u][0][i]=tmp[i][0],f[u][1][i]=tmp[i][1];
    }
}

signed main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    n=read(); prework();
    for(int a,b,i=1;i<n;i++)
        a=read(),b=read(),add(a,b);
    dfs(1,0); 
    res[n-1]=base=1;
    for(int i=n-2;~i;i--,base=base*n%mod) res[i]=f[1][1][n-i]*base%mod;
    for(int i=0;i<n;i++){
        int ans=0;
        for(int j=i,base=1;j<n;j++,base=-base)
            pls(ans,res[j]*base*C(j,i)%mod);
        write(ans,' ');
    }
    return puts(""),0;
}
posted @ 2021-11-14 20:06  keen_z  阅读(48)  评论(0)    收藏  举报