并查集
(1)
int find(int x)
{
return x == pre[x] ? x : pre[x] = find(pre[x]);
}
void join(int x,int y)
{
int fx = find(x);
int fy = find(y);
if(fx != fy)
pre[fx] = fy;
}
(2)求最大秩的集合,num数组初始化为1
void join(int x,int y)
{
int p = find(x);
int q = find(y);
if(p != q)
{
pre[p] = q;
num[q] += num[p];
}
}
背包
(1)多重背包
给定背包的体积,把价值尽量分成一半,不等让第一个大于等于第二个
#include<stdio.h>
#include<string.h>
int v[110],m[110],dp[200000];
int n,sum;
int max(int a,int b)
{
return a > b ? a : b;
}
void zeroone_pack(int value)
{
int i;
for(i=sum; i>=value; i--)
{
dp[i] = max(dp[i],dp[i-value] + value);
}
}
void complete_pack(int value)
{
int i;
for(i=value; i<=sum; i++)
{
dp[i] = max(dp[i],dp[i-value] + value);
}
}
void multi_pack()
{
int i,count,k;
for(i=0; i<n; i++)
{
if(v[i] * m[i] >= sum)
{
complete_pack(v[i]);
}
else//转化为01背包
{
k = 1;
count = m[i];
while(k < count)
{
zeroone_pack(k*v[i]);
count -= k;
k *= 2;
}
zeroone_pack(count*v[i]);
}
}
}
int main()
{
int i,sum2;
while(scanf("%d",&n) && n >0)
{
sum = 0;
memset(dp,0,sizeof(v));
for(i=0; i<n; i++)
{
scanf("%d%d",&v[i],&m[i]);
sum += v[i] * m[i];
}
sum2 = sum;
sum /= 2;
multi_pack();
printf("%d %d\n",sum2-dp[sum],dp[sum]);
}
return 0;
}
动态规划
(1)最大字段和,并输出起点和终点
#include<stdio.h>
int main()
{
int t,n,i,j,a,sum,temp,start,end,max;
scanf("%d",&t);
for(i=1; i<=t; i++)
{
max = -1001;sum = 0;temp = 1;//有可能最大的数就是-1000,所以max刚开始设为-1001
scanf("%d",&n);
for(j=1; j<=n; j++)
{
scanf("%d",&a);
sum += a;
if(sum > max)//比最大值大就更新,不管是正是负
{
max = sum;
start = temp;
end = j;
}
if(sum < 0)//前面子段的和对总和没有贡献,要重新开始累加
{
sum = 0;
temp = j + 1;
}
}
printf("Case %d:\n",i);
printf("%d %d %d\n",max,start,end);
if(i != t)
printf("\n");
}
return 0;
}
(2)最大子矩阵(二维最大 字段和转化为一维)
#include <iostream>
#include<cstring>
using namespace std;
int a[150][150],b[150];
int main()
{
int n,i,j,k,sum,max;
while(cin>>n)
{
max = -128;//最小的数是-127,有可能整个数组都是-127,所以max取-128
for(i=0; i<n; i++)
{
for(j=0; j<n; j++)
{
cin>>a[i][j];
}
}
for(i=0; i<n; i++)//起点是第i行,终点是第n-1行的最大字段和
{
memset(b,0,sizeof(b));//数组b保存的前面几列的总和,因为起点不一样,所以每次得清零,所以外面得套一个for语句
for(j=i; j<n; j++)
{
sum = 0;
for(k=0; k<n; k++)
{
b[k] += a[j][k];//前面同一列的总和加上j行在这一列的和
sum += b[k];//起点行到目前行,前面j列的和
if(sum > max)//和一维的最大字段和一样了
{
max = sum;
}
if(sum < 0)
sum = 0;
}
}
}
cout<<max<<endl;
}
return 0;
}
(3)LIS的O(nlogn)算法
题意:有2n个城市坐落在河岸的2边,n个贫穷的城市坐落在一边,n个富裕的城市坐落在另一边,每个贫穷的城市需要从一个特定的富裕城市进口资源,进口和出口的关系是一对一,问最多能建多少座桥且不能交叉
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <queue>
#include <stack>
#include <map>
#include <cstring>
#include <climits>
#include <cmath>
#include <cctype>
using namespace std;
const int maxn = 500010;
int a[maxn],stack1[maxn];
int main()
{
int n,i;
int poor,rich,cas = 1;
while(scanf("%d",&n) != EOF)
{
for(i=0; i<n; i++)
{
scanf("%d%d",&poor,&rich);
a[poor] = rich;
}
int top = 0;
stack1[0] = 0;
for(i=1; i<=n; i++)
{
if(a[i] > stack1[top])
{
stack1[++top] = a[i];
}
else
{
int low = 1,high = top,mid;
while(low <= high)
{
mid = (low + high) / 2;
if(a[i] > stack1[mid])
{
low = mid + 1;
}
else
{
high = mid - 1;
}
}
stack1[low] = a[i];//里面是low,high会导致错误,细节多注意
}
}
printf("Case %d:\n",cas++);
if(top == 1)
{
printf("My king, at most 1 road can be built.\n\n");
}
else
{
printf("My king, at most %d roads can be built.\n\n",top);
}
}
return 0;
}
(4)区间DP(矩阵连乘)
int m[110][110];
int a[110];
int n;
int course(int i,int j)
{
if(m[i][j] != -1)//已经算出来这个值,直接返回
{
return m[i][j];
}
if(i == j)
{
return 0;
}
if(i == j-1)
{
m[i][j] = a[i] * a[i+1] * a[i+2];
return m[i][j];
}
int u = course(i,i) + course(i+1,j) + a[i] * a[i+1] * a[j+1];
int k,t;
for(k=i+1; k<j; k++)
{
t = course(i,k) + course(k+1,j) + a[i] * a[k+1] * a[j+1];
if(t < u)
u = t;
}
m[i][j] = u;
return m[i][j];
}
int main()
{
int i;
while(scanf("%d",&n) != EOF)//矩阵的格式
{
int b;
memset(m,-1,sizeof(m));//设置备忘录。刚开始初始值为-1
for(i=1; i<=n-1; i++)
{
scanf("%d%d",&a[i],&b);//每个矩阵的行和列,第二个和下一个第一个相同,不用存
}
scanf("%d%d",&a[n],&a[n+1]);
int sum = course(1,n);
printf("%d\n",sum);
}
return 0;
}
(5)区间DP(石子合并)
合并的过程只能将相邻的两堆合成一堆,每次合并的花费为这两堆石子的和
从最中间一条向右上角斜着算
int a[210];
int dp[210][210];
int s[210][210];
int main()
{
int n,i,j,k,r;
while(scanf("%d",&n) != EOF)//石子的个数
{
for(i=1; i<=n; i++)//每个石子的重量
{
scanf("%d",&a[i]);
}
for(i=1; i<=n; i++)
{
s[i][i] = a[i];
for(j=i+1; j<=n; j++)
{
s[i][j] = s[i][j-1] + a[j];//得出合并区间i到j的结果
}
}
for(r=2; r<=n; r++)
{
for(i=1; i<=n-r+1; i++)
{
j = i + r - 1;
dp[i][j] = INT_MAX;
for(k=i; k<j; k++)
{
if(dp[i][j] > dp[i][k] + dp[k+1][j])
{
dp[i][j] = dp[i][k] + dp[k+1][j];
}
}
dp[i][j] += s[i][j];
}
}
printf("%d\n",dp[1][n]);
}
return 0;
}
(6)区间DP(括号匹配)
能匹配几个括号
((())) 6 ()()() 6 ([]]) 4 )[)( 0 ([][][) end
char a[110];
int dp[110][110];
int main()
{
int i,j,k,l;
while(scanf("%s",a+1))
{
memset(dp,0,sizeof(dp));
if(!strcmp(a+1,"end"))
break;
int len = strlen(a+1);
for(l=2; l<=len; l++)
{
for(i=1; i<=len-l+1; i++)
{
j = i + l - 1;
if((a[i] == '(' && a[j] == ')') || ( a[i] == '[' && a[j] == ']'))
{
dp[i][j] = dp[i+1][j-1] + 2;
}
for(k=i; k<j; k++)
{
dp[i][j] = max(dp[i][j],dp[i][k] + dp[k+1][j]);
}
}
}
printf("%d\n",dp[1][len]);
}
return 0;
}
(6)LCS
char a[500],b[500];
int dp[500][500];
int main()
{
int m,n,i,j;
while(scanf("%s%s",a,b) != EOF)
{
memset(dp,0,sizeof(dp));
m = strlen(a);
n = strlen(b);
for(i=1; i<=m; i++)
{
for(j=1; j<=n; j++)
{
if(a[i-1] == b[j-1])
dp[i][j] = dp[i-1][j-1] + 1;
else
dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
}
}
printf("%d\n",dp[m][n]);
}
return 0;
}
(7)DP求最短路
飞机只能由标号底的城市飞到标号高的城市,求从1到n的最大有趣度(1,n)是一个城市
int map1[110][110];
int value[110];
int dp[110];
int pre[110];
int n;
void shuchu(int x)
{
if(pre[x] == 0)
{
printf("%d",x);
return;
}
else
{
shuchu(pre[x]);
if(x == n+1)
printf("->1");
else
printf("->%d",x);
}
}
int main()
{
int t;
scanf("%d",&t);
for(int cas=1; cas<=t; cas++)
{
memset(dp,-1,sizeof(dp));
memset(map1,-1,sizeof(map1));
memset(pre,0,sizeof(pre));
int m;
scanf("%d",&n);
for(int i=1; i<=n; i++)
{
int a;
scanf("%d",&a);
value[i] = a;//每个城市的有趣程度
}
value[n+1] = 0;
scanf("%d",&m);
for(int i=1; i<=m; i++)
{
int a,b;
scanf("%d%d",&a,&b);
map1[a][b] = value[b];//a,b之间有路
}
dp[1] = value[1];
for(int i=2; i<=n+1; i++)
{
for(int j=1; j<i; j++)
{
if(map1[j][i] != -1 && dp[j] + map1[j][i] > dp[i])
{
dp[i] = dp[j] + map1[j][i];
pre[i] = j;
}
}
}
printf("CASE %d#\n",cas);
printf("points : %d\n",dp[n+1]);
printf("circuit : ");
shuchu(n+1);
if(cas != t)
printf("\n\n");
else
printf("\n");
}
return 0;
}
(8)有线边树形DP
题目是说有N个人参加party,每个人有一个rating值(可以理解为权值)和一个up(上司的编号),为了保证party的趣味性,每一个人不可以和他的直接上司都参加,问最后的rating和最大
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
#include <queue>
#include <stack>
#include <map>
#include <cstring>
#include <climits>
#include <cmath>
#include <cctype>
typedef long long ll;
using namespace std;
const int maxn = 6010;
vector<int> vec[maxn];//这里相当于一个二维数组,只所以用vector是因为可以迅速知道他的子节点的个数
int head[maxn];//标记父节点
int value[maxn];//标记他本省的值
int dp[maxn][2];
void dfs(int root)
{
int len = vec[root].size();
dp[root][1] = value[root];//上级要参加初始化一下
for(int i=0; i<len; i++)
{
dfs(vec[root][i]);//求每个子节点的最大值
}
for(int i=0; i<len; i++)
{
dp[root][0] += max(dp[vec[root][i]][0],dp[vec[root][i]][1]);//每个子节点都取最大值加起来
dp[root][1] += dp[vec[root][i]][0];//父节点参加,子节点就都不能参加了
}
}
int main()
{
int n;
while(scanf("%d",&n) != EOF)
{
memset(head,-1,sizeof(head));
memset(dp,0,sizeof(dp));
for(int i=1; i<=n; i++)
{
scanf("%d",&value[i]);
vec[i].clear();//每次把向量容器清空
}
int a,b;
while(scanf("%d%d",&a,&b))
{
if(a + b == 0)
break;
head[a] = b;
vec[b].push_back(a);
}
a = 1;
while(head[a] != -1)//找根节点
a = head[a];
dfs(a);
printf("%d\n",max(dp[a][1],dp[a][0]));
}
return 0;
}<span style="font-family:Arial;color:#494949;"><span style="font-size: 14px; line-height: 22.4px; background-color: rgb(244, 237, 227);">
</span></span>
(9)无向边树形DP
有一棵 n 个节点的树,树上每个节点都有一个正整数权值。如果一个点被选择了,那么在树上和它相邻的点都不能被选择。求选出的点的权值和最大是多少?
第一行包含一个整数 n 。
接下来的一行包含 n 个正整数,第 i 个正整数代表点 i 的权值。
接下来一共 n-1 行,每行描述树上的一条边。
1 2 3 4 5
1 2
1 3
2 4
2 5
对于20%的数据, n <= 20。
对于50%的数据, n <= 1000。
对于100%的数据, n <= 100000。
权值均为不超过1000的正整数。
AC代码:(看了半天别人的代码,原来是链式前向星存图)
- #include <iostream>
- #include <cstdio>
- #include <cstdlib>
- #include <algorithm>
- #include <queue>
- #include <stack>
- #include <map>
- #include <cstring>
- #include <climits>
- #include <cmath>
- #include <cctype>
- const int inf = 0x3f3f3f3f;//1061109567
- const int maxn = 100010;
- typedef long long ll;
- using namespace std;
- int m = 0;
- int dp[maxn][2];//dp[i][0]表示第i个节点不取,dp[i][1]表示第i个节点取
- int head[maxn];//存储每个点连接的边的编号
- int value[maxn];//存储每个节点的权值
- struct node
- {
- int to;//存储这个边指向的点
- int next;//存储和起始点连接的下一个边
- }edge[maxn*2];
- void add(int a,int b)
- {
- edge[m].to = b;
- edge[m].next = head[a];
- head[a] = m++;
- edge[m].to = a;
- edge[m].next = head[b];
- head[b] = m++;
- }
- void dfs(int x,int pre)
- {
- for(int i=head[x]; i!=-1; i=edge[i].next)
- {
- int to = edge[i].to;
- if(to == pre)
- continue;
- dfs(to,x);
- dp[x][1] += dp[to][0];//这个点取,子节点肯定不能取
- dp[x][0] += max(dp[to][0],dp[to][1]);//这个点不取,从子节点取或不取用一个最大值
- }
- }
- int main()
- {
- int n;
- while(scanf("%d",&n) != EOF)
- {
- memset(dp,0,sizeof(dp));
- memset(head,-1,sizeof(head));
- for(int i=1; i<=n; i++)
- {
- scanf("%d",&dp[i][1]);
- }
- for(int i=1; i<=n-1; i++)
- {
- int a,b;
- scanf("%d%d",&a,&b);
- add(a,b);
- }
- dfs(1,-1);
- printf("%d\n",max(dp[1][0],dp[1][1]));
- }
- return 0;
- }