2019杭电多校第八场补题(1006,1008)

1006:

赛场上想的比较麻烦的写法:
先把a,b做差,得到x,第一个取得到收益为x,第二个取得到收益-x。
fi[i][0]表示第一个人先取,取i为起始点,可以获得的最大收益,se[i][0]为对应的次大收益。
fi[i][1]表示第二个人先取,取i为起始点,可以获得的最大收益,se[i][1]为对应次大收益。
转移就是,第一个人取了i之后,第二个人要在i的儿子v里面取fi[v][1]最大的去转移,转移式子 f i [ i ] [ 0 ] = a [ i ] − m a x ( f i [ v ] [ 1 ] ) fi[i][0]=a[i]-max(fi[v][1]) fi[i][0]=a[i]max(fi[v][1]),
同理 f i [ i ] [ 1 ] = − a [ i ] − m a x ( f i [ v ] [ 0 ] ) fi[i][1]=-a[i]-max(fi[v][0]) fi[i][1]=a[i]max(fi[v][0])
可以通过一次O(N)的dfs获得对于某1个点为根的fi[i][0]和fi[i][1]。
然后进行换根dp,对于当前的总根u,我们要得到它的儿子v作为总根得到fi[v][0],se[v][0],fi[v][1],se[v][1]这四个参数,要看fi[u][0]是否由fi[v][1]转移得到,fi[u][1]是否由fi[v][0]得到,这样分类讨论。
这样写之后还是wa了,因为还要考虑一种情况:对于叶子结点v,无论它原来的fi[v][0]是否比a[v]-f[u][0]大,它都必须向上走,强制转移。
麻烦的代码:

#include<bits/stdc++.h>//BUG:在叶子结点,本来必须往上走
#define ll long long//但是由于原本的值比更新值大,导致出现第二个人不往上走的情况
using namespace std;
const int maxn = 1e5 + 50;
const ll inf = 0x3f3f3f3f3f3f3f3f;
int n;
vector<int> g[maxn];
ll a[maxn];
ll fi[maxn][2], se[maxn][2];//最小,次小
int du[maxn];
void init(){
    scanf("%d", &n);
    for(int i = 0; i <= n; ++i){
        g[i].clear();
        du[i] = 0;
    }
    for(int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
    for(int i = 1; i <= n; ++i) {
        ll x;
        scanf("%lld", &x);
        a[i] -= x;
    }
    for(int i = 1; i < n; ++i){
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }
}
void dfs1(int u, int f){
    fi[u][0] = fi[u][1] = se[u][0] = se[u][1] = inf;
    for(int i = 0; i < g[u].size(); ++i){
        int v = g[u][i];
        if(v == f) continue;
        dfs1(v, u);
        du[u]++;
        //0
        if(fi[u][0] > a[u] - fi[v][1]){
            se[u][0] = fi[u][0];
            fi[u][0] = a[u] - fi[v][1];
        }
        else if(se[u][0] > a[u] - fi[v][1]){
            se[u][0] = a[u] - fi[v][1];
        }
        //1
        if(fi[u][1] > -a[u] - fi[v][0]){
            se[u][1] = fi[u][1];
            fi[u][1] = -a[u] - fi[v][0];
        }
        else if(se[u][1] > -a[u] - fi[v][0]){
            se[u][1] = -a[u]-fi[v][0];
        }
    }
    if(!du[u]) {
        fi[u][0] = se[u][0] = a[u];
        fi[u][1] = se[u][1] = -a[u];
    }
}
void dfs2(int u, int f){
    if(f == 0){
        for(int i = 0; i < g[u].size(); ++i){
            int v = g[u][i];
            dfs2(v, u);
        }
        return;
    }
    //0
    if(-a[f]-fi[u][0] == fi[f][1]){//f的1是由u推得的,用se更新
        if(se[f][1] == inf) se[f][1] = -a[f];//除了u没地方走

        if(!du[u]) fi[u][0] = a[u] - se[f][1];//叶子,必须走

        else if(fi[u][0] > a[u] - se[f][1]){//1选f可以让0更小
            se[u][0] = fi[u][0];
            fi[u][0] = a[u] - se[f][1];
        }
        else if(se[u][0] > a[u] - se[f][1]){
            se[u][0] = a[u] - se[f][1];
        }
    }
    else{
        if(!du[u]) fi[u][0] = a[u] - fi[f][1];//叶子,必须走
        else if(fi[u][0] > a[u] - fi[f][1]){
            se[u][0] = fi[u][0];
            fi[u][0] = a[u] - fi[f][1];
        }
        else if(se[u][0] > a[u] - fi[f][1]){
            se[u][0] = a[u] - fi[f][1];
        }
    }
    //1
    if(fi[f][0] == a[f]-fi[u][1]){
        if(se[f][0] == inf) {//除了u没地方走
            se[f][0] = a[f];
        }

        if(fi[u][1] > -a[u]-se[f][0]){
            se[u][1] = fi[u][1];
            fi[u][1] = -a[u] - se[f][0];
        }
        else if(se[u][1] > -a[u]-se[f][0]){
            se[u][1] = -a[u] - se[f][0];
        }
    }
    else{
        if(fi[u][1] > -a[u]-fi[f][0]){
            se[u][1] = fi[u][1];
            fi[u][1] = -a[u]-fi[f][0];
        }
        else if(se[u][1] > -a[u]-fi[f][0]){
            se[u][1] = -a[u]-fi[f][0];
        }
    }
    //处理儿子
    for(int i = 0; i < g[u].size(); ++i){
        int v = g[u][i];
        if(v == f) continue;
        dfs2(v, u);
    }
}
void sol(){
    dfs1(1, 0);
//    for(int i = 1; i <= n; ++i){
//        cout<<"i:"<<i<<" fi0:"<<fi[i][0]<<" se0:"<<se[i][0]<<" fi1:"<<fi[i][1]<<" se1:"<<se[i][1]<<endl;
//    }
    dfs2(1, 0);
    ll ans = fi[1][0];
    for(int i = 1; i <= n; ++i){
        //cout<<"i:"<<i<<" fi0:"<<fi[i][0]<<" se0:"<<se[i][0]<<" fi1:"<<fi[i][1]<<" se1:"<<se[i][1]<<endl;
        ans = max(ans, fi[i][0]);
    }
    printf("%lld\n", ans);
}
int main()
{
	int T;cin>>T;
	while(T--){
        init();sol();
	}
}
/*
8
8
2 -3 1 2 -3 -4 -2 -6
0 0 0 0 0 0 0 0
1 2
2 8
2 3
3 4
4 5
3 6
6 7
*/

题解的写法是,每个点价值都是a-b,第一个人要让总价值最大,第二个人要让总价值最小。那么用fi[i][0]表示第一个人先选i之后可以得到的最大价值,se[i][0]为对应次大价值,fi[i][1]表示第二个先选i之后可以得到的最小价值,se[i][1]为对应次小价值。
其实也可以换根dp,也可以分别考虑走父亲和走儿子。
依然要特判叶子结点的情况。
这是分别考虑父亲和儿子的代码:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn = 1e5 + 50;
const ll inf = 0x3f3f3f3f3f3f3f3f;
int n;
vector<int> g[maxn];
ll a[maxn];
ll fi[maxn][2], se[maxn][2];
ll p[maxn][2];//p[i][0]表示第一个人选i,并且往父亲方向走可以得到的最大收益,p[i][1]为第二个人选i并往父亲走的最小收益
int du[maxn];
void init(){
    scanf("%d", &n);
    for(int i = 0; i <= n; ++i){
        g[i].clear(); du[i] = 0;
    }
    for(int i = 1; i <= n; ++i) scanf("%lld", &a[i]);
    for(int i = 1; i <= n; ++i) {
        ll x;
        scanf("%lld", &x);
        a[i] -= x;
    }
    for(int i = 1; i < n; ++i){
        int u, v;
        scanf("%d%d", &u, &v);
        g[u].push_back(v);
        g[v].push_back(u);
    }
}
void dfs1(int u, int f){
    fi[u][0] = se[u][0] = inf;
    fi[u][1] = se[u][1] = -inf;
    for(int i = 0; i < g[u].size(); ++i){
        int v = g[u][i];
        if(v == f) continue;
        dfs1(v, u);
        du[u]++;
        if(fi[u][0] > fi[v][1] + a[u]) se[u][0] = fi[u][0], fi[u][0] =fi[v][1] + a[u];//0已经选了u,1会在v中挑一个最小的和u连起来。
        else if(se[u][0] > fi[v][1] + a[u]) se[u][0] =fi[v][1] + a[u];

        if(fi[u][1] < fi[v][0] + a[u]) se[u][1] = fi[u][1], fi[u][1] = fi[v][0] + a[u];//1选了u,0会在v中挑一个最大的和u连起来
        else if(se[u][1] < fi[v][0] + a[u]) se[u][1] = fi[v][0] + a[u];
    }
    if(!du[u]) fi[u][0] = fi[u][1] = se[u][0] = se[u][1] = a[u];//叶子结点
    return;

}
void dfs2(int u, int f){
    for(int i = 0; i < g[u].size(); ++i){
        int v = g[u][i];
        if(v == f) continue;
        if(du[u] == 1){//u只能走到v,那么v的p只能用u的p推
            p[v][0] = p[u][1] + a[v];
            p[v][1] = p[u][0] + a[v];
        }
        else{//否则也可以走到u的其他儿子
            ll r;
            if(fi[u][1] != fi[v][0] + a[u]) r = fi[u][1];
            else r = se[u][1];
            if(u != 1) r = max(p[u][1], r);
            p[v][0] = a[v] + r;

            if(fi[u][0] != fi[v][1] + a[u]) r = fi[u][0];
            else r = se[u][0];
            if(u!=1) r = min(r, p[u][0]);
            p[v][1] = a[v] + r;
        }
        dfs2(v, u);
    }
}
void sol(){
    dfs1(1, 0);
    p[1][0] = p[1][1] = a[1];
    dfs2(1, 0);
    ll ans = fi[1][0];
    for(int i = 2; i <= n; ++i){
        //cout<<"i:"<<i<<" fi0:"<<fi[i][0]<<" se0:"<<se[i][0]<<" fi1:"<<fi[i][1]<<" se1:"<<se[i][1]<<endl;
        if(du[i]) ans = max(ans, min(fi[i][0], p[i][0]) );
        else ans = max(ans, p[i][0]);
    }
    printf("%lld\n", ans);
}
int main()
{
	int T;cin>>T;
	while(T--){
        init();sol();
	}
}
/*
8
8
2 -3 1 2 -3 -4 -2 -6
0 0 0 0 0 0 0 0
1 2
2 8
2 3
3 4
4 5
3 6
6 7
*/

1008: Andy and Maze(彩色编码)

题解使用了一个技术:彩色编码技术
原本要求最长的包含k个结点的简单路径。(k<=6)
给每个结点随机染色[0,k-1],然后求包含k个两两颜色不同的结点的最长路径。问题变成这样之后就可以状压DP求了:
dp[i][mask]表示路径以i为结尾,包含的颜色集合为mask的简单路径的最长长度。转移方程好想。
这是个随机化算法,我们要知道它的正确率。如果存在答案,那么答案一定是一个包含k个结点的简单路径。这k个结点刚好被染成k个不同颜色的概率是: k ! k k \frac{k!}{k^k} kkk!.这是它的正确率,大概在1/60左右。我们进行1000次运算就可以把错误率降低到千万分之一的级别(但是会T)。进行200次运算(6个结点的时候正确率大概95%),剩下看脸。ε=

#include<bits/stdc++.h>
#define ll long long
#define P pair<int, int>
using namespace std;
const int maxn = 1e4 + 50;
vector<P> g[maxn];
int dp[maxn][64];
int n, m, k;
void init()
{
    scanf("%d%d%d",&n, &m, &k);
    for(int i = 0; i <= n; ++i) g[i].clear();
    while(m--){
        int u, v; ll w;
        scanf("%d%d%d",&u,&v,&w);
        g[u].push_back(P(v, w));
        g[v].push_back(P(u, w));
    }
}
int c[maxn];
int sol(){
    for(int i = 1; i <= n; ++i) for(int j = 0; j < (1<<k); ++j) dp[i][j] = -1;
    for(int i = 1; i <= n; ++i){
        c[i] = rand()%k;
        dp[i][1<<c[i]] = 0;
    }
    for(int mask = 1; mask < (1<<k); ++mask){
        for(int u = 1; u <= n; ++u){
            if(! ((1<<c[u])&mask) ) continue;
            for(int i = 0; i < g[u].size(); ++i){
                int v = g[u][i].first;
                if((1<<c[v])&mask){
                    if(dp[u][mask^(1<<c[v])] >= 0)
                    dp[v][mask] = max(dp[v][mask], dp[u][mask^(1<<c[v])]+g[u][i].second );
                }
            }
        }
    }
    int ans = 0;
    for(int i = 1; i <= n; ++i) ans = max(ans, dp[i][(1<<k)-1]);
    return ans;
}
int main()
{
	int T;cin>>T;
	while(T--){
        init();
        int ans = 0;
        for(int i = 0; i < 200; ++i) ans = max(ans, sol());
        if(ans == 0) printf("impossible\n");
        else printf("%d\n",ans);
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值