【考试总结】2022-03-08

开幕雷击:CTT simulation

把图用 tarjan 化成 DAG,并计算每个强连通分量里面出现过的点的数量和强连通分量的大小

最直接的想法是使用一个最小链覆盖来求最小消耗,更简单的建图方式也能做

对于 scc 之间的边,直接连 (u,v,+)iTscci 的大小,ii 连反向无穷边表示可以走多点路径,S 向每个点连强连通分量里面出现过的字符数量

如果当前强连通分量是 大小大于 1 或者有出路 且每个点都是出现过的字符,那么 ii 连出现过的字符数量 1 否则是本原数量

容易发现这样子的建图符合实际含义

Code Display
const int N=1e5+10,inf=0x3f3f3f3f;
struct Network_Flow{
    struct edge{int to,nxt,lim;}e[N<<2];
    int head[N],dep[N],S,T,ecnt=1,cur[N];
    inline void adde(int u,int v,int w){
        e[++ecnt]={v,head[u],w}; head[u]=ecnt;
        return ;
    }
    inline void add(int u,int v,int w){return adde(u,v,w),adde(v,u,0);}
    inline bool bfs(){
        queue<int> q; q.push(S); rep(i,1,T) cur[i]=head[i],dep[i]=0; dep[S]=1;
        while(q.size()){
            int fr=q.front(); q.pop();
            for(int i=head[fr];i;i=e[i].nxt) if(e[i].lim){
                int t=e[i].to; if(dep[t]) continue;
                dep[t]=dep[fr]+1; q.push(t);
            }
        } return dep[T];
    }
    inline int dfs(int x,int in){
        if(x==T) return in; int out=0;
        for(int i=head[x];i;cur[x]=i,i=e[i].nxt) if(e[i].lim){
            int t=e[i].to; if(dep[t]!=dep[x]+1) continue;
            int res=dfs(t,min(in,e[i].lim));
            e[i].lim-=res; e[i^1].lim+=res;
            in-=res; out+=res;
            if(!in) break;
        }
        if(!out) dep[x]=0; return out;
    }
    inline int dinic(){
        int sum=0;
        while(bfs()) sum+=dfs(S,inf);
        return sum;
    }
    inline void init(){
        ecnt=1; S=62<<1|1; T=S+1;
        rep(i,1,T) head[i]=0;
        return ;
    }
}F;
int n,num[100];
char s[N];
inline int decode(char s){
    if(s>='a'&&s<='z') return s-'a'+1;
    if(s>='A'&&s<='Z') return s-'A'+27;
    return s-'0'+53;
}
vector<int> G[N];
int dfn[N],low[N],scc,bel[N],stk[N],siz[N],cnt[N],top,tim;
bool ins[N],out[N];
inline void tarjan(int x){
    ins[stk[++top]=x]=1; dfn[x]=low[x]=++tim;
    for(auto t:G[x]){
        if(!dfn[t]) tarjan(t),ckmin(low[x],low[t]);
        else if(ins[t]) ckmin(low[x],dfn[t]);
    }
    if(dfn[x]==low[x]){
        ++scc;
        do{
            cnt[scc]+=num[stk[top]];
            siz[scc]++;
            ins[stk[top]]=0;
            bel[stk[top]]=scc;
        }while(stk[top--]!=x);
    } return ;
}
signed main(){
    freopen("graph.in","r",stdin); freopen("graph.out","w",stdout);
    int Test=read(); while(Test--){
        scanf("%s",s+1); n=strlen(s+1);
        rep(i,1,62){
            num[i]=0;
            bel[i]=dfn[i]=low[i]=siz[i]=cnt[i]=0;
            ins[i]=out[i]=0;
            G[i].clear();
        }
        top=tim=scc=0;
        rep(i,1,n) num[decode(s[i])]=1;
        F.init();
        int m=read();
        while(m--){
            char lnk[10];
            scanf("%s",lnk+1);
            int v1=decode(lnk[1]),v2=decode(lnk[2]);
            G[v1].push_back(v2); 
        }
        rep(i,1,62) if(!dfn[i]) tarjan(i);
        for(int i=1;i<=62;++i){
            for(int t:G[i]) if(bel[t]!=bel[i]){
                F.add(bel[i],bel[t]+scc,inf);
                out[bel[i]]=1;
            }
        }
        for(int i=1;i<=scc;++i){
            if(cnt[i]) F.add(F.S,i,cnt[i]);
            F.add(i+scc,i,inf);
            //使走过多个 scc 可行
            if(siz[i]==cnt[i]&&(cnt[i]>1||out[i])){
                F.add(i,i+scc,cnt[i]-1);
                //如果只有一个无出路的满点那么可以走掉
                //如果多于一个点的满scc就必须带来损耗
            }else F.add(i,i+scc,cnt[i]);
            F.add(i+scc,F.T,siz[i]);// 空点也要放流
        }
        print(F.dinic());
    }
    return 0;
}

想不出

3 的斜率就是保证点发出的射线不经过另外一个整点,那么变化坐标轴可以让南偏东/西变成向下、向左

使用平面图欧拉公式 V+F=E+S+1,这里 V 是射线端点和交点个数之和 I+n,容易发现 E 恰好是二倍交点也就是 2I,所以所求就是 F=In+1

对于具有左上右下关系的一对点 (a,b) 一定是 a 出发向下,b 出发向左,否则调整过后更为优秀,类似地我们可以得出存在一条 x 上升的过程中单调不降的分界线满足分界线上面射线向下,分界线下面射线向左

找到分界线可以逐点判定,其实本质上是二次函数求最值的过程,那么如果这个点左上的点数大于右下的点数,那么射线朝左,否则朝上

最后模拟连边过程写个 Θ(n2) 连边的并查集即可,当然使用 KDTree 优化建图可以低于这个复杂度

最后的问题是处理坐标轴变换,一种可能的方式是设 y=±3x 为新的 x/y 轴,那么 x/y 坐标数值大小关系可以通过 y=3x+b 映射到 y 轴来比较

Code Display
const int N=2010;
int x[N],y[N],ans,n,d[N],xid[N],yid[N],mp[N],dir[N];
const double get_b(int x,int y,double k){return y-k*x;}
struct BIT{
    int c[N];
    inline void insert(int x,int v){for(;x<=n;x+=x&(-x)) c[x]+=v; return ;}
    inline int query(int x){int res=0; for(;x;x-=x&(-x)) res+=c[x]; return res;}
    inline int query(int l,int r){return query(r)-query(l-1);}
}suf,pre;
int per[N];
struct Dsu{
    int fa[N];
    inline int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
    inline void init(){rep(i,1,n) fa[i]=i; return ;}
    inline void merge(int x,int y){fa[find(x)]=find(y);}
}T;
signed main(){
    freopen("surface.in","r",stdin); freopen("surface.out","w",stdout);
    n=read();
    rep(i,1,n) x[i]=read(),y[i]=read(),xid[i]=yid[i]=i;
    const double K=sqrt(3);
    sort(xid+1,xid+n+1,[&](const int a,const int b){
        return get_b(x[a],y[a],-K)>get_b(x[b],y[b],-K);
    });
    sort(yid+1,yid+n+1,[&](const int a,const int b){
        return get_b(x[a],y[a],K)>get_b(x[b],y[b],K);
    });
    for(int i=1;i<=n;++i) per[yid[i]]=i;
    for(int i=1;i<=n;++i) mp[i]=per[xid[i]];
    rep(i,1,n) suf.insert(i,1);
    for(int i=1;i<=n;++i){
        int down=suf.query(1,mp[i]-1),lef=pre.query(mp[i]+1,n);
        suf.insert(mp[i],-1);
        pre.insert(mp[i],1);
        if(down<lef) dir[i]=1;//turn left
    }
    T.init();
    for(int i=1;i<=n;++i) if(!dir[i]){
        for(int j=i+1;j<=n;++j) if(dir[j]&&mp[i]>mp[j]) T.merge(i,j),++ans;
    }

    set<int> st;
    rep(i,1,n) st.insert(T.find(i));
    print(ans-n+st.size());
    return 0;
}

题目名称

集合的 OGF

F(x)=i=1nxli1xrili+11x

那么考虑找到答案的 OGF,考虑写答案作:

Ans=124(aF4(x)+bF(x3)F(x)+cF2(x2)+dF(x2)F2(x)+eF(x4))

不难发现在选 4 个意义下只有上面 5 种算子是合法的

尝试根据实际含义来找到 a,b,c,d,e 的值,可能的选值种类集合有以下几种,需要让不合法的种类计算次数为 0 否则为 1

  • abcd 型,只在 a 里面算,由于外面带 124 的系数,所以要算 24 次,也就是 (4!)a=24

  • aaaa 型:在每个部分都只会计算 1 次,所以限制 a+b+c+d+e=0

  • aabc 型:在 a 中表现为四选 2 之后 21;在 dF(x2) 带走 aa 之后 bc 可以换顺序,两种方案,得到 12a+2d=0

  • aabb 型:

    a 中表现为两组占四个

    c,d 中是 (a,b),(b,a) 算两次

    d 中一组在 F(x2) 里面算,另外一组在 F2(x) 里面算,系数都是 1

    所以有 (42)a+2c+2d=0

  • aaab 型:

    a 中是 (43)

    b 中是 1

    d 中是删掉 aa 之后换顺序,两种方案

    所以有 4a+b+2d=0

是不是再推广一下就能到选任意个的容斥了

方程组是满秩的,可以得到 a,b,c,d,e 的值。至此答案可以得出为

Ans=124[A4(x)+8A(x3)A(x)+3A2(x2)6A2(x)A(x2)6A(x4)]

可以暴力计算 [xs]A(x3)A(x),[xs]A2(x2),[xs]A2(x)A(x2),对于剩下两项需要使用一类折半搜索来解决

A4(x) 为先,计算出来 (i=1nxlixri+1)2 之后逐个枚举 xc,那么要计算得到

[xd=sc](11x)4i=1sizcoefixti=i=1siz[xti]coefixti[xdti](11x)4=i=1sizcoefi(dti+33)=i=1siz16coefi[(d+1)(d+2)(d+3)(3d2+12d+11)ti+(3d+6)ti2ti3]

最后一步直接将组合数展开作下降幂除阶乘的形式就能得到了,那么对 d 之外的项维护前缀和,每次查询 ci 就可以找到 maxic+tis 前缀和来得到答案

另外的 A(x2)A2(x) 前置一个恒等式

i=0n(i+2i)(1)ni=(n+2)24

证明

RHS 的实际含义是从 ijn+2[jnmod2]

(i+2i)=(i+22)(i+22) 的含义是在 i+2 里面选择一对点

对于和 n 同奇偶性的一对点 (x,j) 发现计算次数是 (1)nk=jn(1)k 由于奇偶性相同,(1)j+n=1 而其他项消掉了,不同奇偶性的类似,正好全消掉了是 0


和上面的一样推推:

[xd=sc]1(1x)3(1+x)i=1sizcoefixti=i=1siz[xti]coefixti[xdti]1(1x)3(1+x)=i=1sizcoefij=0dti(j+2j)(1)dtij=i=1sizcoefi(dti+2)24

下取整的计算考虑继续拆成 (dti+2)2 的和以及 (dti+2)2mod4 的值的和即可,前面可以直接拆成下面

=14i=1sizcoefi[(d+2)22(d+2)t+t2]

后面分开 ti 的奇偶性计算,查询找 d 的奇偶性减去再除 4 即可,这里使用 A(x2) 来做前缀和就只用维护一个了

Code Display
const int N=1010;
int n,s,l[N],r[N],ans;
signed main(){
    freopen("count.in","r",stdin); freopen("count.out","w",stdout);    
    n=read(); s=read(); 
    rep(i,1,n) l[i]=read(),r[i]=read();
    if(s%4==0){
        for(int i=1;i<=n;++i){
            if(l[i]*4<=s&&s<=r[i]*4){
                ckdel(ans,6); 
                break;
            }
        }
    }
    rep(i,1,n) rep(j,1,n){
        int L=max(0ll,s-r[j]),R=s-l[j];
        if(R<l[i]*3||L>r[i]*3) continue;
        ckmax(L,l[i]*3); ckmin(R,r[i]*3);
        while(L%3) ++L; while(R%3) --R;
        if(R<L) continue;
        ckadd(ans,mul(8,(R-L)/3+1));
    }
    vector<pair<int,int> >now,tmp;
    rep(i,1,n) rep(j,1,n){
        now.emplace_back(l[i]+l[j],1);
        now.emplace_back(r[i]+r[j]+2,1);
        now.emplace_back(r[i]+l[j]+1,mod-1);
        now.emplace_back(r[j]+l[i]+1,mod-1);
    }   
    sort(now.begin(),now.end());
    for(auto t:now){
        if(!tmp.size()||tmp.back().fir!=t.fir) tmp.push_back(t);
        else ckadd(tmp.back().sec,t.sec);
    }
    now.clear();
    for(auto t:tmp) if(t.sec) now.push_back(t);
    if(s%2==0){
        for(auto t:now) if(t.fir<=s/2){
            int d=s/2-t.fir;
            ckadd(ans,mul(3,(d+1)*t.sec%mod));
        }
    }
    if(1){
        vector<int> sum[4];
        int siz=now.size();
        rep(i,0,3) sum[i].resize(siz);
        for(int i=0;i<siz;++i){
            sum[0][i]=now[i].sec;
            sum[1][i]=mul(now[i].sec,now[i].fir);
            sum[2][i]=mul(now[i].sec,mul(now[i].fir,now[i].fir));
            sum[3][i]=mul(now[i].sec,ksm(now[i].fir,3));
        }
        rep(i,1,siz-1) rep(j,0,3) ckadd(sum[j][i],sum[j][i-1]);
        int contr=0,pter=siz-1;
        for(auto t:now){
            while(pter>=0&&now[pter].fir>s-t.fir) --pter;
            if(pter<0) break;
            int d=s-t.fir;
            int tsum=0;
            ckadd(tsum,mul(sum[0][pter],mul(mul(d+1,d+2),d+3)));
            ckdel(tsum,mul(sum[1][pter],(3*d*d+12*d+11)%mod));
            ckadd(tsum,mul(sum[2][pter],(3*d+6)%mod));
            ckdel(tsum,sum[3][pter]);
            ckadd(contr,mul(tsum,t.sec));
        }
        ckadd(ans,mul(ksm(6,mod-2),contr));
    }
    if(1){
        vector<pair<int,int> >poly;
        rep(i,1,n) poly.emplace_back(2*l[i],1),poly.emplace_back(2*r[i]+2,mod-1);
        vector<int> sum[3];
        int siz=poly.size();
        rep(i,0,2) sum[i].resize(siz);
        for(int i=0;i<siz;++i){
            sum[0][i]=poly[i].sec;
            sum[1][i]=mul(poly[i].sec,poly[i].fir);
            sum[2][i]=mul(poly[i].sec,mul(poly[i].fir,poly[i].fir));
        }
        rep(i,1,siz-1) rep(j,0,2) ckadd(sum[j][i],sum[j][i-1]);
        int pter=siz-1,contr=0;
        for(auto t:now){
            while(pter>=0&&poly[pter].fir>s-t.fir) --pter;
            if(pter<0) break;
            int d=s-t.fir,tsum=0;
            ckadd(tsum,mul(sum[0][pter],(d+2)*(d+2)%mod));
            ckdel(tsum,mul(2*(d+2)%mod,sum[1][pter]));
            ckadd(tsum,sum[2][pter]);
            if(d&1) ckdel(tsum,sum[0][pter]);
            ckadd(contr,mul(tsum,t.sec));
        }
        ckdel(ans,mul(3*contr%mod,(mod+1)/2));
    }
    print(mul(ans,ksm(24,mod-2)));
    return 0;
}

posted @   没学完四大礼包不改名  阅读(115)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示