今天来了一波很成功的la1la1la四连做..
还是同样的激情0.0..
还是熟悉的爆零..
先把 ai 减去 i 把问题转化成单调不降..
然后比较显而易见地就可以知道最终的序列的数一定是原序列中出现过的..
那么简单dp乱搞就可以了..
#include
#include
#include
#include
#define LL long long
using namespace std;
const LL Maxn = 3010;
LL a[Maxn], b[Maxn], bl, n;
LL f[Maxn][Maxn];
LL _abs ( LL x ){ return x < 0 ? -x : x; }
LL _min ( LL x, LL y ){ return x < y ? x : y; }
int main (){
//freopen ( "a.in", "r", stdin );
//freopen ( "a.out", "w", stdout );
LL i, j, k;
scanf ( "%I64d", &n );
for ( i = 1; i <= n; i ++ ){
scanf ( "%I64d", &a[i] );
a[i] -= i;
b[i] = a[i];
}
sort ( b+1, b+n+1 );
bl = unique ( b+1, b+n+1 ) - (b+1);
memset ( f, 63, sizeof (f) );
for ( i = 1; i <= bl; i ++ ){
f[1][i] = _abs (b[i]-a[1]);
f[1][i] = _min ( f[1][i], f[1][i-1] );
}
for ( i = 2; i <= n; i ++ ){
for ( j = 1; j <= bl; j ++ ){
f[i][j] = f[i-1][j] + _abs (b[j]-a[i]);
f[i][j] = _min ( f[i][j], f[i][j-1] );
}
}
printf ( "%I64d\n", f[n][bl] );
return 0;
}
比赛的时候想到一个类似于就是正解的做法.. 然而我并不能实现..
好悲催啊0.0..
首先这棵树肯定是小于等于 20 层的..(显而易见的啊..不信自己打表去..)
dp两个数组, f1x,i 表示 x 节点最近的水井在该子树内距离为 i 的最小花费, f2x,i 表示 x 节点最近的水井在该子树外距离为 i 的最小花费..
推啊推啊就行了0.0..
#include
#include
#include
#include
using namespace std;
const int Maxn = 200010;
struct node {
int y, next;
}a[Maxn]; int first[Maxn], len;
void ins ( int x, int y ){
len ++;
a[len].y = y;
a[len].next = first[x]; first[x] = len;
}
int phi[Maxn], pr[Maxn], prl; bool v[Maxn];
int f1[Maxn][41], f2[Maxn][41], n, cost[Maxn];
int g[Maxn];
int _min ( int x, int y ){ return x < y ? x : y; }
void get (){
int i, j;
for ( i = 2; i <= n; i ++ ){
if ( v[i] == false ){
pr[++prl] = i;
phi[i] = i-1;
}
ins ( phi[i], i );
for ( j = 1; j <= prl && i*pr[j] <= n; j ++ ){
v[i*pr[j]] = true;
if ( i % pr[j] == 0 ){
phi[i*pr[j]] = phi[i] * pr[j];
break;
}
else phi[i*pr[j]] = phi[i] * phi[pr[j]];
}
}
}
void dfs ( int x ){
int i, j, k;
if ( first[x] == 0 ){
f1[x][0] = g[x] = cost[0];
for ( i = 0; i <= 40; i ++ ) f2[x][i] = cost[i];
return;
}
for ( k = first[x]; k; k = a[k].next ){
int y = a[k].y;
dfs (y);
}
for ( i = 0; i <= 40; i ++ ){
f1[x][i] = f2[x][i] = cost[i];
int Min = 0x7fffffff;
for ( k = first[x]; k; k = a[k].next ){
int y = a[k].y;
int t = _min ( g[y], f2[y][i+1] );
f1[x][i] += t;
f2[x][i] += t;
if ( i > 0 ) Min = _min ( f1[y][i-1]-t, Min );
}
if ( i > 0 ) f1[x][i] += Min;
g[x] = _min ( g[x], f1[x][i] );
}
}
int main (){
int i, j, k;
scanf ( "%d", &n );
get ();
for ( i = 0; i < n; i ++ ) scanf ( "%d", &cost[i] );
memset ( f1, 63, sizeof (f1) );
memset ( f2, 63, sizeof (f2) );
memset ( g, 63, sizeof (g) );
dfs (1);
printf ( "%d\n", g[1] );
return 0;
}
首先如果只能放置一个点的话那肯定是放在这棵树的重心吧0.0..
那么这道题就相当于把整棵树分成两块,两块的点分别去该块的重心..
所以我们就枚举断开的边然后乱搞就行了..
#include
#include
#include
#include
using namespace std;
const int Maxn = 50010;
struct node {
int y, next;
}a[Maxn*2]; int first[Maxn], len;
void ins ( int x, int y ){
len ++;
a[len].y = y;
a[len].next = first[x]; first[x] = len;
}
int w[Maxn], size[Maxn], sum[Maxn], dep[Maxn];
int f1[Maxn], f2[Maxn], fa[Maxn][18];
int son1[Maxn], son2[Maxn];
int n;
void dfs1 ( int x, int f ){
size[x] = w[x];
dep[x] = dep[f]+1;
fa[x][0] = f;
for ( int i = 1; i <= 16; i ++ ){
fa[x][i] = fa[fa[x][i-1]][i-1];
}
for ( int k = first[x]; k; k = a[k].next ){
int y = a[k].y;
if ( y == f ) continue;
dfs1 ( y, x );
size[x] += size[y];
if ( size[y] > size[son1[x]] ){ son2[x] = son1[x]; son1[x] = y; }
else if ( size[y] > size[son2[x]] ) son2[x] = y;
f1[x] += f1[y]+size[y];
}
}
void dfs2 ( int x, int f ){
for ( int k = first[x]; k; k = a[k].next ){
int y = a[k].y;
if ( y == f ) continue;
sum[y] = sum[x]+size[x]-size[y];
f2[y] = f2[x]+sum[x]+f1[x]-f1[y]+size[x]-2*size[y];
dfs2 ( y, x );
}
}
bool v[Maxn], bo[Maxn];
int rt1, rt2, ss;
void fr ( int &rt, int x, int msize ){
rt = 0; int ans;
while (x){
int ms, ne;
if ( bo[son1[x]] == true ){ ne = son2[x]; ms = size[son2[x]]; }
else if ( v[son1[x]] == true ){
if ( size[son1[x]]-ss < size[son2[x]] ){ ne = son2[x]; ms = size[son2[x]]; }
else { ne = son1[x]; ms = size[son1[x]]-ss; }
}
else { ne = son1[x]; ms = size[son1[x]]; }
if ( v[x] == true ){ if ( msize-(size[x]-ss) > ms ) ms = msize-(size[x]-ss); }
else if ( msize-size[x] > ms ) ms = msize-size[x];
if ( ms < ans || rt == 0 ){ rt = x; ans = ms; }
else break;
x = ne;
}
}
int anss;
int lca ( int x, int y ){
int i;
if ( dep[x] < dep[y] ) swap ( x, y );
for ( i = 16; i >= 0; i -- ){
if ( dep[fa[x][i]] >= dep[y] ){
x = fa[x][i];
}
}
if ( x == y ) return x;
for ( i = 16; i >= 1; i -- ){
if ( fa[x][i] != fa[y][i] ){
x = fa[x][i];
y = fa[y][i];
}
}
return fa[x][0];
}
void dp ( int x, int f ){
v[x] = true;
for ( int k = first[x]; k; k = a[k].next ){
int y = a[k].y;
if ( y == f ) continue;
bo[y] = true;
ss = size[y];
fr ( rt1, 1, size[1]-size[y] );
int ret1 = f1[rt1]+f2[rt1]-f1[y]-size[y]*(dep[rt1]+dep[y]-2*dep[lca(y,rt1)]);
ss = 0;
fr ( rt2, y, size[y] );
int ret2 = f1[rt2]+f2[rt2]-f2[y]-sum[y]*(dep[rt2]-dep[y]);
if ( ret1+ret2 < anss ) anss = ret1+ret2;
//printf ( "%d %d %d\n", x, y, ret1+ret2 );
bo[y] = false;
dp ( y, x );
}
v[x] = false;
}
int main (){
int i, j, k;
scanf ( "%d", &n );
for ( i = 1; i < n; i ++ ){
int x, y;
scanf ( "%d%d", &x, &y );
ins ( x, y ); ins ( y, x );
}
for ( i = 1; i <= n; i ++ ) scanf ( "%d", &w[i] );
dfs1 ( 1, 0 );
dfs2 ( 1, 0 );
anss = 0x7fffffff;
dp ( 1, 0 );
printf ( "%d\n", anss );
return 0;
}
用线段树保存该区间的 b 的排序结果(归并啊0.0..)
然后修改 a 就去二分区间保存答案..