模板1

并查集

(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

  算法训练 结点选择  
时间限制:1.0s   内存限制:256.0MB
       
问题描述

有一棵 n 个节点的树,树上每个节点都有一个正整数权值。如果一个点被选择了,那么在树上和它相邻的点都不能被选择。求选出的点的权值和最大是多少?

输入格式

第一行包含一个整数 n 。

接下来的一行包含 n 个正整数,第 i 个正整数代表点 i 的权值。

接下来一共 n-1 行,每行描述树上的一条边。

输出格式
输出一个整数,代表选出的点的权值和的最大值。
样例输入
5
1 2 3 4 5
1 2
1 3
2 4
2 5
样例输出
12
样例说明
选择3、4、5号点,权值和为 3+4+5 = 12 。
数据规模与约定

对于20%的数据, n <= 20。

对于50%的数据, n <= 1000。

对于100%的数据, n <= 100000。

权值均为不超过1000的正整数。

AC代码:(看了半天别人的代码,原来是链式前向星存图)

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <iostream>  
  2. #include <cstdio>  
  3. #include <cstdlib>  
  4. #include <algorithm>  
  5. #include <queue>  
  6. #include <stack>  
  7. #include <map>  
  8. #include <cstring>  
  9. #include <climits>  
  10. #include <cmath>  
  11. #include <cctype>  
  12.   
  13. const int inf = 0x3f3f3f3f;//1061109567  
  14. const int maxn = 100010;  
  15. typedef long long ll;  
  16. using namespace std;  
  17.   
  18. int m = 0;  
  19. int dp[maxn][2];//dp[i][0]表示第i个节点不取,dp[i][1]表示第i个节点取  
  20. int head[maxn];//存储每个点连接的边的编号  
  21. int value[maxn];//存储每个节点的权值  
  22.   
  23. struct node  
  24. {  
  25.     int to;//存储这个边指向的点  
  26.     int next;//存储和起始点连接的下一个边  
  27. }edge[maxn*2];  
  28.   
  29. void add(int a,int b)  
  30. {  
  31.     edge[m].to = b;  
  32.     edge[m].next = head[a];  
  33.     head[a] = m++;  
  34.     edge[m].to = a;  
  35.     edge[m].next = head[b];  
  36.     head[b] = m++;  
  37. }  
  38.   
  39. void dfs(int x,int pre)  
  40. {  
  41.     for(int i=head[x]; i!=-1; i=edge[i].next)  
  42.     {  
  43.         int to = edge[i].to;  
  44.         if(to == pre)  
  45.             continue;  
  46.         dfs(to,x);  
  47.         dp[x][1] += dp[to][0];//这个点取,子节点肯定不能取  
  48.         dp[x][0] += max(dp[to][0],dp[to][1]);//这个点不取,从子节点取或不取用一个最大值  
  49.     }  
  50. }  
  51.   
  52. int main()  
  53. {  
  54.     int n;  
  55.     while(scanf("%d",&n) != EOF)  
  56.     {  
  57.         memset(dp,0,sizeof(dp));  
  58.         memset(head,-1,sizeof(head));  
  59.         for(int i=1; i<=n; i++)  
  60.         {  
  61.             scanf("%d",&dp[i][1]);  
  62.         }  
  63.         for(int i=1; i<=n-1; i++)  
  64.         {  
  65.             int a,b;  
  66.             scanf("%d%d",&a,&b);  
  67.             add(a,b);  
  68.         }  
  69.         dfs(1,-1);  
  70.         printf("%d\n",max(dp[1][0],dp[1][1]));  
  71.     }  
  72.     return 0;  
  73. }  




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值