• [考试反思]0330省选模拟58:运作


    百年难得一遇

    其实只是$gmk$大神没写快速乘被卡常掉了$40pts$而已。

    $T1$写了个挺简单但是貌似容易被遗忘的暴力。写了快速乘拿到了$60$

    然后$T2$的简单$dp$也没啥脑子。

    然后我这场干啥了??

    其实我把主要的时间砸在$T3$上了,然而有一个细节想简单了,爆炸成$10$分了。

    早知道写$50pts$的暴力了,已经想到了但是最后还是想写”正解“。其实$50pts$也不少哈(苟就是了)

    三道题尝试写正解的都挂了没看见嘛?想啥呢

    但是这套题的确挺难写的,代码长度都是$geq 2.3k$。写着就很难受。

    考察的思路还不错(至少我想着挺顺的

    T1:带权图

    大意:无向联通图,边带权$A,B,C$。$A(u,v)=-A(v,u),B(u,v)=B(v,u),C(u,v)=-C(v,u),sumlimits_{vin suc(u)} C(u,v)=0,sumlimits_{(u,v) in cycle(x)} B(u,v)C(u,v)-A(u,v)=0$。

    以上运算均在$mod p$意义下。$p$是质数。$nle 100,mle 2000,P le 10^{18}$。保证有唯一解

    这么大的模数,那就快速乘呗。慢速乘多个$log$直接掉一档分。

    然后最粗暴的想法(还是别太粗暴)就是列关于$C$的方程。

    首先对于第二个条件可以把所有的点拿出来列方程。然而不是$n$个而是$n-1$个。(因为和都是$0$所以要去掉一个点)

    然后随便跑一个生成树出来。

    然后对于每条非树边,它与树边会形成一个环,根据这个环拿第三个条件来列式。一共$m-(n-1)$条非树边,也就有这么多方程。

    所以总共就有了$m$个方程,高斯消元解出来就行了。$O(m^3)$

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define ll long long
     4 ll mod,g[2222][2222],a[6666],b[6666];
     5 int n,m,mp[111][111],fir[111],l[6666],to[6666],cnt,ec=1,f[111],al[111],fe[111];
     6 ll mul(ll a,ll b){return ((ll)((unsigned ll)a*b-(unsigned ll)(1.L*a/mod*b)*mod)+mod)%mod;}
     7 ll qp(ll b,ll t,ll a=1){for(;t;t>>=1,b=mul(b,b))if(t&1)a=mul(a,b);return a;}
     8 ll mo(ll a){return a>=mod?a-mod:a;}
     9 void Gauss(){
    10     for(int i=1;i<=m;++i){
    11         for(int j=i+1;!g[i][i];++j)for(int k=i;k<=m+1;++k)swap(g[i][k],g[j][k]);
    12         ll iv=qp(g[i][i],mod-2);
    13         for(int j=i;j<=m+1;++j)g[i][j]=mul(g[i][j],iv);
    14         for(int j=1;j<=m;++j)if(j!=i)for(int k=m+1;k>=i;--k)g[j][k]=mo(g[j][k]-mul(g[i][k],g[j][i])+mod);
    15     }for(int i=1;i<=m;++i)printf("%lld
    ",g[i][m+1]);
    16 }
    17 void ctb(int i){g[cnt][i>>1]=i&1?mod-b[i>>1]:b[i>>1]; g[cnt][m+1]=mo(g[cnt][m+1]+(i&1?mod-a[i>>1]:a[i>>1]));}
    18 void dfs(int p,int fa){
    19     al[p]=2;
    20     for(int i=fir[p];i;i=l[i])if(!al[to[i]])fe[to[i]]=i,dfs(to[i],p);
    21         else if(al[to[i]]==2&&to[i]!=fa){
    22             cnt++; ctb(i); int r=p;
    23             while(r!=to[i])ctb(fe[r]),r=to[fe[r]^1];
    24         }
    25     al[p]=1;
    26 }
    27 void link(int a,int b){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;}
    28 int main(){//freopen("0.in","r",stdin);
    29     cin>>n>>m>>mod;ll A,B;
    30     for(int i=1,x,y;i<=m;++i)scanf("%d%d%lld%lld",&x,&y,&a[i],&b[i]),link(x,y),link(y,x);
    31     for(int i=1;i<n;++i){
    32         cnt++;
    33         for(int j=fir[i];j;j=l[j])g[cnt][j>>1]=j&1?mod-1:1;
    34     }dfs(1,0); Gauss();
    35 }
    60pts

    不难发现最后那$m-(n-1)$个方程中,每个方程都包含恰好一条非树边$E$。想到主元法。

    于是我们其实可以通过移项,两边同乘,得到$C_E=xC_i+yC_j+...+a$。这样的式子。右边是树边以及常量。

    然后我们把所有的非树边都这么带入前$n-1$个式子,就的到了只关于树边的方程组。解出来再回代,复杂度是$O(n^3)$的。

    正常主元法的题其实是$O(n^2m)$的。瓶颈在于将$m-n$个式子代入前$n$个里。

    然而这道题每条非树边只会被两个节点的方程包含,所以是$O(nm)$的。

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define ll long long
     4 ll mod,g[111][111],a[6666],b[6666],G[2222][2222],ans[2222];
     5 int n,m,fir[111],l[6666],to[6666],cnt,ec=1,f[111],al[111],fe[111],ex[2222],rex[111];
     6 ll mul(ll a,ll b){return ((ll)((unsigned ll)a*b-(unsigned ll)(1.L*a/mod*b)*mod)+mod)%mod;}
     7 ll qp(ll b,ll t,ll a=1){for(;t;t>>=1,b=mul(b,b))if(t&1)a=mul(a,b);return a;}
     8 ll mo(ll a){return a>=mod?a-mod:a;}
     9 void Gauss(){
    10     for(int i=1;i<n;++i){
    11         for(int j=i+1;!g[i][i];++j)for(int k=i;k<=n;++k)swap(g[i][k],g[j][k]);
    12         ll iv=qp(g[i][i],mod-2);
    13         for(int j=i;j<=n;++j)g[i][j]=mul(g[i][j],iv);
    14         for(int j=1;j<n;++j)if(j!=i)for(int k=n;k>=i;--k)g[j][k]=mo(g[j][k]-mul(g[i][k],g[j][i])+mod);
    15     }
    16 }
    17 void ctb(int i){G[cnt][i>>1]=i&1?mod-b[i>>1]:b[i>>1]; G[cnt][m+1]=mo(G[cnt][m+1]+(i&1?mod-a[i>>1]:a[i>>1]));}
    18 void dfs(int p,int fa){
    19     al[p]=2;
    20     for(int i=fir[p];i;i=l[i])if(!al[to[i]])fe[to[i]]=i,dfs(to[i],p);
    21         else if(al[to[i]]==2&&to[i]!=fa){
    22             cnt++; ctb(i); int r=p; ex[i>>1]=-cnt;
    23             while(r!=to[i])ctb(fe[r]),r=to[fe[r]^1];
    24             ll iv=mod-qp(G[cnt][i>>1],mod-2);
    25             for(int j=1;j<=m+1;++j)G[cnt][j]=mul(G[cnt][j],iv);
    26         }
    27     al[p]=1;
    28 }
    29 void link(int a,int b){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;}
    30 int main(){//freopen("0.in","r",stdin);
    31     cin>>n>>m>>mod;ll A,B;
    32     for(int i=1,x,y;i<=m;++i)scanf("%d%d%lld%lld",&x,&y,&a[i],&b[i]),link(x,y),link(y,x);
    33     for(int i=1;i<n;++i){
    34         cnt++;
    35         for(int j=fir[i];j;j=l[j])G[cnt][j>>1]=j&1?mod-1:1;
    36     }dfs(1,0);
    37     int z=0;
    38     for(int i=1;i<=m;++i)if(!ex[i])ex[i]=++z,rex[z]=i;
    39     //for(int i=1;i<=m;++i)cout<<ex[i]<<endl;
    40     //for(int i=1;i<=m;++i,puts(""))for(int j=1;j<=m+1;++j)printf("%2lld ",G[i][j]);
    41     
    42     for(int i=1;i<n;++i){
    43         g[i][n]=G[i][m+1];
    44         for(int j=1;j<=m;++j)if(ex[j]>0)g[i][ex[j]]=mo(g[i][ex[j]]+G[i][j]);
    45             else if(G[i][j]){
    46                 int p=-ex[j];
    47                 for(int k=1;k<=m;++k)if(ex[k]>0)g[i][ex[k]]=mo(g[i][ex[k]]+mul(G[i][j],G[p][k]));
    48                 g[i][n]=mo(g[i][n]+mul(G[i][j],G[p][m+1]));
    49             }
    50     }
    51     //puts("");
    52     //for(int i=1;i<=m;++i,puts(""))for(int j=1;j<=m+1;++j)printf("%2lld ",g[i][j]);
    53     
    54     Gauss();
    55     
    56     for(int i=1;i<n;++i)ans[rex[i]]=g[i][n];
    57     for(int i=1;i<=m;++i)if(ex[i]<0){
    58         int p=-ex[i]; ans[i]=mod-G[p][m+1];
    59         for(int k=1;k<=m;++k)if(ex[k]>0)ans[i]=mo(ans[i]+mul(ans[k],G[p][k]));
    60     }
    61     
    62     for(int i=1;i<=m;++i)printf("%lld
    ",ans[i]);
    63 }
    View Code

    T2:网络

    大意:有$C$关键点。从$(1,1)$出发,沿两条不相交的路径向右或向下走到$(n,m)$,且经过关键点数不超过$D$。答案对$mod$取模。求方案数。$C le 200,n,m le 10^5,mod le 10^9$

    一看这个模数就知道它大概想让你干什么。$CRT$呗。

    然而特殊的是,这道题$n,m$不大,所以可以直接预处理阶乘,逆元,以及阶乘所含的质数的次数。

    如果只有一条路径,可以直接$dp:f[i][j]$表示踩了$i$个关键点,最后落在第$j$个上。$g[i][j]$表示从第$i$个关键点走到第$j$个,不经过其它关键点。

    $g$可以通过枚举“过程中最后一个遇到的关键点”来进行容斥。有了$g$,$f$很好转移。复杂度$O(C^3)$

    如果不考虑关键点,那么考虑两条路径相交的情况。

    其实本质上是两条$(2,1) ightarrow (n,m-1),(1,2) ightarrow (n-1,m)$的路径。如果相交,那么交点一定有偶数个。(碰上就立刻分开算两个)

    在每个交点处,两条路径可以选择交换上下关系,也可以不交换。

    如果交换奇数次那么最后的路径其实是$(1,2) ightarrow (n,m-1),(2,1) ightarrow (n-1,m)$。

    类似状压的角度考虑,发现对于两条特定的相交路径,交换奇数次的方案数与交换偶数次的相等。

    然而如果两条路径压根就不相交,那么当然会被记录到偶数中去。

    那么答案就是$(2,1) ightarrow (n,m-1),(1,2) ightarrow (n-1,m)$的方案数减去$(1,2) ightarrow (n,m-1),(2,1) ightarrow (n-1,m)$的方案数。

    这样就去掉了相交的限制。

    外面再套一层$CRT$就完事了。然而可能会卡常。

    发现再对于同一个质数$4种起终点对关系,$g$数组大概都是一样的,只有$g[0][i],g[i][C+1]$变化了。所以不每次更新,就卡过去了。

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 #define S 222222
     4 struct Pt{int x,y;friend bool operator<(Pt x,Pt y){return x.x+x.y<y.x+y.y;}}P[222];
     5 int c,d,n,m,fac[S],ft[S],finv[S],g[222][222],f[222][222],A[222],B[222],p,k,pk,pw[33];
     6 void exgcd(int a,int b,int&x,int&y){
     7     if(!b){x=1;y=0;return;}
     8     exgcd(b,a%b,y,x);y-=a/b*x;
     9 }
    10 int mo(int x){return x>=pk?x-pk:x;}
    11 int inv(int a,int b){int x,y;pk=b;exgcd(a,b,x,y);return mo(x%pk+pk);}
    12 int C(int b,int t,int a=1){return (b<t||t<0)||ft[b]-ft[t]-ft[b-t]>=k?0:1ll*pw[ft[b]-ft[t]-ft[b-t]]*fac[b]%pk*finv[t]%pk*finv[b-t]%pk;}
    13 int cal(int i,int j){return C(P[j].x+P[j].y-P[i].x-P[i].y,P[j].x-P[i].x);}
    14 void cal(int*a){
    15     for(int i=0,j=i+1;j<=c;++j){
    16         g[i][j]=cal(i,j);
    17         for(int k=i+1;k<j;++k)g[i][j]=mo(g[i][j]-1ll*g[i][k]*cal(k,j)%pk+pk);
    18     }
    19     for(int i=0,j=c;i<j;++i){
    20         g[i][j]=cal(i,j);
    21         for(int k=i+1;k<j;++k)g[i][j]=mo(g[i][j]-1ll*g[i][k]*cal(k,j)%pk+pk);
    22     }
    23     for(int i=1;i<c;++i)if(P[i].x==P[0].x&&P[i].y==P[0].y){for(int j=1;j<=c;++j)g[0][j]=0;g[0][i]=1;}
    24     for(int i=1;i<c;++i)if(P[i].x==P[c].x&&P[i].y==P[c].y){for(int j=1;j<=c;++j)g[j][c]=0;g[i][c]=1;}
    25     for(int i=0;i<=c;++i)for(int j=0;j<=d;++j)f[i][j]=0; f[0][0]=1;
    26     for(int j=1;j<=c;++j)for(int k=0;k<j;++k)if(g[k][j])for(int i=0;i<d;++i)f[j][i+1]=(f[j][i+1]+1ll*f[k][i]*g[k][j])%pk;
    27     for(int i=0;i<=d;++i)a[i]=f[c][i];
    28 }
    29 int solve(){
    30     fac[0]=pw[0]=finv[0]=1; int a=0;
    31     for(int i=1;i<n+m;++i){
    32         int x=i; ft[i]=ft[i-1];
    33         while(x%p==0)ft[i]++,x/=p;
    34         fac[i]=1ll*fac[i-1]*x%pk;
    35         finv[i]=inv(fac[i],pk);
    36     }for(int i=1;i<k;++i)pw[i]=pw[i-1]*p;
    37     for(int i=1;i<c;++i)for(int j=i+1;j<c;++j){
    38         g[i][j]=cal(i,j);
    39         for(int k=i+1;k<j;++k)g[i][j]=mo(g[i][j]-1ll*g[i][k]*cal(k,j)%pk+pk);
    40     }
    41     P[0]=(Pt){1,2};P[c]=(Pt){n-1,m};cal(A);
    42     P[0]=(Pt){2,1};P[c]=(Pt){n,m-1};cal(B);
    43     for(int i=0;i<=d;++i)for(int j=0;i+j<=d;++j)a=(a+1ll*A[i]*B[j])%pk;
    44     P[0]=(Pt){2,1};P[c]=(Pt){n-1,m};cal(A);
    45     P[0]=(Pt){1,2};P[c]=(Pt){n,m-1};cal(B);
    46     for(int i=0;i<=d;++i)for(int j=0;i+j<=d;++j)a=mo(a+pk-1ll*A[i]*B[j]%pk);
    47     return a;
    48 }
    49 int main(){//freopen("grid20.in","r",stdin);
    50     int T,Pr[9],K[9],ans[9],PK[9],z,mod;cin>>T;while(T--){
    51         cin>>n>>m>>c>>d>>mod; 
    52         for(int i=1;i<=c;++i)cin>>P[i].x>>P[i].y;
    53         sort(P+1,P+1+c); c++;d+=2;z=0;
    54         for(int i=2;i*i<=mod;++i)if(mod%i==0){
    55             Pr[++z]=i;K[z]=0;PK[z]=1;
    56             while(mod%i==0)mod/=i,K[z]++,PK[z]*=i;
    57         }if(mod!=1)Pr[++z]=mod,K[z]=1,PK[z]=Pr[z];
    58         for(int i=1;i<=z;++i)p=Pr[i],k=K[i],pk=PK[i],ans[i]=solve();
    59         for(int i=2,M;i<=z;++i)
    60             M=PK[i-1]*PK[i],
    61             ans[i]=(1ll*ans[i-1]*PK[i]%M*inv(PK[i],PK[i-1])+1ll*ans[i]*PK[i-1]%M*inv(PK[i-1],PK[i]))%M,
    62             PK[i]*=PK[i-1];
    63         cout<<ans[z]<<endl;
    64     }
    65 }
    View Code

    T3:修路

    大意:图,按照一定规则生成了一棵最短路树。强制在线,多次询问:给定祖先后代$(u,v)$,这条链从上往下最长可以砍掉多少边,使得砍掉之后根到$v$最短距离不变。$n,m,q le 2itmes 10^5$

    题意就是求出一个深度最大的点$w$,满足在反向的最短路径有向图上,它可以不经过$(u...w)$的树边,到达深度$le dep(u)$的点。

    我们设$C_x$表示点$x$在不经过$(C_x,x)$的树边的情况下可以到达的点中能到达的最小深度。

    假如我们求出这个,那么我们就可以对于每个询问直接倍增从$v$开始往上跳了。

    发现每条非树边(u,v)的贡献,就是$C_v$对$C_{lca(u,v)...u}$取$min$。拿树剖线段树维护一下就行。

    然后只要把所有点按照到根的距离排序,依次考虑就能保证更新出的$C$是正确的了(因为小点更新大点,差不多就是拓扑的意思)

    不倍增的话也可以直接线段树上二分。常数可能会比较大。

    另一种做法是主席树,以深度为下标,也是维护能到达的最小深度,然后主席树上二分。

    复杂度大约都是$O(nlog^2n+qlogn)$级别的。

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 int read(){int p=0;char ch=getchar();
     4     for(;!isdigit(ch);ch=getchar()); for(;isdigit(ch);ch=getchar())p=p*10+ch-48;
     5     return p;
     6 }
     7 #define S 222222
     8 priority_queue<pair<long long,int> >q;
     9 vector<int>to[S],w[S],s[S];
    10 int n,m,o,C,p[S],f[20][S],rp[S],al[S],dep[S],low[20][S],la,sz[S],hson[S],tp[S],dfn[S],tim,idfn[S],v[S<<2],rs[S];
    11 long long d[S];
    12 void con(int a,int b,int W){
    13     to[a].push_back(b);w[a].push_back(W);
    14     to[b].push_back(a);w[b].push_back(W);
    15 }
    16 void dfs(int p){
    17     dep[p]=low[0][p]=dep[f[0][p]]+1; sz[p]=1;
    18     for(int i=0,y;y=i<w[p].size()?to[p][i]:0;++i)if(d[y]==d[p]+w[p][i]&&!al[y]){
    19         s[p].push_back(y),al[y]=1,f[0][y]=p,dfs(y);sz[p]+=sz[y];
    20         if(sz[y]>sz[hson[p]])hson[p]=y;
    21     }
    22 }
    23 void DFS(int p,int top){
    24     dfn[p]=++tim; idfn[tim]=p; tp[p]=top;
    25     for(int i=1;i<19;++i)f[i][p]=f[i-1][f[i-1][p]];
    26     if(hson[p])DFS(hson[p],top);
    27     for(int i=0;i<s[p].size();++i)if(s[p][i]!=hson[p])DFS(s[p][i],s[p][i]);
    28 }
    29 #define lc p<<1
    30 #define rc lc|1
    31 #define md (L+R>>1)
    32 void build(int p,int L,int R){
    33     if(L==R){v[p]=low[0][idfn[L]];return;}
    34     build(lc,L,md); build(rc,md+1,R); v[p]=min(v[lc],v[rc]);
    35 }
    36 int ask(int l,int r,int p=1,int L=1,int R=n){
    37     if(l<=L&&R<=r)return v[p];
    38     return min(l<=md?ask(l,r,lc,L,md):n,r>md?ask(l,r,rc,md+1,R):n);
    39 }
    40 void chg(int P,int w,int p=1,int L=1,int R=n){
    41     if(L==R){v[p]=w;return;}
    42     if(P<=md)chg(P,w,lc,L,md);else chg(P,w,rc,md+1,R); v[p]=min(v[lc],v[rc]);
    43 }
    44 int query(int x,int y,int a=n){
    45     while(tp[x]!=tp[y])if(dep[tp[x]]>dep[tp[y]])x=f[0][tp[x]];
    46         else a=min(a,ask(dfn[tp[y]],dfn[y])),y=f[0][tp[y]];
    47     return min(a,ask(min(dfn[x],dfn[y]),dfn[y]));
    48 }
    49 int main(){//freopen("0.in","r",stdin);
    50     cin>>n>>m>>C>>o;
    51     for(int i=1;i<=n;++i)p[i]=read(),rp[p[i]]=i;
    52     for(int i=1,a,b;i<=m;++i)a=read(),b=read(),con(a,b,read());
    53     for(int i=1;i<=n;++i){
    54         for(int j=0;j<w[i].size();++j)q.push(make_pair(-p[to[i][j]],w[i][j]));
    55         to[i].clear(); w[i].clear();
    56         while(!q.empty())to[i].push_back(rp[-q.top().first]),w[i].push_back(q.top().second),q.pop();
    57     }
    58     memset(d,0x3f,sizeof d); d[C]=0; q.push(make_pair(0,C));
    59     while(!q.empty()){
    60         long long D=-q.top().first;int p=q.top().second; q.pop();
    61         if(D!=d[p])continue;
    62         for(int i=0;i<w[p].size();++i) if(d[to[p][i]]>d[p]+w[p][i])
    63             q.push(make_pair(-(d[to[p][i]]=d[p]+w[p][i]),to[p][i]));
    64     }
    65     dfs(C); DFS(C,C); build(1,1,n);
    66     for(int i=1;i<=n;++i)rs[i]=i;
    67     sort(rs+1,rs+1+n,[](int a,int b){return d[a]<d[b];});
    68     for(int _=1,x;x=rs[_];++_){
    69         for(int i=0,y;y=i<w[x].size()?to[x][i]:0;++i)if(d[x]==d[y]+w[x][i]&&y!=f[0][x])
    70             chg(dfn[x],low[0][x]=min(low[0][x],query(x,y)));
    71         for(int i=1;i<19;++i)low[i][x]=min(low[i-1][x],low[i-1][f[i-1][x]]);
    72     }
    73     for(int i=read(),u,v;i;--i){
    74         u=read();v=read(); if(o)u^=la,v^=la; la=0;
    75         for(int i=18;~i;--i)if(low[i][v]>dep[u])v=f[i][v];
    76         printf("%d
    ",la=dep[v]-dep[u]);
    77     }
    78 }
    View Code
  • 相关阅读:
    春秋战国时期灭了三个国家的陈国女人
    学历史有什么用?
    真正的奴才韩非
    深度学习的历史
    深度学习三十年
    图算法
    几种常见的查找算法
    数据结构之基于堆的优先队列
    几种常见的排序算法
    数据结构(背包、队列和栈)
  • 原文地址:https://www.cnblogs.com/hzoi-DeepinC/p/12601848.html
Copyright © 2020-2023  润新知