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);
}
}