禁止转载
9.10 全部完工.
A | √ | O ( n 2 m ) O(n^2m) O(n2m). |
---|---|---|
B | √ | O ( n 2 ) O(n^2) O(n2). |
C | √ | O ( n 2 ) O(n^2) O(n2). |
D | √ | O ( n log n ) O(n\log n) O(nlogn). |
E | √ | O ( n 2 ) O(n^2) O(n2). |
F | √ | O ( n log n ) O(n\log n) O(nlogn). |
G | √ | O ( n 2 ) O(n^2) O(n2). |
H | √ | O ( n 3 k ) O(n^3k) O(n3k). |
I | √ | O ( n ) O(n) O(n). |
J | √ | O ( n ) O(n) O(n). |
K | √ | O ( n log n ) O(n\log n) O(nlogn). |
L | √ | O ( ∑ i n i ) O(\sum\limits_{i}n_i) O(i∑ni). |
M | √ | O ( n log n ) O(n\log n) O(nlogn). |
N | √ | O ( n 2 ) O(n^2) O(n2). |
O | √ | O ( n 2 ) O(n^2) O(n2). |
P | √ | O ( n 2 ) O(n^2) O(n2). |
这里是题解汇总
A[POJ 1923]
O ( n 2 m ) O(n^2m) O(n2m)被我水过去了我还是第一次见…
两条直线无非相交或者平行.
显然这个问题给直线编号等于白干,所以我们并不用关心直线的添加顺序.
我们的策略是将两两平行的直线合成一个组一起加进去.
设之前已经放了 i i i条直线,现在准备放 j j j条平行的线,根据上边定义这 j j j条直线都会和前 i i i条直线有交点.
于是一共会新增 i ∗ j i*j i∗j个交点.
再画个图发现,会多出 i ∗ j + 1 i*j+1 i∗j+1个新的区域.
于是我们 d p dp dp方程就写出来了.
由已知转移到未知好写一点,虽然反着来也行.
c o d e code code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 110 , M = 10010;
int f[N][M] , n , m , Tcase;
int main()
{
while(scanf("%d%d" , &n , &m) && (n + m))
{
for(int i = 1 ; i <= n ; i++)
for(int j = 0 ; j <= m ; j++) f[i][j] = 0;
for(int i = 1 ; i <= n ; i++) f[i][0] = i + 1;
for(int i = 0 ; i <= n ; i++)
{
for(int k = 0 ; k <= m ; k++)
if(f[i][k])
for(int j = 1 ; i + j <= n && i * j + k <= m ; j++)
f[i + j][k + j * i] = max(f[i + j][k + i * j] , f[i][k] + j * i + j);
}
if(f[n][m]) printf("Case %d: %d lines with exactly %d crossings can cut the plane into %d pieces at most.\n" , ++Tcase , n , m , f[n][m]);
else printf("Case %d: %d lines cannot make exactly %d crossings.\n" , ++Tcase , n , m);
}
return 0;
}
B[POJ 2677]
这从左到右又从右到左不好做啊.
换个模型.
现在有若干点,给每个点标上黑或者白两种颜色
同种颜色 x x x最近的两个点会产生 d i s ( i , j ) dis(i,j) dis(i,j)的代价.
问最终总代价是多少.
其实这个方向没有关系…
那就好做了.
设 f [ i ] [ j ] f[i][j] f[i][j]表示黑点 x x x最大的点是 i i i,白点 x x x最大是 j j j.
转移直接枚举就行.
总时间复杂度 O ( n 3 ) O(n^3) O(n3),有点赌.
其实两类点没有本质上的区别,因为如果去的是黑,回的是白,白含了最右点,那就把黑白颠倒就行.
而且还有一个性质.
最终黑和白总有一个点转移的时候状态会出现 i − 1 i-1 i−1.
假定 f [ i ] [ j ] f[i][j] f[i][j]中规定 i > j i>j i>j.
那 f [ i ] [ j ] f[i][j] f[i][j]只能由 f [ i − 1 ] [ k ] , k ∈ [ 1 , i − 1 ] f[i-1][k]\ , \ k\in[1,i-1] f[i−1][k] , k∈[1,i−1]转移而来.
于是我们的转移是 O ( n ) O(n) O(n),总时间复杂度 O ( n 2 ) O(n^2) O(n2).
狗血的是,这份代码交 C + + C++ C++是对的,交 G + + G++ G++是 W A WA WA.
c o d e code code:
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cmath>
using namespace std;
const int N = 3010;
const long double inf = 192413842272192472.5;
struct point
{
long double x , y;
} a[N];
int n;
long double f[N][N] , ans;
bool cmp(point a , point b)
{
return a.x < b.x;
}
long double dis(int i , int j)
{
if(j == 0) return 0;
long double x = a[i].x - a[j].x , y = a[i].y - a[j].y;
return sqrt(x * x + y * y);
}
int main()
{
while(scanf("%d" , &n) != EOF)
{
for(int i = 1 ; i <= n ; i++)
for(int j = 1 ; j <= n ; j++) f[i][j] = inf;
for(int i = 1 ; i <= n ; i++) scanf("%Lf%Lf" , &a[i].x , &a[i].y);
if(n == 1)
{
printf("0\n");
continue;
}
sort(a + 1 , a + n + 1 , cmp);
f[2][1] = dis(2 , 1);
for(int i = 3 ; i <= n ; i++)
{
for(int j = 1 ; j < i - 1 ; j++)
{
f[i][j] = min(f[i][j] , f[i - 1][j] + dis(i , i - 1));
f[i][i - 1] = min(f[i][i - 1] , f[i - 1][j] + dis(i , j));
}
}
for(int i = 1 ; i < n ; i++) f[n][n] = min(f[n][n] , f[n][i] + dis(n , i));
printf("%.2Lf\n" , f[n][n]);
}
return 0;
}
C [POJ1239]
另类思想.
首先可以想到一个很自然的想法,设 d p [ i ] [ l ] [ r ] dp[i][l][r] dp[i][l][r]表示处理到第 i i i最后一个数由 [ l , r ] [l,r] [l,r]组成是否可行.
但与其这么设,还不如换个设法.
设 d p [ i ] dp[i] dp[i]表示就目前而言, [ d p [ i ] , i ] [dp[i],i] [dp[i],i]可以作为目前的最后一个数.
转移直接按位判断是否可行就行.
题目要求最后一个数尽量小,那我们 d p dp dp转移的时候取 max \max max即可.
剩下字典序最大.
我们想到位数越多,字典序越大.
于是倒着再扫一遍,用类似上边的做法就行.
复杂度是 O ( n 2 ) O(n^2) O(n2).
c o d e code code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 110;
int n , dpfor[N] , dpback[N];
char s[N];
bool cmp(int s1, int t1, int s2, int t2)
{
while(s[s1] == '0' && s1 <= t1)
s1++;
while(s[s2] == '0' && s2 <= t2)
s2++;
if(s1 > t1 && s2 > t2)
return true;
if((t1 - s1) > (t2 - s2))
return false;
else if((t1 - s1) < (t2 - s2))
return true;
else
{
for(int i = s1, j = s2; i <= t1; i++, j++)
{
if(s[i] > s[j])
return false;
else if(s[i] < s[j])
return true;
}
}
return false;
}
int main()
{
while(scanf("%s" , s + 1) != EOF)
{
n = strlen(s + 1);
if(n == 1 && s[1] == '0') break;
memset(dpfor , 0 , sizeof dpfor);
memset(dpback , 0 , sizeof dpback);
for(int i = 1 ; i <= n ; i++)
{
dpfor[i] = 1;
for(int j = 1 ; j < i ; j++)
if(cmp(dpfor[j] , j , j + 1 , i)) dpfor[i] = max(dpfor[i] , j + 1);
}
int tn = dpfor[n];
dpback[tn] = n;
for(int i = tn - 1 ; s[i] == '0' ; i--) dpback[i] = n;
for(int i = tn - 1 ; i ; i--)
{
for(int j = i ; j <= tn - 1 ; j++)
{
if(cmp(i , j , j + 1 , dpback[j + 1]))
dpback[i] = max(dpback[i] , j);
}
}
int pos = dpback[1] , i = 1;
while(i <= n)
{
while(i <= pos && i <= n) printf("%c" , s[i++]);
if(i <= n) printf(",");
pos = dpback[pos + 1];
}
puts("");
}
return 0;
}
E [POJ 1958]
开始想的背包,因为转移简单.
后来找不到怎么设状态,就上网搜了一下…
只能说非常巧妙.
汉诺塔本质是,必定会把前一部分移到某一座塔,再移动剩下的.
并且两次移动的问题是独立的.
第一次移动就是我们的四塔移动,第二次相当于三塔移动.
于是设 f [ i ] f[i] f[i]表示用四塔移动 i i i个盘的最小步数, d [ i ] d[i] d[i]表示用三塔移动 i i i个盘的最小步数.
得到转移: f [ i ] = min ( f [ j ] ∗ 2 + g [ i − j ] ) f[i]=\min(f[j]*2+g[i-j]) f[i]=min(f[j]∗2+g[i−j]).
d [ i ] d[i] d[i]我们已经有了结论: d [ i ] = 2 i − 1 d[i]=2^i-1 d[i]=2i−1,直接递推求即可.
时间复杂度 O ( n 2 ) O(n^2) O(n2).
c o d e code code:
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N = 13;
int d[N] , f[N] , n;
int main()
{
n = 12;
d[0] = 1;
for(int i = 1 ; i <= 12 ; i++) d[i] = d[i - 1] * 2;
for(int i = 1 ; i <= 12 ; i++) d[i]--;
memset(f , 0x3f , sizeof f);
f[0] = 0;
f[1] = 1 , f[2] = 3;
for(int i = 3 ; i <= 12 ; i++)
for(int j = 1 ; j < i ; j++)
f[i] = min(f[i] , 2 * f[j] + d[i - j]);
for(int i = 1 ; i <= 12 ; i++) cout << f[i] << endl;
return 0;
}
G [CF 1551E]
首先是一个性质.
若当前删去第 i i i个数,则对 [ 1 , i − 1 ] [1,i-1] [1,i−1]的个数没有影响.
设 d p [ i ] [ j ] dp[i][j] dp[i][j]前 i i i个数一共删了 j j j个数后,前 i i i个数最多能有几个符合.
转移枚举 j j j,总时间 O ( n 2 ) O(n^2) O(n2).
c o d e code code:
#include<bits/stdc++.h>
using namespace std;
const int N = 2010;
int T , a[N] , dp[N][N] , n , m;
int main()
{
scanf("%d" , &T);
while(T--)
{
scanf("%d%d" , &n , &m);
for(int i = 1 ; i <= n ; i++) scanf("%d" , &a[i]);
for(int i = 1 ; i <= n ; i++)
for(int j = 0 ; j <= i ; j++)
{
dp[i][j] = max(dp[i][j] , dp[i - 1][j] + ((i - j) == a[i]));
if(j) dp[i][j] = max(dp[i][j] , dp[i - 1][j - 1]);
}
int fl = 0;
for(int i = 0 ; i <= n ; i++)
if(dp[n][i] >= m)
{
printf("%d\n" , i);
fl = 1;
break;
}
if(!fl) printf("-1\n");
for(int i = 0 ; i <= n ; i++) for(int j = 0 ; j <= n ; j++) dp[i][j] = 0;
}
}
H [CF 1551F]
本来想着直接换根统计深度的,结果一直跑WA…
看了题解才发现…
同深度同子树选出来是不合法的…
所以我们需要的是正经的 d p dp dp.
我们设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示 i i i这棵子树深度为 j j j的节点有多少个.
题目中 k k k相当于选一共 k k k棵子树统计答案.
选上 i i i这棵子树相当于乘上 d p [ i ] [ j ] dp[i][j] dp[i][j].
但是需要枚举 k k k,相当的麻烦.
考虑下怎么拆贡献?
发现这玩意不要求顺序.
又启示我们很神奇的东西.
我们将所有的 d p [ i ] [ j ] dp[i][j] dp[i][j]全部列出来,再设 f [ i ] [ j ] f[i][j] f[i][j]表示考虑了前 i i i棵子树,选出 j j j棵的方案数.
那对于当前枚举的子树,我们无非就是选或者不选.
转移是 O ( 1 ) O(1) O(1),枚举是 O ( n k ) O(nk) O(nk).
换根总时间复杂度是 O ( n 3 k ) O(n^3k) O(n3k),不过跑不满.
注意…再把根选上不满足两辆距离相等…犯傻逼了
c o d e code code:
#include<bits/stdc++.h>
using namespace std;
const int N = 110 , M = 210 , mod = 1e9 + 7;
int t , n , k , ans;
int dp[N][N] , f[N][N] , b[N] , len;
vector<int> ch[N];
void dfs(int x , int lst)
{
dp[x][0] = 1;
for(int i = 0 , siz = ch[x].size() ; i < siz ; i++)
{
int v = ch[x][i];
if(v != lst)
{
dfs(v , x);
for(int j = 1 ; j <= n ; j++)
dp[x][j] += dp[v][j - 1];
}
}
}
int solve(int x , int dep)
{
memset(b , 0 , sizeof b);
len = 0;
for(int i = 0 , siz = ch[x].size() ; i < siz ; i++)
{
int v = ch[x][i];
if(dp[v][dep - 1]) b[++len] = dp[v][dep - 1];
}
f[1][0] = 1 ; f[1][1] = b[1];
for(int i = 2 ; i <= len ; i++)
for(int j = 0 ; j <= k ; j++)
{
f[i][j] = f[i - 1][j];
if(j) f[i][j] = (f[i][j] + 1LL * f[i - 1][j - 1] * b[i] % mod) % mod;
}
return f[len][k];
}
int main()
{
scanf("%d" , &t);
while(t--)
{
scanf("%d%d" , &n , &k);
for(int i = 1 , a , b ; i < n ; i++)
{
scanf("%d%d" , &a , &b);
ch[a].push_back(b) ; ch[b].push_back(a);
}
if(k == 2)
{
printf("%d\n" , 1LL * n * (n - 1) / 2 % mod);
for(int i = 1 ; i <= n ; i++) while(!ch[i].empty()) ch[i].pop_back();
continue;
}
ans = 0;
for(int i = 1 ; i <= n ; i++)
{
memset(dp , 0 , sizeof dp);
dfs(i , i);
for(int j = 1 ; j <= n ; j++)
{
memset(f , 0 , sizeof f);
ans = (ans + solve(i , j)) % mod;
}
}
printf("%d\n" , ans);
for(int i = 1 ; i <= n ; i++) while(!ch[i].empty()) ch[i].pop_back();
}
return 0;
}
I [CF 1535C]
这…
设 f [ i ] [ 0 / 1 ] f[i][0/1] f[i][0/1]为处理完前 i i i位,以第 i i i位的 0 / 1 0/1 0/1结尾的子串的个数.
然而这样会算重,因为一个 ? ? ?对一个结尾最多只会产生一次贡献.
换个定义嘛, f [ i ] [ 0 / 1 ] f[i][0/1] f[i][0/1]表示处理完前 i i i位,以第 i i i位的 0 / 1 0/1 0/1结尾的子串最长长度.
这样统计的时候取个 max \max max即可.
总时间复杂度 O ( n ) O(n) O(n).
c o d e code code:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 200010;
int n , t;
ll f[N][2] , ans;
char s[N];
int main()
{
scanf("%d" , &t);
while(t--)
{
scanf("%s" , s + 1);
n = strlen(s + 1);
for(int i = 1 ; i <= n ; i++)
{
if(s[i] == '?')
{
f[i][0] = f[i - 1][1] + 1;
f[i][1] = f[i - 1][0] + 1;
}
else
{
if(s[i] == '1') f[i][0] = 0 , f[i][1] = f[i - 1][0] + 1;
else f[i][1] = 0 , f[i][0] = f[i - 1][1] + 1;
}
}
ans = 0;
for(int i = 1 ; i <= n ; i++)
ans = ans + max(f[i][0] , f[i][1]);
printf("%lld\n" , ans);
for(int i = 1 ; i <= n ; i++) f[i][0] = f[i][1] = 0;
}
}
J [CF 1547E]
非常简单的 d p dp dp.
看到绝对值,肯定想办法搞掉.
搞掉还不简单,分两个方向转移就行了.
于是我们拆开了绝对值,接下来正式分析,以从左往右为例.
t i = min 1 ≤ j ≤ k & & a j ≤ i ( t j + i − a j ) = i + min 1 ≤ j ≤ k & & a j ≤ i ( t j − a j ) t_i=\min\limits_{1\le j\le k\ \&\& \ a_j\le i}(t_{j}+i-a_j)=i+ \min\limits_{1\le j\le k\ \&\& \ a_j\le i}(t_j-a_j) ti=1≤j≤k && aj≤imin(tj+i−aj)=i+1≤j≤k && aj≤imin(tj−aj).
直接维护一个最小值即可,有一处细节是有空调的地方也要转移…
总时间复杂度 O ( n ) O(n) O(n).
c o d e code code:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 300010;
int n , m , T , a[N] , vis[N];
ll t[N] , mmin;
int main()
{
scanf("%d" , &T);
while(T--)
{
scanf("%d%d" , &n , &m);
for(int i = 1 ; i <= n ; i++) t[i] = 1e14 + 7 , vis[i] = 0;
for(int i = 1 ; i <= m ; i++) scanf("%d" , &a[i]) , vis[a[i]] = 1;
for(int i = 1 ; i <= m ; i++) scanf("%lld" , &t[a[i]]);
mmin = 1e14 + 7;
for(int i = 1 ; i <= n ; i++)
{
if(vis[i])
mmin = min(mmin , t[i] - i);
t[i] = min(t[i] , i + mmin);
}
mmin = 1e14 + 7;
for(int i = n ; i >= 1 ; i--)
{
if(vis[i])
mmin = min(mmin , t[i] + i);
t[i] = min(t[i] , mmin - i);
}
for(int i = 1 ; i <= n ; i++) printf("%lld " , t[i]);
puts("");
}
return 0;
}
K [CF1536 C]
我靠我是蠢比…
你这么想.
假设最后一个前缀被分成了若干段.
那由于每段比例都相等,那相加之后还是原来那个比例.
所以比例就是这个前缀的比例.
直接一个 m a p map map搞定.
c o d e code code:
#include<bits/stdc++.h>
using namespace std;
const int N = 500010;
char s[N];
int n , t;
int a , b;
map<pair<int , int> , int> st;
inline int gcd(int a , int b)
{
while(b != 0)
{
int t = a ; a = b ; b = t % a;
}
return a;
}
int main()
{
scanf("%d" , &t);
while(t--)
{
scanf("%d" , &n);
scanf("%s" , s + 1);
st.clear();
a = 0 , b = 0;
for(int i = 1 ; i <= n ; i++)
{
if(s[i] == 'D') a++;
else b++;
int d = gcd(a , b);
st[make_pair(a / d , b / d)]++;
printf("%d " , st[make_pair(a / d , b / d)]);
}
puts("");
}
return 0;
}
L [CF 1529C]
结论猜的:每个点最后取值肯定是两个端点中的一个.
反正好写,写完跑了个样例发现跑对了.
那怎么证明呢?
我们发现当前点取值 x x x会产生的贡献来自两类,一类是比 x x x小的,一类是比 x x x大的.
考虑 x + 1 x+1 x+1,那么所有比 x x x小的数贡献 + 1 +1 +1,比 x x x大的数贡献 − 1 -1 −1.
我们发现,若原本比 x x x小的数比比 x x x大的数要多,那么我们这么操作之后会使答案变大,反之亦然.
那我们只需要将对应操作尽量做多点就行了,等价于取区间端点.
由此考虑,设 f [ 0 / 1 ] [ x ] f[0/1][x] f[0/1][x]表示 x x x取了左/右端点的子树最大贡献.
直接做是 O ( 2 n ) O(2^n) O(2n)的,但我们发现最终每个点无非是两种取一个,那在子树合并的时候以按死根的取值为前提,再进行转移即可.
时间复杂度 O ( n ) O(n) O(n),总 O ( ∑ i n i ) O(\sum\limits_{i}n_i) O(i∑ni).
c o d e code code:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 100010;
vector<int> ch[N];
int n , t , l[N] , r[N];
ll f[2][N];
ll Abs(int x){return x < 0 ? -x : x;}
void dfs(int x , int lst)
{
for(int i = 0 , siz = ch[x].size() ; i < siz ; i++)
{
int v = ch[x][i];
if(v == lst) continue;
dfs(v , x);
f[0][x] = f[0][x] + max(Abs(l[x] - l[v]) + f[0][v] , Abs(l[x] - r[v]) + f[1][v]);
f[1][x] = f[1][x] + max(Abs(r[x] - l[v]) + f[0][v] , Abs(r[x] - r[v]) + f[1][v]);
}
}
int main()
{
scanf("%d" , &t);
while(t--)
{
scanf("%d" , &n);
for(int i = 1 ; i <= n ; i++) f[0][i] = f[1][i] = 0;
for(int i = 1 ; i <= n ; i++) while(!ch[i].empty()) ch[i].pop_back();
for(int i = 1 ; i <= n ; i++) scanf("%d%d" , &l[i] , &r[i]);
for(int i = 1 , a , b ; i < n ; i++)
{
scanf("%d%d" , &a , &b);
ch[a].push_back(b) ; ch[b].push_back(a);
}
dfs(1 , 1);
printf("%lld\n" , max(f[0][1] , f[1][1]));
}
return 0;
}
M [CF 1529C]
我他吗方程早推出来了,额外代价就是找不到怎么处理.
设 f [ i ] f[i] f[i]表示一共 2 i 2i 2i个点的方案.
显然两种情况.
- 留一个所有大区间都包含的位置,相当于子状态 f [ 1 … i − 1 ] f[1\ldots i-1] f[1…i−1],加上就行,可以前缀和.
- 所有区间都是等长,这类贡献设为 g [ i ] g[i] g[i].
那最终就是 f [ i ] = s u m [ i − 1 ] + g [ i ] f[i]=sum[i-1]+g[i] f[i]=sum[i−1]+g[i].
终点在于 g [ i ] g[i] g[i]怎么求.
设我们这一轮的长度为 k k k.
那么这个区间中间会出现若干个空,显然我们的左端点要落在这里面.
最右边的左端点会落在 k − 1 k-1 k−1上.
于是整一个所用到的最右边的点为 2 k − 2 2k-2 2k−2.
于是最终就等价于若干块长度为 2 k − 2 2k-2 2k−2的整块拼成了 2 n 2n 2n,意味着 2 k − 2 2k-2 2k−2还是 2 n 2n 2n的因数.
这个注意到 2 k − 2 2k-2 2k−2与 k k k一一映射,所以我们换成枚举 2 k − 2 2k-2 2k−2,做这个东西可用埃氏筛.
主要时间开销在于 g [ i ] g[i] g[i],总时间为 O ( n log n ) O(n\log n) O(nlogn).
c o d e code code:
#include<bits/stdc++.h>
using namespace std;
const int N = 1000010 , mod = 998244353;
int f[N] , sum[N] , g[N] , n;
void pre(int lmt)
{
g[1] = 1 ;
for(int i = 2 ; i <= lmt ; i++)
{
g[i] = 1LL * (g[i] + 2) % mod;
// cout << g[i] << endl;
for(int j = 2 ; i * j <= lmt ; j++)
g[i * j]++;
}
}
int main()
{
scanf("%d" , &n);
f[1] = 1;
f[2] = 3;
sum[1] = 1 ; sum[2] = 4;
pre(n);
for(int i = 3 ; i <= n ; i++)
{
f[i] = 1LL * (sum[i - 1] + g[i]) % mod;
sum[i] = 1LL * (sum[i - 1] + f[i]) % mod;
}
printf("%d" , f[n]);
return 0;
}
N [CF 1525D]
没头绪,硬着头皮写.
一个很重要的性质:存在一种最优方案使得最终黑点匹配的白点编号递增.
考虑设 f [ i ] [ j ] f[i][j] f[i][j]表示处理到第 i i i个黑点,第 i i i个黑点和 j j j匹配的最小代价.
转移 O ( n 2 ) O(n^2) O(n2),总时间 O ( n 3 ) O(n^3) O(n3),土块做法…
发现时间主要花在确定一个 j j j后的枚举 k k k的这个 O ( n ) O(n) O(n).
将方程列出来:
f [ i ] [ j ] = min k < j ( f [ i − 1 ] [ k ] + ∣ i − j ∣ ) = ∣ i − j ∣ + min k < j ( f [ i − 1 ] [ k ] ) f[i][j]=\min\limits_{k<j}(f[i-1][k]+|i-j|)=|i-j|+\min\limits_{k<j}(f[i-1][k]) f[i][j]=k<jmin(f[i−1][k]+∣i−j∣)=∣i−j∣+k<jmin(f[i−1][k]).
这下好办,记录前缀最小值即可.
时间复杂度 O ( n 2 ) O(n^2) O(n2).
c o d e code code:
#include<bits/stdc++.h>
using namespace std;
const int N = 5010;
int a[N] , b[N] , n , len;
int f[N][N] , mmin[N][N] , ans = 1e9 + 7;
int Abs(int x){return x < 0 ? -x : x;}
int main()
{
scanf("%d" , &n);
for(int i = 1 ; i <= n ; i++) scanf("%d" , &a[i]) , (a[i] == 1) ? b[++len] = i : 0;
if(len == 0)
{
printf("0");
return 0;
}
memset(f , 0x3f , sizeof f);
memset(mmin , 0x3f , sizeof mmin);
for(int i = 1 ; i <= n ; i++)
if(a[i] == 0) f[1][i] = Abs(b[1] - i);
for(int i = 1 ; i <= n ; i++) mmin[1][i] = min(f[1][i] , mmin[1][i - 1]);
for(int i = 2 ; i <= len ; i++)
{
for(int j = 1 ; j <= n ; j++)
{
if(a[j]) continue;
f[i][j] = Abs(b[i] - j) + mmin[i - 1][j - 1];
}
for(int j = 1 ; j <= n ; j++)
mmin[i][j] = min(f[i][j] , mmin[i][j - 1]);
}
for(int i = 1 ; i <= n ; i++) ans = min(ans , f[len][i]);
printf("%d" , ans);
return 0;
}