Test 2022.10

20221008 DP:树形DP

Review

1. T1 P3177 [HAOI2015] 树上染色

一月前做过,然二Test挂分了
一眼树形DP,应该都能想到 DP状态:\(f[u][i]\)表示\(u\)子树染i个黑色的价值;

DP转移:考虑贡献由每条边做出,
对于边\(u->v\),首先想到\(f[u][j]\)计入\(u->v\)对子树贡献\(w\);
但子树之间不好转移,突然发现:子树v的染黑点数\(k\)确定意味着\(v\)子树内白点数和外部的黑白点数全部确定,我们却可以直接得到\(u->v\)对整棵树贡献\(=w*(k*(siz[v]-k)+(N-K-siz[v]+k))*(K-k))\)
那么\(f[u][j]\)严格定义为表示\(u\)子树染i个黑色时内部所有边对整棵树的最大贡献和。
细节:1.非法状态(无法达成)设\(-1\),初始化\(f[u][0]=f[u][1]=0\); 2.\(v\)转移\(u\)时最初需转移\(0\)(状态一定合法),因此正序枚举第二维更好。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll inf=0x3f3f3f3f3f3f3f3f, maxn=2e3+3;

ll n, K;
/*...edge...*/
ll f[maxn][maxn];

ll siz[maxn];
void dfs(ll u, ll fa){
    siz[u]=1, f[u][0]=f[u][1]=0;
    for(ll i = hd[u]; i ; i=e[i].nxt){
	ll v=e[i].to, w=e[i].w;
	if(v==fa) continue;
	dfs(v, u), siz[u]+=siz[v];
		
	for(ll j = min(K, siz[u]); j >= 0 ; j--)
	for(ll k = 0; k <= min(j, siz[v]) ; k++)
	if(f[u][j-k]!=-1)  f[u][j]=max(f[u][j], f[u][j-k]+f[v][k]+w*( (K-k)*k+(siz[v]-k)*(n-K-siz[v]+k) ));
    }
}
int main{
    /*...input/add_edge...*/
    memset(f, -1, sizeof(f));
    dfs(1, 0);
    /*...output...*/
    return 0;
}

2. T2 山顶问题

话说某某在cj校运会上异军突起,其实不是偶然,而是有历史原因的。
从某某家到学校中间的这\(N\)里山路在一条直线上,第\(i\)里山路的海拔高度为\(H_i\),如果一段相同高度的山路两边都比它低或者是山的边界,那么这段山路将被称之为“山顶”。降低山路海拔高度使山顶个数不超过\(K\),最小化所有山路降低的高度和。
简明题意:给定序列,定义山顶为一段等值连续子序列(序列两侧的值低于序列值或为序列边界),一次操作为令\(a_i--\);求使序列山顶数不超过\(k\)的最少操作次数。

本题2个难点:1.序列转树。 2.建树。 3.树上操作转移
设山顶\(a\)长度为\(lena\)
0.观察:削平一个山顶平面\(a\),一定得到新高度平面\(b\),代价为\(lena\)
1.序列转树:根据\(a\)依赖于\(b\),可以设\(b->a\)\(a\)\(b\)子节点); 同时说明删\(b\)前先删\(a\)。对于所有节点,节点代表一个高度平面,节点值\(a[i]\)即为删除削平平面到下一更低平面的代价\(lenx*(h[x]-h[nxtlower])\)
2.建树:思路如1.所述。难在实现:使用单调栈(\(stk\));
\(\quad\) 1.\(h[i]>stk[top]\):直接入栈;
\(\quad\) 2.\(h[i]==stk[top]\):累加当前高度平台长度(\(cnt[top]++\)
\(\quad\) 3.\(h[i]<stk[top]\):退掉栈顶,循环退栈直到\(h[i]>=stk[top]\)
\(\quad\) 若其中\(stk[top-1]<h[i]\),原\(top\)的树上父亲为新建高度平台\(x\),否则为\(stk[top-1]\)(保证高度层级递减);
3.树上操作转移:设\(f[u][k]\)\(u\)子树只剩\(k\)个叶节点的代价。注意:1.\(u\)子树操作完后\(f[u][0]+=a[u]\)(把自己删了); 2.叶节点\(f[u][1]=0\)(不用删就只有1个点);

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll inf=0x3f3f3f3f3f3f3f3f, maxn=1e5+5;

ll n, K;
ll h[maxn], a[maxn], id[maxn];//id[x]为栈中stk[x]的树结点编号
ll stk[maxn], top, cnt[maxn], tot;//stk为单调栈(top栈顶),cnt为当前高度平台长度,tot为高度平台数(树结点数)
vector<ll> e[maxn];
 
ll siz[maxn], f[maxn][27], dp[27];
void dfs(ll u){
    siz[u]=1, f[u][0]=0;
    ll lv=1;//叶节点特殊处理
    for(auto v : e[u])  lv=0, dfs(v), siz[u]+=siz[v];
    for(auto v : e[u]){
        memcpy(dp, f[u], sizeof(f[u]));//copy一份 避免转移效果重叠
        memset(f[u], 0x3f, sizeof(f[u]));
        for(ll i = 0; i <= min(K, siz[u]); i++)
        for(ll j = 0; i+j <= min(K, siz[u]); j++)
            f[u][i+j] = min(f[u][i+j], dp[i]+f[v][j]);
    }
    f[u][0]+=a[u];
    if(lv) f[u][1]=0;
}
int main(){
    scanf("%lld%lld", &n, &K);
    ll mi=inf;
    for(ll i = 1; i <= n ; i++) scanf("%lld", &h[i]), mi=min(mi, h[i]);
    h[0]=h[n+1]=mi;
     
    for(ll i = 0; i <= n+1 ; i++){//建树:单调栈 十分细节
        bool f=0;
        while(h[i]<stk[top] && top>0){//h[i]<stk[top]
            if(stk[top-1]>=h[i]) e[id[top-1]].push_back(id[top]);//stk[top-1]>=h[i]
            else{                                                //stk[top-1]<h[i]
                e[++tot].push_back(id[top]);
                a[id[top]]=cnt[top]*(stk[top]-h[i]);
                id[top]=tot;
                stk[top]=h[i], cnt[top]+=1;
                f=1; continue;
            }
            a[id[top]]=cnt[top]*(stk[top]-stk[top-1]);
            cnt[top-1]+=cnt[top];
            cnt[top--]=0;
        }

        if(f) continue;
        if(h[i]>stk[top]) stk[++top]=h[i], cnt[top]=1, id[top]=++tot;//h[i]>stk[top]
        else if(h[i]==stk[top]) cnt[top]++;                          //h[i]==stk[top]
    }
    a[1]=n;//Sepcially: a[1]=n;

    memset(f, 0x3f, sizeof(f));
    dfs(1);

    printf("%lld", K>=tot?0:f[1][K]);
    return 0;
}

3. T3 P3565 [POI2014]HOT-Hotels

设一个可行三元组\((a, b, c)\)距离中心(以下简称中心)为\(u\),总答案\(ans\)
枚举\(u\in[1, n]\)
对于每一个\(u\):
\(\quad\) \((a,b,c)\)中心为u当且仅当:1.\(a,b,c\)分属u的3个不同子树;\(\quad\) 2.以\(u\)为根时:\(dep[a]=dep[b]=dep[c]\)
\(\quad\) \(p1[dep]\)记已处理所有子树中深度为\(dep\)\(1\)个点的方案数,\(p2[dep]\)记已处理所有子树中深度为\(dep\)\(2\)个点对(两点不同子树)的方案数。
\(\quad\) 对于每个子树\(v\)\(f[dep]\)记录当前子树\(v\)深度\(dep\)的节点数
\(\quad\) \(dfs->f[dep], ans+=f[dep]*p2[dep]\)
\(\quad\) \(p2[dep]+=p1[dep]*f[dep], p1[dep]+=f[dep];\)
时间复杂度:\(\mathcal{O}(n^2)\)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll inf=0x3f3f3f3f3f3f3f3f, maxn=5e3+5;

ll n;
vector<ll> e[maxn];
ll f[maxn], p1[maxn], p2[maxn], mxdep, ans;
void dfs(ll u, ll fa, ll dep){
    mxdep=max(mxdep, dep), f[dep]++;
    for(auto v : e[u]) if(v!=fa) dfs(v, u, dep+1);
}

int main(){
    scanf("%lld", &n);
    for(ll i = 1, u, v; i < n ; i++) u=read(), v=read(), e[u].push_back(v), e[v].push_back(u);

    for(ll i = 1; i <= n ; i++){//以每个点作为中心(根节点)
        memset(p1, 0, sizeof(p1)), memset(p2, 0, sizeof(p2));
        for(auto v : e[i]){//每个子树查找
            memset(f, 0, sizeof(f));
            mxdep=0, dfs(v, i, 1);

            for(ll j = 1; j <= mxdep ; j++){
                ans+=f[j]*p2[j];//f[d]为在当前子树选1个深为d的点
                p2[j]+=p1[j]*f[j];//p2[d]为在前面子树选2个深为d的点
                p1[j]+=f[j];//p1[d]为在前面子树选1个深为d的点
            }
        }
    }
    /*...output...*/
    return 0;
}

20221009 图论:最短路

Review

1. T1 分层图模板 P2939 [USACO09FEB]Revamping Trails G/P4822 [BJWC2012]冻结

两个板子,除了分层处理套最短路没啥了。代码/记录:P2939/P4822
分层图板子题:P4568 [JLOI2011]飞行路线//P3119 [USACO15JAN]Grass Cownoisseur G//UVA11374 Airport Express

2. T2 P6822 [PA2012]Tax

最短路思维题。
点代价依赖于入边和出边的较大值,考虑构建新图-化边为点:即到达该条边的代价;

对于原图每条边 \(e\)
\(\quad\) 1.向该边的反向边 \(e'\) 连有向边, 权值为原边权值 \(w\)
\(\quad\) 2.对于原图每个点 \(u\) 的出边:配对连边;
\(\quad\quad\) 注意到连边菊花图空间极限 \(\mathcal{O}(n^2)\) ,考虑优化:将 \(u\) 出边按权值由小至大排序。
\(\quad\quad\)\(e_i\) 向边 \(e_{i+1}\) 连一条权值为 \(e_{i+1}.w-e_i.w\) 的有向边,并反过来建一条权值为 0 的有向边;
\(\quad\quad\) 解释: 差分传递了新图边的代价,与 \(\mathcal{O}(n^2)\) 配对等价(思考优化极妙)。
\(\quad\) 3.建立超级源点/汇点:\(s/t\) ; 原图\(1\) 号点的出边连 \(s\)\(n\) 号点的入边连 \(t\)

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll inf=0x3f3f3f3f3f3f3f3f, maxn=300005;

ll n, m;
ll s=0, t=1;
struct Edge{
    ll v, w, id;
    bool operator <(const Edge &y)const{return w<y.w;}
};
vector<Edge> g[maxn<<1], e[maxn<<1];//g为原图,e为新图
vector<Edge>::iterator it;
void add(ll u, ll v, ll w){e[u].push_back((Edge){v, w, u});}

/*...Dijktra()...*/

int main(){
    scanf("%lld%lld", &n, &m);
    for(ll i = 1; i <= m ; i++){
        ll u, v, w;
        scanf("%lld%lld%lld", &u, &v, &w);
        g[u].push_back((Edge){v, w, i*2+1}), g[v].push_back((Edge){u, w, i*2});//异或即得反向边
    }

    for(ll i = 1; i <= n ; i++){
        if(g[i].empty()) continue;
        sort(g[i].begin(), g[i].end());
        for(it=g[i].begin(), ++it; it!=g[i].end(); ++it){//同一个中转点 差分建边
            add((it-1)->id, it->id, it->w - (it-1)->w);
            add(it->id, (it-1)->id, 0);
        }
        for(auto j : g[i]){//所有边向反向边连边
            add(j.id^1, j.id, j.w);
            if(i==1) add(s, j.id, j.w);
            if(j.v==n) add(j.id, t, j.w);
        }
    }
    Dijktra();
    /*...output...*/
    return 0;
}

3. T3 BZOJ4144 [AMPPZ2014]Petrol

最短路+最小生成树MST
实际上:我们只需考虑油量上限为b时图中所有油站的连通性,离线查询即可。

处理
油站 \(x,y\) 何时连通?即存在点 $z $满足 \(dis(x, z)+dis(y, z)<=b\)
难在每对油站距离不好处理——我们却可以处理每个点 \(z\) 最近加油站 \(x\) !!!(可以证明每个点往最近油站跑一定不劣)
设点 \(z\) 最近加油站为 \(c[z]\) ,距离为 \(dis[z]\) ;
那么对于边 \(e:u->v\)
\(c[u]!=c[v]\) :新边 \(c[u]->c[v],w=dis[u]+dis[v]+e.w\) 表示油量至少为 \(w\)\(c[u]\)\(c[v]\) 连通(属同一连通块)
查询:新边从小到大排序,并查集维护连通性

\(c[z],dis[z]\) :多个油站跑多源最短路
\(query(x, y, b)\) :询问b从小到大排序,新边从小到大排序,并查集维护连通性。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pb push_back
const ll inf=0x3f3f3f3f3f3f3f3f, maxn=4e5+5;
 
ll n, s, m, Q;
ll p[maxn], c[maxn];
struct Edge{ll v, w;};
vector<Edge> e[maxn<<1];

/*...void Dijkstra()...*/
 
struct Ed{//新边
    ll u, v, w;
    bool operator <(Ed y)const{return w<y.w;}
} E[maxn<<2];
ll fa[maxn];//MST/连通性
ll gf(ll x){return fa[x]==x?x:fa[x]=gf(fa[x]);}
ll ans[maxn];//询问与答案 query/ans
struct Qy{
    ll x, y, b, num;
    bool operator <(Qy y)const{return b<y.b;}
} q[maxn];
 
int main(){
    /*...input/add_p(station)/add_edge/add_E...*/
    Dijkstra();
     
    for(ll i = 1; i <= m ; i++){
        ll u=E[i].u, v=E[i].v;
        if(c[u]==c[v]) continue;
        E[i].u=c[u], E[i].v=c[v], E[i].w=dis[u]+dis[v]+E[i].w;
    }
    sort(E+1, E+m+1);

    /*...Q/get_query/sort_query...*/
    for(ll i = 1; i <= n ; i++) fa[i]=i;
    ll nw=1;//当前边
    for(ll i = 1; i <= Q ; i++){
        while(E[nw].w<=q[i].b&&nw<=m){//满足油量限制
            ll u=E[nw].u, v=E[nw].v, fu, fv;
            fu=gf(u), fv=gf(v);
            if(fu!=fv) fa[fv]=fu;
            nw++;
        }
        if(gf(q[i].x)==gf(q[i].y)) ans[q[i].num]=1;//查询连通性
    }
    /*...output...*/
    return 0;
}
posted @ 2022-10-09 13:51  Yzfu  阅读(41)  评论(0)    收藏  举报