为了11月份的复赛得一等奖。拼了
一.动态规划
1.关于字段和的一系列问题
(1)最长公共子序列:这个问题是最简单的问题了。公式记住就好。
for(int i=0;i<=len_1;i++) dp[i][0]=dp[0][i]=0;
for(int i=1;i<=len_2;i++){
for(int j=1;j<=n;j++){
if(i==j) dp[i][j]=dp[i-1][j-1]+1;
else dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
这里我讲一下它的优化,容易看到的,肯定是滚动数组优化,
//滚动
for(int l=1,i=0;l<=n;l++,i=(i+1)%2){
for(int j=1;j<=n;j++){
if(l==j)dp[i][j]=dp[(i+1)%2][j-1];
else dp[i][j]=max(dp[(i+1)%2][j],dp[i][j-1]);
}
}
如何输出序列呢?输出所有的公共序列?方法应该很多。
我一般采用的还是再开一个数组保存路径。仔细想一想,产生多种路径的地方在哪里?
对,肯定是dp[i-1][j]=dp[i][j-1]的时候。
所以按照套路,递归输出路径。
B保存的是决策。有1,2,3三种情况。
void Print_LCS(string x,int B[M][N],int i,int j) { /*输出LCS*/
if(!i||!j) return;
if(!B[i][j]) {
Print_LCS(x,B,i-1,j-1);
cout<
else if(B[i][j]==1)
Print_LCS(x,B,i-1,j);
else if(B[i][j]==2)
Print_LCS(x,B,i,j-1);
else {
cout<<'(';
Print_LCS(x,B,i-1,j);
cout<<'+';
Print_LCS(x,B,i,j-1);
cout<<')'; } }
(2)最长上升子序列 HDU 1950
一般的会的就不说了。现在来个快的好记的方法。
int len=1;
int a[maxn];//原数组
int ans[maxn];//保存的数组
for(int i=2;i<=len;i++){
if(a[i]>ans[len]){
ans[++len]=a[i]; //记录末尾
}
else{
int pos=lower_bound(ans,ans+len,a[i])-ans;
ans[pos]=a[i]; //并且更新pos位置的末尾。
}
}
printf("%d",len);
最长不下降,直接改成>=咯
(3)最长公共上升子序列。
朴素算法很好想也很好写。n的三次方的复杂度。像是dp,其实我感觉就是枚举
for(i = 1; i <= n; i++) {
for(j = 1; j <= m; j++) {
f[i][j] = f[i-1][j]; // if(a[i] != b[j])
if(a[i] == b[j]) {
int MAX = 0;
for(k = 1; k <= j-1; k++) if(b[j] > b[k]) { //枚举最大的f[i-1][k]
MAX = max(MAX, f[i-1][k]); }
f[i][j] = MAX+1; } } }
当然这个肯定是需要优化的啦。
通过思考发现,第三层循环找最大值是否可以优化呢?我们能否直接把枚举最大的f[i-1][k]值直接算出来呢?假设存在这么一个序列a[i] == b[j],我们继续看状态转移方程②,会发现b[j] > b[k],即当a[i] == b[j]时,可以推出a[i] > b[k],那么有了这个表达式我们可以做什么呢?可以发现,我们可以维护一个MAX值来储存最大的f[i-1][k]值。即只要有a[i] > a[j]的地方,那么我们就可以更新最大值,所以,当a[i] == b[j]的时候,f[i][j] = MAX+1,即可。
void dp()
{
for(int i = 1; i <= n; i++)
{
int MAX = 0; //维护最大值
for(int j = 1; j <= m; j++)
{
f[i][j] = f[i-1][j]; //a[i] != b[j]
if(a[i] > b[j]) MAX = max(MAX, f[i-1][j]);
if(a[i] == b[j]) f[i][j] = MAX+1;
}
}
int ans = 0;
for(int i = 1; i <= m; i++) ans = max(ans, f[n][i]);
printf("%d\n", ans);
}
而且这样就可以用滚动数组优化了。我就不写了。
前面讲的都是基础的字符串dp,不过差不多了。
下面做道题,稍微转一点弯
给定n个数,求两段连续不重叠子段的最大和。比如1 -1 2 2 3 -3 4 -4 5 -5结果就是 {2,2,3,-3,4} 和 {5},也就是两者的和13。
如果直接找第一大的,第二大的,就会有重复的情况出现?
怎么办呢?
直接正着做一遍保存最大值,倒着做一遍,然后相加即可。
for(i = 1; i <= n; i++) {
scanf("%d", &num[i]);
sum += num[i];
if(sum > tmp) tmp = sum;
a[i] = tmp; // 记录每个字段的最大值。
if(sum < 0) sum = 0; }
tmp = ans = MIN;
sum = 0;
for(i = n; i > 1; i--) {
sum += num[i];
if(sum > tmp) tmp = sum;
if(ans < (a[i-1] + tmp)) ans = a[i-1] + tmp;
if(sum < 0) sum = 0; }
printf("%d\n", ans);
剩下的背包问题就先不讲,很扎实了,就做一些例题来讲吧
在遥远的国家佛罗布尼亚,嫌犯是否有罪,须由陪审团决定。陪审团是由法官从公众中挑选的。先随机挑选n 个人作为陪审团的候选人,然后再从这n 个人中选m 人组成陪审团。选m 人的办法是:控方和辩方会根据对候选人的喜欢程度,给所有候选人打分,分值从0 到20。为了公平起见,法官选出陪审团的原则是:选出的m 个人,必须满足辩方总分D和控方总分P的差的绝对值|D-P|最小。如果有多种选择方案的 |D-P| 值相同,那么选辩控双方总分之和D+P最大的方案即可。
输出:
选取符合条件的最优m个候选人后,要求输出这m个人的辩方总值D和控方总值P,并升序输出他们的编号。
慢慢分析这道题。
最终目的是要差值最小,那么dp显然只和差值有关,但是还有一个条件,差值同是最小,选择和最大的。所以就保存一个和的数组,一个差值的数组。要输出选择,按照老方法,用一个数组去标记。
考虑一下dp[i][j],i表示选择了哪一个。j表示当前差值和,方程就很好列了。
#include
#include
#include
#include
#include
#include
using namespace std;
int dp[21][801];
vector<int> path[21][801];
int main() {
int times=1;
int subtraction[201],_plus[201];
int n,m,i,j,k;
while(~scanf("%d%d",&n,&m) && n && m) {
for(i=0; i//清空vector
for(j=0; j<801; ++j) path[i][j].clear();
memset(dp,-1,sizeof(dp));
int d,p;
for(i = 0; i < n; i++) {
cin>>d>>p;
subtraction[i] = d-p;
_plus[i] = d+p; }
int fix = 20*m;//算出最大范围,防止负数出现
dp[0][fix] = 0;
for(k = 0; k < n; k++)//选择一个
for(i = m-1; i >= 0; i--) { //进行逆推,其他的选择
for(j = 0; j < 2*fix; j++) {
if(dp[i][j] >= 0) {
if(dp[i+1][j+subtraction[k]] <= dp[i][j] + _plus[k]) {
dp[i+1][j+subtraction[k]] = dp[i][j] + _plus[k];
path[i+1][j+subtraction[k]] = path[i][j];//每次更新都要把path全部复制过来,就是因为这个才用的vector,相当于是个三维数组
path[i+1][j+subtraction[k]].push_back(k); } } } }
for(i = 0; dp[m][fix+i] == -1 && dp[m][fix-i] == -1; i++);
int temp = (dp[m][fix+i] > dp[m][fix-i]) ? i : -i;
int sumD = ( dp[m][fix+temp] + temp )/2;
int sumP = ( dp[m][fix+temp] - temp )/2;
printf( "Jury #%d\n", times++ );
printf( "Best jury has value %d for prosecution and value %d for defence:\n", sumD,sumP);
for( i=0; i < m; i++ ) printf( " %d", path[m][fix+temp][i]+1);
printf( "\n\n" ); }
return 0; }
二.搜索
三.贪心
四.最短路
这个问题很重要啊,基本上每次考试都会有。我觉得只需要掌握弗洛伊得和spfa就行啦。
模板题就不放了,直接上一些要转换的题。
年轻的探险家来到了一个印第安部落里。在那里他和酋长的女儿相爱了,于是便向酋长去求亲。酋长要他用10000个金币作为聘礼才答应把女儿嫁给他。探险家拿不出这么多金币,便请求酋长降低要求。酋长说:“嗯,如果你能够替我弄到大祭司的皮袄,我可以只要8000金币。如果你能够弄来他的水晶球,那么只要5000金币就行了。”探险家就跑到大祭司那里,向他要求皮袄或水晶球,大祭司要他用金币来换,或者替他弄来其他的东西,他可以降低价格。探险家于是又跑到其他地方,其他人也提出了类似的要求,或者直接用金币换,或者找到其他东西就可以降低价格。不过探险家没必要用多样东西去换一样东西,因为不会得到更低的价格。探险家现在很需要你的帮忙,让他用最少的金币娶到自己的心上人。另外他要告诉你的是,在这个部落里,等级观念十分森严。地位差距超过一定限制的两个人之间不会进行任何形式的直接接触,包括交易。他是一个外来人,所以可以不受这些限制。但是如果他和某个地位较低的人进行了交易,地位较高的的人不会再和他交易,他们认为这样等于是间接接触,反过来也一样。因此你需要在考虑所有的情况以后给他提供一个最好的方案。 为了方便起见,我们把所有的物品从1开始进行编号,酋长的允诺也看作一个物品,并且编号总是1。每个物品都有对应的价格P,主人的地位等级L,以及一系列的替代品Ti和该替代品所对应的“优惠”Vi。如果两人地位等级差距超过了M,就不能“间接交易”。你必须根据这些数据来计算出探险家最少需要多少金币才能娶到酋长的女儿。
输入包括了多个测试数据。每个测试数据的第一行是两个整数M,N(1<=N<=100),依次表示地位等级差距限制和物品的总数。接下来按照编号从小到大依次给出了N个物品的描述。每个物品的描述开头是三个非负整数P、L、X(X
#include
#include
#include
#define inf 0x7fffffff
using namespace std;
int n,m;
struct edge {
int to,next,v;
} e[50001];
int l[105],dis[105],q[105],head[105],u,d,tot,ans=inf;
bool vis[105];
void addedge(int u,int v,int w) {
tot++;
e[tot].to=v;
e[tot].v=w;
e[tot].next=head[u];
head[u]=tot;
}
bool judge(int x) {
if(l[x]>d+m||l[x]return 0;
return 1;
}
void spfa() {
memset(dis,127/3,sizeof(dis));
int t=0,w=1,i,now;
dis[0]=q[0]=0;
vis[0]=1;
while(t!=w) {
now=q[t];
t++;
i=head[now];
if(t==101)t=0;
while(i) {
if(dis[now]+e[i].vif(!vis[e[i].to]) {
vis[e[i].to]=1;
q[w++]=e[i].to;
if(w==101)w=0;
}
}
i=e[i].next;
}
vis[now]=0;
}
ans=min(dis[1],ans);
}
int main() {
scanf("%d%d",&m,&n);
int p,x,u,w;
for(int i=1; i<=n; i++) {
scanf("%d%d%d",&p,&l[i],&x);
addedge(0,i,p);
for(int j=1; j<=x; j++) {
scanf("%d%d",&u,&w);
addedge(u,i,w);
}
}
for(d=l[1]-m; d<=l[1]; d++) {
spfa();
}
printf("%d",ans);
return 0;
}
五.最小生成树
六.并查集
七.数论
八.线段树
九.高精度
十.模拟
十一.数据结构