大约是来搞笑的了。
$T1$想到了$wqs$二分但是不会(或者说根本没往这个方向想)$dp$。
$T2$神仙题暂且不管($15$是全场最高但是有个啥用?)
$T3$的话会$30pts$的状压,但是觉得对我整场的分数并没有什么作用于是也就没有写。随便输出了个$0$拿走$10pts$
主观来讲,我大概是没长脑子,啥都想不出来
客观来讲,题有一点不对胃口,好像考试的时候始终不太敢去猜测性质然后就开始写代码
那些用的不多的知识点以及扩展出来的性质就更不敢用了,可能也是导致这样的原因
大约多做题是一个合理的解决办法,至少下次再遇到这些东西,我不会慌了。。。
又把锅甩给了下次啊。。。
T1:新访问计划
大意:边权树,要求遍历每条树边至少一次最后回到根,可以花费$c$的代价直接传送到任意点最多$k$次。求最小代价。多测。$sum n,k le 10^5,n le 2 imes 10^4,w_i,c le c imes 10^4$
可以转化题意:首先要求回路那么所有树边必须走一次,接下来你可以用树边 和 代价为$c$的树上路径至多$k$条 来覆盖整棵树。求最小代价。
可以搞出一个$dp$表示$f[i][j][0/1]$表示是否有一个未配对(可以再次转弯的)路径连向当前$i$且子树内完全被覆盖而已经申请了$j$条路径的最小代价。
子树归并。$0/1 + 0/1 ightarrow 0/1$共$8$种情况其中有一种不合法剩下的按照含义转移即可。
复杂度是$O(n^2)$的。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }}
这题的含义让人不难想到$wqs$二分。
但是我们的要求是,第二维也就是已使用的路径数不能超过$k$。所以我们再开一个数组存储在此代价下最小的路径使用数。
二分并据此查找合适的$c$
如果对于某个特定的$c_0$最有决策下使用的路径书是$x$,对于$c_1$也是,那么在这两个代价下的决策也一定是完全一样的。
所以说,我们可以先进行一次$dp$,然后看最有决策使用的路径数是否超过$k$
如果没有超过那么就是说最优解就合法可以直接输出。否则,我们一定希望我们恰好选择$k$条路径。
那么通过$wqs$去找恰好$k$条路径的方案,如果不存在恰好为$k$的,那么也当成$k$去计算就好了。
(此时一定是存在多条路径,选择它们的收益相同,所以才会一选都选一不选都不选,然而恰好选$k$个是最优的,依次贡献答案是最佳的)
总的时间复杂度是$O(n log w)$
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }}
T2:计算几何
看到题目名果断弃坑。
T3:树形图求和
大意:计算所有以$n$为根的内向树边权和。$n le 300,m le 10^5$
原来$Matrix-Tree$是可以用在内向树之类的东西上的啊。
对于内向树我们建基尔霍夫矩阵的时候,边只用单向边,度数只用出度,然后做$Matrix-Tree$的时候只要强制删掉的是根节点所在行列即可。
首先求出整个图有多少生成内向树。
然后只需要枚举每种边,把它去掉再求内向树个数,相减就能得到包含它的内向树个数。乘上权值就可以计算其贡献。
复杂度$O(mn^3)$
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }
复杂度瓶颈在于每次都要求行列式,于是我们引入(大量)线性代数知识:
设$R$为$B$的余子式矩阵,则有拉普拉斯展开:
$ret(B) = forall x sumlimits_{i=1}^{n} R_{x,i} B_{x,i}$
$ret(B) = forall y sumlimits_{j=1}^{n} R_{j,y} B_{j,y}$
其中,$R=(ret(B) B^{-1} )^T$
(其实还有点中间过程,就是说:余子式矩阵是伴随矩阵的转置,伴随矩阵乘原矩阵是原矩阵行列式倍的单位矩阵)
证明显然不会(也查不到)
我们发现我们要求解行列式之前每次会删除一条边,矩阵的一行的两个元素发生改变,其余均不变。
然后只改变第$x$行的元素那么这一行的余子式也不会改变(含义嘛,把这一行删掉了有什么影响)
所以我们有了这一行的余子式,还有这一行矩阵的新值,我们就可以直接套用拉普拉斯展开来$O(n)$计算整行的行列式。
只需要写一个矩阵求逆就好了。时间复杂度$O(n^3+mn)$
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
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 }