[考试反思]0416省选模拟72:停滞

大约是来搞笑的了。

$T1$想到了$wqs$二分但是不会(或者说根本没往这个方向想)$dp$。

$T2$神仙题暂且不管($15$是全场最高但是有个啥用?)

$T3$的话会$30pts$的状压,但是觉得对我整场的分数并没有什么作用于是也就没有写。随便输出了个$0$拿走$10pts$

主观来讲,我大概是没长脑子,啥都想不出来

客观来讲,题有一点不对胃口,好像考试的时候始终不太敢去猜测性质然后就开始写代码

那些用的不多的知识点以及扩展出来的性质就更不敢用了,可能也是导致这样的原因

大约多做题是一个合理的解决办法,至少下次再遇到这些东西,我不会慌了。。。

又把锅甩给了下次啊。。。

T1:新访问计划

大意:边权树,要求遍历每条树边至少一次最后回到根,可以花费$c$的代价直接传送到任意点最多$k$次。求最小代价。多测。$\sum n,k \le 10^5,n \le 2 \times 10^4,w_i,c \le c \times 10^4$

可以转化题意:首先要求回路那么所有树边必须走一次,接下来你可以用树边 和 代价为$c$的树上路径至多$k$条 来覆盖整棵树。求最小代价。

可以搞出一个$dp$表示$f[i][j][0/1]$表示是否有一个未配对(可以再次转弯的)路径连向当前$i$且子树内完全被覆盖而已经申请了$j$条路径的最小代价。

子树归并。$0/1 \ +  \ 0/1 \ \rightarrow 0/1$共$8$种情况其中有一种不合法剩下的按照含义转移即可。

复杂度是$O(n^2)$的。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define S 2222
 4 #define inf 1000000000
 5 int f[S][S],g[S][S],n,fir[S],l[S<<1],to[S<<1],w[S<<1],ec,k,c,sz[S],F[S],G[S],tot;
 6 void link(int a,int b,int x){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;w[ec]=x;}
 7 void con(int a,int b,int x){link(a,b,x);link(b,a,x);}
 8 void dfs(int p,int fa){
 9     sz[p]=1; f[p][1]=g[p][0]=0; f[p][0]=g[p][1]=inf;
10     for(int _=fir[p],z;_;_=l[_])if((z=to[_])!=fa){
11         dfs(z,p); int W=w[_];
12         for(int i=0;i<=sz[p]+sz[z];++i)F[i]=G[i]=inf;
13         for(int i=0;i<=sz[p];++i)for(int j=0;j<=sz[z];++j)
14             G[max(0,i+j-1)]=min(G[max(0,i+j-1)],f[p][i]+f[z][j]),
15             F[i+j]=min(F[i+j],f[p][i]+f[z][j]),
16             F[i+j]=min(F[i+j],f[p][i]+g[z][j]+W),
17             G[i+j]=min(G[i+j],f[p][i]+g[z][j]),
18             F[i+j]=min(F[i+j],g[p][i]+f[z][j]),
19             F[i+j+1]=min(F[i+j+1],g[p][i]+g[z][j]),
20             G[i+j]=min(G[i+j],g[p][i]+g[z][j]+W);
21         sz[p]+=sz[z];
22         for(int i=0;i<=sz[p];++i)f[p][i]=F[i],g[p][i]=min(G[i],F[i]);
23     }
24 }
25 int main(){//freopen("ex_newmzz3.in","r",stdin);
26 while(cin>>n>>k>>c){
27     for(int i=1,a,b,x;i<n;++i)scanf("%d%d%d",&a,&b,&x),con(a,b,x),tot+=x;
28     dfs(0,0);
29     int ans=inf;
30     for(int i=0;i<=k;++i)ans=min(ans,min(g[0][i],f[0][i])+c*i);
31     cout<<ans+tot<<endl;
32     for(int i=0;i<n;++i)fir[i]=0; ec=tot=0;
33 }}
View Code

这题的含义让人不难想到$wqs$二分。

但是我们的要求是,第二维也就是已使用的路径数不能超过$k$。所以我们再开一个数组存储在此代价下最小的路径使用数。

二分并据此查找合适的$c$

如果对于某个特定的$c_0$最有决策下使用的路径书是$x$,对于$c_1$也是,那么在这两个代价下的决策也一定是完全一样的。

所以说,我们可以先进行一次$dp$,然后看最有决策使用的路径数是否超过$k$

如果没有超过那么就是说最优解就合法可以直接输出。否则,我们一定希望我们恰好选择$k$条路径。

那么通过$wqs$去找恰好$k$条路径的方案,如果不存在恰好为$k$的,那么也当成$k$去计算就好了。

(此时一定是存在多条路径,选择它们的收益相同,所以才会一选都选一不选都不选,然而恰好选$k$个是最优的,依次贡献答案是最佳的)

总的时间复杂度是$O(n log w)$

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define S 100005
 4 #define ll long long
 5 ll f[S][2],tot;int g[S][2],n,fir[S],l[S<<1],to[S<<1],w[S<<1],ec,k,c;
 6 void link(int a,int b,int x){l[++ec]=fir[a];fir[a]=ec;to[ec]=b;w[ec]=x;}
 7 void con(int a,int b,int x){link(a,b,x);link(b,a,x);}
 8 void upd(ll&F,int&G,int p,int z,int x,int y,int df,int dg){
 9     ll nf=df+f[p][x]+f[z][y],ng=dg+g[p][x]+g[z][y];
10     if(nf<F||(nf==F&&ng<G))F=nf,G=ng;
11 }
12 void dfs(int p,int fa){
13     f[p][1]=c; g[p][1]=1; f[p][0]=0; g[p][0]=0;
14     for(int _=fir[p],z;_;_=l[_])if((z=to[_])!=fa){
15         dfs(z,p); int W=w[_],g1,g0;ll f0=1e17,f1=1e17;
16         upd(f0,g0,p,z,1,1,-c,-1);
17         upd(f1,g1,p,z,1,1, 0, 0);
18         upd(f1,g1,p,z,1,0, W, 0);
19         upd(f0,g0,p,z,1,0, 0, 0);
20         upd(f1,g1,p,z,0,1, 0, 0);
21         upd(f1,g1,p,z,0,0, c, 1);
22         upd(f0,g0,p,z,0,0, W, 0);
23         f[p][1]=f1; f[p][0]=f0; g[p][1]=g1; g[p][0]=g0;
24     }
25 }
26 int main(){//freopen("ex_newmzz3.in","r",stdin);
27 while(cin>>n>>k>>c){
28     for(int i=1,a,b,x;i<n;++i)scanf("%d%d%d",&a,&b,&x),con(a,b,x),tot+=x;
29     dfs(0,0);
30     int C=c,l=0,r=1000000000,b;ll ans=g[0][0]>k?1e18:f[0][0];
31     while(c=l+r>>1,l<=r){
32         dfs(0,0);
33         if(g[0][0]<=k)r=c-1,b=c;else l=c+1;
34     }
35     c=b;dfs(0,0);
36     ans=min(ans,f[0][0]+k*(C-c));
37     cout<<ans+tot<<endl;
38     for(int i=0;i<n;++i)fir[i]=0; ec=tot=0;
39 }}
View Code

T2:计算几何

看到题目名果断弃坑。

T3:树形图求和

大意:计算所有以$n$为根的内向树边权和。$n \le 300,m \le 10^5$

原来$Matrix-Tree$是可以用在内向树之类的东西上的啊。

对于内向树我们建基尔霍夫矩阵的时候,边只用单向边,度数只用出度,然后做$Matrix-Tree$的时候只要强制删掉的是根节点所在行列即可。

首先求出整个图有多少生成内向树。

然后只需要枚举每种边,把它去掉再求内向树个数,相减就能得到包含它的内向树个数。乘上权值就可以计算其贡献。

复杂度$O(mn^3)$

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define mod 1000000007
 4 int A[55][55],n,m,w[55][55],ans;
 5 int mo(int a){return a>=mod?a-mod:a;}
 6 int qp(int b,int t,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;}
 7 int ret(int A[55][55]){
 8     int R=1,a[55][55];
 9     for(int i=1;i<n;++i)for(int j=1;j<n;++j)a[i][j]=A[i][j];
10     for(int i=1;i<n;++i){
11         R=1ll*R*a[i][i]%mod;
12         for(int iv=qp(a[i][i],mod-2),j=i;j<n;++j)a[i][j]=1ll*a[i][j]*iv%mod;
13         for(int j=i+1;j<n;++j)for(int k=n-1;k>=i;--k)a[j][k]=mo(a[j][k]-a[j][i]*1ll*a[i][k]%mod+mod);
14     }return R;
15 }
16 int main(){
17     cin>>n>>m;
18     for(int i=1,x,y,z;i<=m;++i)scanf("%d%d%d",&x,&y,&z),w[x][y]=mo(w[x][y]+z),A[x][y]=mo(A[x][y]+mod-1),A[x][x]++,ans=mo(ans+z);
19     ans=1ll*ans*ret(A)%mod;
20     for(int x=1;x<=n;++x)for(int y=1;y<=n;++y)if(x!=y&&A[x][y])
21         A[x][y]++,A[x][x]--,ans=(ans-1ll*ret(A)*w[x][y])%mod,A[x][y]--,A[x][x]++;
22     cout<<mo(ans+mod)<<endl;
23 }
View Code

复杂度瓶颈在于每次都要求行列式,于是我们引入(大量)线性代数知识:

设$R$为$B$的余子式矩阵,则有拉普拉斯展开:

$ret(B) = \forall x \sum\limits_{i=1}^{n} R_{x,i} B_{x,i}$

$ret(B) = \forall y \sum\limits_{j=1}^{n} R_{j,y} B_{j,y}$

其中,$R=(ret(B) B^{-1} )^T$

(其实还有点中间过程,就是说:余子式矩阵是伴随矩阵的转置,伴随矩阵乘原矩阵是原矩阵行列式倍的单位矩阵)

证明显然不会(也查不到)

我们发现我们要求解行列式之前每次会删除一条边,矩阵的一行的两个元素发生改变,其余均不变。

然后只改变第$x$行的元素那么这一行的余子式也不会改变(含义嘛,把这一行删掉了有什么影响)

所以我们有了这一行的余子式,还有这一行矩阵的新值,我们就可以直接套用拉普拉斯展开来$O(n)$计算整行的行列式。

只需要写一个矩阵求逆就好了。时间复杂度$O(n^3+mn)$

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 #define mod 1000000007
 4 int A[333][333],n,m,w[333][333],ans,R[333][333],RET;
 5 int mo(int a){return a>=mod?a-mod:a;}
 6 int qp(int b,int t,int a=1){for(;t;t>>=1,b=1ll*b*b%mod)if(t&1)a=1ll*a*b%mod;return a;}
 7 int ret(int A[333][333]){
 8     int r=1,a[333][333];
 9     for(int i=1;i<n;++i)for(int j=1;j<n;++j)a[i][j]=A[i][j],R[i][j]=i==j;
10     for(int i=1;i<n;++i){
11         if(!a[i][i])for(int j=i+1;j<n;++j)if(a[j][i]){r=mod-r,swap(a[j],a[i]),swap(R[j],R[i]);break;}
12         r=1ll*r*a[i][i]%mod;
13         for(int iv=qp(a[i][i],mod-2),j=1;j<n;++j)a[i][j]=1ll*a[i][j]*iv%mod,R[i][j]=1ll*R[i][j]*iv%mod;
14         for(int j=1;j<n;++j)if(j!=i)for(int z=a[j][i],k=n-1;k;--k)
15             R[j][k]=mo(R[j][k]-z*1ll*R[i][k]%mod+mod),
16             a[j][k]=mo(a[j][k]-z*1ll*a[i][k]%mod+mod);
17     }return r;
18 }
19 int main(){
20     cin>>n>>m;
21     for(int i=1,x,y,z;i<=m;++i){
22         scanf("%d%d%d",&x,&y,&z);
23         if(x==n)continue;
24         w[x][y]=mo(w[x][y]+z),A[x][y]=mo(A[x][y]+mod-1),A[x][x]++,ans=mo(ans+z);
25     }
26     RET=ret(A); ans=1ll*ans*RET%mod;
27     for(int i=1;i<n;++i)for(int j=i+1;j<n;++j) swap(R[i][j],R[j][i]),R[i][j]=1ll*R[i][j]*RET%mod,R[j][i]=1ll*R[j][i]*RET%mod;
28     for(int i=1;i<n;++i)R[i][i]=1ll*R[i][i]*RET%mod;
29     for(int x=1;x<n;++x)for(int y=1;y<=n;++y)if(x!=y&&A[x][y]){
30         A[x][y]++,A[x][x]--;
31         for(int i=1;i<n;++i)ans=(ans-1ll*R[x][i]*A[x][i]%mod*w[x][y])%mod;
32         A[x][y]--,A[x][x]++;
33     }cout<<mo(ans+mod)<<endl;
34 }
View Code

猜你喜欢

转载自www.cnblogs.com/hzoi-DeepinC/p/12717305.html