超级基础的数据结构
由于这一部分,特别简单,而且白皮书上也讲过,所以快速带过。
(1) 栈, 先进后出,
一般就是手写栈就行了。用一个数组模拟栈,再用一个变量表示栈顶指针。具体的实现,你们以后做题时会看见的。就不讲了。
一般,和树有关的会用的多一些。感觉最多的就是Tarjan了。。。。
(2) 队列,就是栈漏了。先进先出
一般做题用的比较多的是queue , 就是STL提供的那个,当然也可以手写,手写一定要写循环的队列,不然会死的很惨。
还有一个优先队列 priority_queue 也经常用,这个可以当做一个大根堆来用。要是放一个结构体,一定记得重载运算符。
queue 一般用于bfs,SPFA,网络流,之类的,用的还是挺多的。
priority_queue 一般可以用于贪心,DIJ,启发式合并等等。。。
(3) 堆
用优先队列实现就行了。。。
可删除堆
优先队列并不支持删除。
固然可以将元素一个一个取出来,然后再把除了要删去的那个再放回去。但是这样复杂度太高。
可以用一个简便的方法。在维护一个有限队列,用于储存删除的元素,就相当于给那个要删的元素打上一个标记,暂时不删它。
等啥时候,取堆顶时,发现一个已经死了的元素就跳过就行了。
建议封装起来,这样更方便。
对顶堆
用于维护中位数。
维护一个大根堆,一个小根堆,大根堆里放前一半的元素,小根堆里放后一半的元素,保证大小根堆的size大小相差小于2
有道题就是这个模板题.15min切掉。。。
(4) 单调栈&队列
就是它单调了。。。。也操蛋了
它们内部的元素满足单调性,
其中单调队列可以用于斜率优化dp。
有模板题,可以做一做,,,主要是跟一些其他的东西结合起来,有时间的话最后回来再讲这一块。要是没讲也没事,相信你们早晚会会的。。。
DP
如果发现,代码有不换行的,那是本人在远古时期写的代码,那是本人误入歧途的一段时间,望后人千万不要效仿。
坚持代码换行,构建美丽OI 。。。。
那个最长上升子序列,最长公共子序列,背包之类的,应该都会吧。。。(我记得我好像讲过。。)
(1).区间DP
这个算是一道经典题了。
首先,最大值和最小值都是一样的,可以先考虑其中一个。
然后设 (dp[i][j]) 表示把 ([i,j]) 这段区间合并的最大值(最小值同理)。
然后有一个转移方程 (dp[i][j] = max{ dp[i][k] + dp[k+1][j] + sum[j] - sum[i-1]})
(n <= 100) , (O(n^3)) 随便搞。
2.P1063能量项链
题解同上,只是为了放个代码
#include <iostream>
#include <cstdio>
using namespace std;
int n;
int a[210];
int f[210][210];
int main()
{
cin >> n;
for(int i = 1 ; i <= n ; ++i)
{
cin >> a[i];
a[i+n] = a[i];
}
n *= 2;
for(int len = 2; len <= n/2 ; ++len)
for(int i = 1; i + len - 1 <= n ;++i)
{
int j = i + len;
for(int k = i + 1;k <= j - 1;++k)
f[i][j] = max(f[i][j] , f[i][k] + f[k][j] + a[i] * a[j] * a[k]);
}
int ans = 0;
for(int i = 1;i <= n/2;++i) ans = max(ans , f[i][i+n/2]);
cout << ans << endl;
return 0;
}
3.P2339 [USACO04OPEN]Turning in Homework G
设计状态 (f[i][j][0/1]) 表示交完([i,j])这段区间,现在在 0 i , 1 j 处。
然后,,,,就没了。。。
这份代码还是有点丑,就不放了。
4.P5336 [THUSC2016]成绩单
题意
给定一个序列,你可以将其分成任意段,每段非空。假定分了k段,那么代价就是
(A * K + B * sum_{i=1}^K (max_i - min_i)^2)
求最小的代价。
题解
设计状态 f[l][r][x][y] 表示 将[l,r]区间里,弄的只剩权值大小在 [x,y] 之间的数的最小代价。
g[l][r]表示将 [l,r] 全部弄没的最小代价
转移。
f[l][r][x][y] = min(f[l][k][x][y] + f[k+1][r][x][y])
f[l][r][x][y] = min(f[l][k][x][y] + g[k+1][r]
g[l][r] = min(f[l][r][x][y] + A + B * (y - x) * (y - x))
#include<algorithm>
#include<iostream>
#include<cstring>
#include<vector>
#include<cstdio>
#include<ctime>
using namespace std;
#define int long long
int n , A , B;
int s[55];
inline int Abs(int x) { return x < 0 ? -x : x; }
inline int calc1(int x) { return A; }
inline int calc2(int x , int y)
{
int ans = A + (x - y) * (x - y) * B;
ans = min(ans , A + A);
return ans;
}
inline int calc3(int x , int y , int z)
{
int ans = 1e15;
ans = min(ans , calc2(x , y) + A);
ans = min(ans , calc2(x , z) + A);
ans = min(ans , calc2(y , z) + A);
ans = min(ans , A + A + A);
int mx = max(max(x , y) , z) , mn = min(min(x , y) , z);
ans = min(ans , A + B * (mx - mn) * (mx - mn));
return ans;
}
namespace Work
{
int Ans;
void dfs(vector<int> v , int res)
{
if(res >= Ans) return ;
if(v.empty()) { Ans = min(Ans , res); return ; }
if(v.size() <= 3)
{
if(v.size() == 1) Ans = min(Ans , res + calc1(v[0]));
if(v.size() == 2) Ans = min(Ans , res + calc2(v[0] , v[1]));
if(v.size() == 3) Ans = min(Ans , res + calc3(v[0] , v[1] , v[2]));
return ;
}
Ans = min(Ans , res + (int)v.size() * A);
int S = v.size() , mx , mn; vector<int> to; to.clear();
for(int i = 0 , j , k ; i < S ; ++i)
{
mx = v[i] , mn = v[i];
for(j = i ; j < S ; ++j)
{
if(v[j] > mx || v[j] < mn)
{
mx = max(mx , v[j]); mn = min(mn , v[j]);
to.clear();
for(k = 0 ; k <= i - 1 ; ++k) to.push_back(v[k]);
for(k = j + 1 ; k < S ; ++k) to.push_back(v[k]);
dfs(to , res + A + B * (mx - mn) * (mx - mn));
}
}
}
}
int main()
{
int mx = 0 , mn = 1e15;
for(int i = 1 ; i <= n ; ++i) mx = max(mx , s[i]) , mn = min(mn , s[i]);
Ans = min(A * n , A + B * (mx - mn) * (mx - mn));
vector<int> v; v.clear();
for(int i = 1 ; i <= n ; ++i) v.push_back(s[i]);
dfs(v , 0); cout << Ans << '
';
return 0;
}
}
namespace Work1
{
const int N = 52;
int g[N][N] , f[N][N][N][N] , b[N];
int main()
{
memset(g , 0x3f , sizeof g); memset(f , 0x3f , sizeof f);
for(int i = 1 ; i <= n ; ++i) b[i] = s[i];
sort(b + 1 , b + 1 + n); int tot = unique(b + 1 , b + 1 + n) - b - 1;
for(int i = 1 ; i <= n ; ++i) s[i] = lower_bound(b + 1 , b + 1 + tot , s[i]) - b;
for(int i = 1 ; i <= n ; ++i)
{
for(int x = 1 ; x <= tot ; ++x)
for(int y = x ; y <= tot ; ++y)
if(x <= s[i] && s[i] <= y) f[i][i][x][y] = 0; else f[i][i][x][y] = A;
g[i][i] = A;
}
for(int len = 2 ; len <= n ; ++len)
{
for(int i = 1 ; i + len - 1 <= n ; ++i)
{
int j = i + len - 1;
for(int x = 1 ; x <= tot ; ++x)
for(int y = x ; y <= tot ; ++y)
{
for(int k = i ; k < j ; ++k)
f[i][j][x][y] = min(f[i][j][x][y] , min(f[i][k][x][y] + f[k + 1][j][x][y] , f[i][k][x][y] + g[k + 1][j]));
}
for(int x = 1 ; x <= tot ; ++x)
for(int y = x ; y <= tot ; ++y)
g[i][j] = min(g[i][j] , f[i][j][x][y] + A + B * (b[x] - b[y]) * (b[x] - b[y]));
for(int k = i ; k < j ; ++k) g[i][j] = min(g[i][j] , g[i][k] + g[k+1][j]);
}
}
cout << g[1][n] << '
';
return 0;
}
}
signed main()
{
//freopen("c.in" , "r" , stdin); freopen("c.out" , "w" , stdout);
scanf("%lld%lld%lld" , &n , &A , &B);
for(int i = 1 ; i <= n ; ++i) scanf("%lld" , &s[i]);
if(n == 1) cout << calc1(s[1]) << '
';
else
if(n == 2) cout << calc2(s[1] , s[2]) << '
';
else
if(n == 3) cout << calc3(s[1] , s[2] , s[3]) << '
';
else
Work1::main();
fclose(stdin); fclose(stdout);
return 0;
}
/*
3
5 1 5 3 4
50
100 10
205 200 100 200 100 200 100 200 205 200
205 210 215 210 205 200 100 200 205 210
205 200 205 210 205 210 205 200 100 200 205 200 100 200 100 200 100 200 100 200 205 210 215 210 215 210 205 210 215 300
*/
(2).树上DP
1.P1352 没有上司的舞会
这个可以抽象成一个树,若根节点选了,那么儿子节点就不能选。
设 (f[i][0/1]) 表示以i为根的子树,i这个节点选或不选的最大价值。
那么有
(f[i][0] = sum max{ f[son][0] , f[son][1] })
(f[i][1] = sum f[son][0])
#include<iostream>
#include<cstdio>
using namespace std;
const int M = 8050;
int head[M],f[M][2];
int val[M],fa[M];
struct node
{
int to,nex;
}a[M];
int n,cnt;
inline int read()
{
int x = 0,f = 1;char c = getchar();
while(c < '0'|| c >'9'){if(c == '-')f = -1;c = getchar();}
while(c >= '0'&& c<= '9'){x = x * 10 + c- '0';c = getchar();}
return x * f;
}
void add(int from,int to)
{
cnt++;
a[cnt].to = to;
a[cnt].nex = head[from];
head[from] = cnt;
}
void dfs(int k)
{
for(int j = head[k]; j ;j = a[j].nex)
{
int to = a[j].to;
dfs(to);
f[k][1] += f[to][0];
f[k][0] += max(f[to][0],f[to][1]);
}
}
int main()
{
n = read();
for(int i = 1;i <= n;++i)
val[i] = read();
for(int i = 1;i <= n;++i)
{
f[i][1] = val[i];
}
int x,y;
for(int i = 1;i <= n;++i)
{
x = read(); y = read();
if(x == 0 && y == 0)break;
fa[x] = y;
add(y,x);
}
for(int i = 1;i <= n;++i)
if(fa[i] == 0)
{
x = i;
break;
}
dfs(x);
cout<<max(f[x][1],f[x][0]);
return 0;
}
2.P2014 [CTSC1997]选课
同样,很显然的,这个是棵树,首先,为了方便可以增加一个0号节点。将其视为没有fa的点的fa,其权值为0;
那么现在,就成了在一个n+1个节点的树上选择m+1个节点,使得权值和最大。限制条件是,若想选儿子就必须先选父亲。
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long LL;
const int N = 310;
int n,m;
int cnt,head[N],size[N];
int dp[N][N];
struct node{
int to,nex;
}e[N<<1];
int read() {
int x=0,f=1;char c=getchar();
while (c < '0' || c > '9') {if (c == '-') f*=-1;c=getchar();}
while (c >= '0' && c <= '9') {x = (x << 3) + (x << 1) + c - '0'; c=getchar();}
return x * f;
}
void add(int &from,int &to) {
e[++cnt].to = to; e[cnt].nex = head[from]; head[from] = cnt;
}
void dfs(int x,int F) {
int t = 0;
size[x] = 1;
for(int i = head[x]; i ;i = e[i].nex) {
int to = e[i].to;
if(to == F) continue;
dfs(to,x);
size[x] += size[to];
for(int j = m+1 ;j >= 1;--j) for(int k = 0;k < j;++k) {
dp[x][j] = max(dp[x][j],dp[x][j - k] + dp[to][k]);
}
}
}
int main() {
n = read(); m = read();
for(int i = 1;i <= n;++i){
int x = read(); add(x,i);
dp[i][1] = read();
}
dfs(0,0);
cout << dp[0][m+1];
return 0;
}
3.状压dp
1.P1896 [SCOI2005]互不侵犯
状态 (dp[i][S][k]) 表示从上到下,到了第i行,第i行的状态是S,总共放了k个旗子(包括第i行)
(dp[i][S][k] += dp[i-1][S'][k-cnt])
S' 是上一行的状态,cnt是S中1的个数。同时,还要保证合法,比如S和S'要合法。
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<bitset>
#include<cstdio>
#include<vector>
#include<cmath>
#include<queue>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
#define int long long
inline int read()
{
register int x = 0 , f = 0; register char c = getchar();
while(c < '0' || c > '9') f |= c == '-' , c = getchar();
while(c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0' , c = getchar();
return f ? -x : x;
}
int n , k;
int dp[10][1<<9|1][82];
inline int check(int sta) { return !((sta & (sta << 1)) || (sta & (sta >> 1))); }
inline int check(int sta1 , int sta2) { return !((sta1 & sta2) || ((sta1 << 1) & sta2) || ((sta1 >> 1) & sta2)); }
inline int Count(int sta)
{
int res = 0;
for(int i = 1 ; i <= n ; ++i) res += (sta >> (i - 1)) & 1;
return res;
}
signed main()
{
n = read(); k = read();
dp[0][0][0] = 1;
for(int i = 1 ; i <= n ; ++i) // 行号
for(int j = 0 ; j < (1 << n) ; ++j) if(check(j)) // 这一行状态
for(int l = 0 ; l < (1 << n) ; ++l) if(check(l) && check(j , l)) // 上一行状态
for(int s = 0 ; s <= k ; ++s) // 总个数
{
int c = Count(j) , d = Count(l); if(s < c + d) continue;
dp[i][j][s] += dp[i-1][l][s - c];
}
int ans = 0;
for(int i = 0 ; i < (1 << n) ; ++i) ans += dp[n][i][k];
cout << ans << '
';
return 0;
}
上传一个图
最后那个复杂度是(O(n^3))
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
typedef long long LL;
const int N = 1e3+2;
const double Eps = 1e-6;
inline int read()
{
int x = 0 , f = 1; char c = getchar();
while(c < '0' || c > '9') { if(c == '-') f = -f; c = getchar(); }
while(c >= '0' && c <= '9') x = x * 10 + c - '0' , c = getchar();
return x * f;
}
int n , m , tot , ans;
struct node{
double x , y;
}zhu[20];
struct Node{
double a , b;
int sta;
bool operator < (const Node &A) const
{
int cnt = 0;
for(int i = 0 ; i < n ; ++i)
{
if(sta & (1 << i)) cnt++;
if(A.sta & (1 << i)) cnt--;
}
return ((cnt > 0) ? true : false);
}
}hs[400];
int dp[(1<<18)+100];
int dfs(int now)
{
if(now == ((1 << n) - 1)) return 0;
if(dp[now] != -1) return dp[now];
int temp = n;
for(int i = 1 ; i <= tot ; ++i)
if((now | hs[i].sta) > now) temp = min(temp , dfs( now | hs[i].sta ) + 1);
dp[now] = temp;
return temp;
}
void work()
{
n = read(); m = read(); ans = n;
for(int i = 1 ; i <= n ; ++i) scanf("%lf %lf", &zhu[i].x , &zhu[i].y);
tot = 0;
for(int i = 1 ; i <= n ; ++i)
for(int j = i + 1 ; j <= n ; ++j) if(zhu[i].x - zhu[j].x > Eps || zhu[j].x - zhu[i].x > Eps)
{
++tot;
hs[tot].a = ( zhu[i].y - zhu[j].y * zhu[i].x / zhu[j].x ) / ( zhu[i].x * zhu[i].x - zhu[i].x * zhu[j].x );
hs[tot].b = ( zhu[i].y - hs[tot].a * zhu[i].x * zhu[i].x ) / zhu[i].x;
if(hs[tot].a >= Eps) tot--;
}
for(int i = 1 ; i <= tot ; ++i)
{
hs[i].sta = 0;
for(int j = 1 ; j <= n ; ++j)
if( zhu[j].x * zhu[j].x * hs[i].a + hs[i].b * zhu[j].x - zhu[j].y <= Eps && zhu[j].y - zhu[j].x * zhu[j].x * hs[i].a - hs[i].b * zhu[j].x <= Eps )
hs[i].sta |= 1 << (j - 1);
}
for(int i = 1 ; i <= n ; ++i)
hs[i+tot].sta = 1 << (i - 1);
tot += n;
memset( dp , -1 , sizeof dp);
sort(hs + 1 , hs + 1 + tot);
printf("%d
",dfs(0));
}
int main()
{
int T = read();
while( T-->0 ) work();
return 0;
}
/*
1
10 0
7.16 6.28
2.02 0.38
8.33 7.78
7.68 2.09
7.46 7.86
5.77 7.44
8.24 6.72
4.42 5.11
5.42 7.79
8.15 4.99
2
2 0
1.00 3.00
3.00 3.00
5 2
1.00 5.00
2.00 8.00
3.00 9.00
4.00 8.00
5.00 5.00
3
2 0
1.41 2.00
1.73 3.00
3 0
1.11 1.41
2.34 1.79
2.98 1.49
5 0
2.72 2.72
2.72 3.14
3.14 2.72
3.14 3.14
5.00 5.00
0001000010
0000000011
0000000101
0000001010
0000001100
0000010001
0000010010
0000011000
0000100001
0000100010
0000100100
0000101000
0000110000
1001000000
0001000100
0001010000
0001100000
0010000010
0010000100
0010010000
0010100000
1000000001
1000000010
1000000100
1000001000
1000010000
*/
4.期望dp
1.P1850 换教室
(dp[i][j][0/1]) 表示到了第i节课,申请了j次,这一次有没有申请。
然后,枚举,大力讨论即可。。。。。
#include<iostream>
#include<cstdio>
using namespace std;
typedef long long LL;
inline int read() {
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f*=-1;c=getchar();}
while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+c-'0';c=getchar();}
return x*f;
}
int n , m , V , E;
int c[2100] , d[2100];
int f[310][310];
double k[2100];
double dp[2100][2100][2];
int main() {
// freopen("classroom.in","r",stdin);
// freopen("classroom.out","w",stdout);
n = read(); m = read(); V = read(); E = read();
for(int i = 1;i <= n;++i) c[i] = read();
for(int i = 1;i <= n;++i) d[i] = read();
for(int i = 1;i <= n;++i) scanf("%lf",&k[i]);
int x , y , z;
for(int i = 1;i <= V;++i) for(int j = 1;j <= V;++j) f[i][j] = 1e8;
for(int i = 1;i <= E;++i)
{
x = read(); y = read(); z = read();
f[x][y] = f[y][x] = min(f[x][y] , z);
}
for(int kk = 1;kk <= V;++kk)
for(int i = 1;i <= V;++i)
for(int j = 1;j <= V;++j)
if(f[i][j] > f[i][kk] + f[kk][j])
f[i][j] = f[i][kk] + f[kk][j];
for(int i = 1;i <= V;++i) f[i][i] = 0;
for(int i = 0;i <= n;++i) for(int j = 0;j <= m;++j)
{
dp[i][j][0] = 999999999.9;
dp[i][j][1] = 999999999.9;
}
dp[1][0][0] = 0.0;
dp[1][1][1] = 0.0;
for(int i = 2;i <= n;++i)
for(int j = 0;j <= min(i,m);++j)
{
dp[i][j][0] = min(dp[i-1][j][0] + 1.0 * f[c[i-1]][c[i]] , dp[i][j][0]);
if(j) dp[i][j][0] = min(dp[i-1][j][1] + k[i-1] * f[d[i-1]][c[i]] + (1.0-k[i-1]) * f[c[i-1]][c[i]] , dp[i][j][0]);
if(j) dp[i][j][1] = min(dp[i-1][j-1][0] + k[i] * f[c[i-1]][d[i]] + (1-k[i]) * f[c[i-1]][c[i]] , dp[i][j][1] );
if(j >= 2) dp[i][j][1] = min(dp[i-1][j-1][1] + k[i] * k[i-1] * f[d[i-1]][d[i]] + k[i] * (1.0 - k[i-1]) * f[c[i-1]][d[i]]
+ (1.0 - k[i]) * k[i-1] * f[c[i]][d[i-1]] + (1.0 - k[i]) * (1.0 - k[i-1]) * f[c[i-1]][c[i]] , dp[i][j][1]);
}
double ans = 999999999.9;
for(int i = 0;i <= m;++i)
{
ans = min(ans , dp[n][i][1]);
ans = min(ans , dp[n][i][0]);
}
printf("%.2f",ans);
// fclose(stdin);
// fclose(stdout);
return 0;
}
/*
3 2 3 3
2 1 2
1 2 1
0.8 0.2 0.5
1 2 5
1 3 3
2 3 1
*/
2.P2473 [SCOI2008] 奖励关
首先这个题,先请你们自己想想。
按套路
设 (dp[i][j]) 表示到了第i个,已选的状态是j的最大收益。
然后大力讨论,。。。
但是吧,它不对,因为,这样是有后效性的。因为你之前的决定可能会对后面的产生影响。
所以。考虑换一种状态设计。
设 (dp[i][j]) 表示从第i次开始,一直到第n次,之前的选择的状态是j的最大分数。
这样它就没有了后效性。
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<vector>
#include<cstring>
#include<queue>
#include<cmath>
using namespace std;
inline int read()
{
register int x = 0 , f = 0; register char c = getchar();
while(c < '0' || c > '9') f |= c == '-' , c = getchar();
while(c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0' , c = getchar();
return f ? -x : x;
}
int K , n;
int pre[30] , val[30] ;
double f[110][1 << 16];
int main()
{
K = read(); n = read();
for(int i = 1 , x; i <= n ; ++i)
{
val[i] = read(); while(x = read()) pre[i] |= 1 << (x - 1);
}
for(int i = K ; i >= 1 ; --i) for(int j = 0 ; j < (1 << n) ; ++j)
{
for(int k = 1 ; k <= n ; ++k)
if((j & pre[k]) == pre[k]) f[i][j] += max(f[i+1][j] , f[i+1][j | (1 << (k - 1))] + val[k]);
else f[i][j] += f[i+1][j];
f[i][j] /= n;
}
printf("%.6f
" , f[1][0]);
return 0;
}
然后,据某位巨佬说,求期望最好倒着求。
最后,有一些别人的课件,那上面有题,有题,有题。。。题还不少,试着做一下吧。
最好先弄完博客上的。
-- 毒瘤讲课人RQ