失分小结:
估分:200+
实际分数:200
考试过程:
第一题算是近几场中打起来较麻烦的,但理清关系还是简单的
第二题打的也十分流畅,先
2n
枚举水30,再暴力
n4
dp
前缀和优化到
n3
再到
n2
,然后对拍
这时离考试结束还有两个半小时
第三题先是打了暴力,感觉不是特别好,但也没有去检查它的正确性
模拟了一下发现就是个路径压缩,就开始敲,快交的的时候才调过样例,
自己对拍的时候错误百出。。。
考完才发现暴力敲错了,正解最小生成树把m打成了n。。。
痛失100(╯°Д°)╯︵┻━┻
小结:考试别总是想着正解正解。。暴力对了再说,不然敲完也没法保证正确性
题解:
第一题:
一道搜索题,
听说好多人都卡在memset上了┓( ´-` )┏
较为简单的做法是先求联通块,分别给他们染色,并记录每种颜色连通块的个数
枚举把那个变成“.”看上下左右是否有连通块,然后把颜色不同的连通块个数相加就好了
第二题:
如果做过完美队形,这道题其实是很简单的,
这道题的动态规划很显然,而且是一道区间dp
二维的初始dp就是:
dp[i][j]=∑dp[h][k]+1
{
h∈[1,i]
,
k∈[j,n]
&&
i!=j
}
ans+=dp[i][j]+∑dp[h][k]
{
i==j
}
第二条式子表示取i这个点作为回文子串的中点的方案数
dp[i][j]表示从1取到i从n取到j的子串方案数
然后不难发现
∑dp[h][k]
是可以前缀和处理的,然后复杂度就会由
n4
降到
n2
具体代码实现:
int dp[2005][2005];
int Sum[2005][2005];
void solve(){
memset(dp,0,sizeof(dp));
int ans=0;
FOR(i,1,n){
FOR(j,i,n){
if(A[i]==A[j]&&i!=j){
dp[i][j]=Sum[i-1][j+1]+1;
if(dp[i][j]>=P)dp[i][j]-=P;
}
if(i==j){
ans++;
ans+=Sum[i-1][j+1];
if(ans>=P)ans-=P;
}
ans+=dp[i][j];
if(ans>=P)ans-=P;
}
DOR(j,n,i){
Sum[i][j]=Sum[i][j+1]+dp[i][j];// j--n
Sum[i][j]%=P;
}
FOR(j,i,n){
Sum[i][j]+=Sum[i-1][j];
Sum[i][j]%=P;
}
}
printf("%d\n",ans);
}
第三题
首先生成最小生成树S(在所有边都有的情况下)
然后可得知:若把非这个最小生成树上的边删去,那么最后生成的最小生成树就是S,则这些非树边的答案就是S的权值
但若把S上的边删去,情况就不同了
假设能生成最小生成树,则只需加入一条边
设加入边的权值为a,S的权值为A,删除边的权值为b
则改变后的生成树的权值为A-b+a
可以发现对于一条S上的边,删除它后的生成树大小只与加入的边有关
定理一:要是生成树为最小生成树,则要加入的边最小
可证加入一条边后,在原最小生成树S上会构成一个环,删去还上任意一条边,都可形成一棵树,即:
定理二:加进去的边能更新其两端点到LCA上的任意边
我们根据边权从小到大排序,加入一条边后把其两端点到LCA路径上的所有点更新,根据定理一,得出
定理三:被更新的边已经是最优解,则不必在更新一遍
那么可以采用路径压缩(并查集)保证每条边只被更新一次
代码实现:
void dfs(int x,int f,int id,int v) {
fa[0][x]=f;//记录x点的父亲
fa[1][x]=id;//记录x点到父亲这条边的编号
fa[2][x]=v;//记录x点到父亲这条边的边权
dep[x]=dep[f]+1;
for(unsigned i=0; i<edge[x].size(); i++) {
int y=edge[x][i].to;
if(y==f)continue;
dfs(y,x,edge[x][i].id,edge[x][i].v);
}
}
int Find(int x) {return x==FA[x]?x:FA[x]=Find(FA[x]);}
void up(int &x,int v) {//并查集实现路径压缩
if(ans[fa[1][x]]==INF)ans[fa[1][x]]=Mi-fa[2][x]+v;
FA[x]=fa[0][x];
x=Find(x);
}
void LCA(int x,int y,int v) {
while(x!=y) {
if(dep[x]>dep[y])up(x,v);
else up(y,v);
}
}
int deal() {//最小生成树
sort(A+1,A+m+1,cmp);
FOR(i,1,n)Fa[i]=i;
int sum=0;
FOR(i,1,m) {
int x=A[i].x,y=A[i].y;
int f1=find(x),f2=find(y);
if(f1==f2)continue;
Fa[f1]=f2;
edge[x].pb((node1) {y,A[i].id,A[i].v});
edge[y].pb((node1) {x,A[i].id,A[i].v});
sum+=A[i].v;
mark[A[i].id]=1;
}
return sum;
}
void solve() {
FOR(i,1,m)ans[i]=INF;
FOR(i,1,m) {
scanf("%d %d %d",&A[i].x,&A[i].y,&A[i].v);
A[i].id=i;
}
Mi=deal();
FOR(i,1,n)FA[i]=i;
dfs(1,0,0,0);
FOR(i,1,m) {//若为非树边,进行更新
if(mark[A[i].id])continue;
else {
LCA(A[i].x,A[i].y,A[i].v);
ans[A[i].id]=Mi;
}
}
FOR(i,1,m) {//输出答案,若为INF,说明没有被更新过,即不存在最小生成树
if(ans[i]==INF)printf("%d\n",-1);
else printf("%d\n",ans[i]);
}
}
int main() {
scanf("%d %d",&n,&m);
solve();
return 0;
}