20190405 DP模拟赛1总结

概况

重要的模型&&方法:T1,T2,T3

(你这不是在说废话嘛)

分数

T1:100
T2:8
T3:73

失分原因

T2:DFS常数过大,正解未想出(状态设计不对)
T3:本来应该是100,但是忘记特判 (原来只输出1可以拿27)

题目及其题解

T1:Lg P2737 [USACO4.1]麦香牛块Beef McNuggets

难度:普及+
重要:提高+

麦香牛块
(nuggets.cpp/in/out)

题目描述
农夫布朗的奶牛们正在进行斗争,因为它们听说麦当劳正在考虑引进一种新产品:麦香牛块。奶牛们 正在想尽一切办法让这种可怕的设想泡汤。奶牛们进行斗争的策略之一是“劣质的包装”。“看,”, 奶牛们说,“如果你用只有一次能装 3 块、6 块或 10 块的三种包装盒装麦香牛块,你就不可能满足 想要一次只想买 1、2、4、5、7、8、11、14 或 17 块麦香牛块的顾客了。劣质的包装意味着劣质的 产品。”
你的任务是帮助这些奶牛。给出包装盒的种类数 N(1<=N<=10)和 N 个代表不同种类包装盒容纳麦香 牛块个数的正整数(1<=i<=256),输出顾客不能用上述包装盒(每种盒子数量无限)买到麦香牛块的 最大块数。如果在限定范围内所有购买方案都能得到满足,则输出 0。 范围限制是所有不超过 2,000,000,000 的正整数。
输入格式:
第 1 行: 包装盒的种类数 N
第 2 行到 N+1 行: 每个种类包装盒容纳麦香牛块的个数
输出格式:
输出文件只有一行数字:顾客不能用包装盒买到麦香牛块的最大块数或 0(如果在限定范围内所有 购买方案都能得到满足)。
样例输入 (nuggets.in)
3 3 6 10
样例输出:(nuggets.out)
17

思路及分析

和NOIP2017 小凯的疑惑简直一模一样,只是数据范围该大了一些…那怎么办?

考场上:那我就不拿完分呗。我通过把数据范围缩小一点把时间卡到0.5s左右就可以拿部分分啦~~。

就这样把maxn卡到了800005左右,就奇迹 般地AC了
“哇,AC的香气”
其实,最大的数不超过2562
为啥?
所以说:

T1::CODE
#include <bits/stdc++.h>
using namespace std;
const int maxn=800005; 
template<typename t>void read(t &x)
{
    int f=1;x=0;char ch=getchar();
    while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0' && ch<='9'){x=x*10+ch-'0';ch=getchar();}
    x=x*f;
}
bool used[maxn+5];
int n;
int a[300];

int gcd(int a,int b)
{
    if(b==0)return a;
    return gcd(b,a%b);
}

void init()
{
    freopen("nuggets.in","r",stdin);
    freopen("nuggets.out","w",stdout);
}

void readdata()
{
    
    read(n);used[0]=1;
    for(int i=1;i<=n;i++)
    {
        read(a[i]);
    }
    if(n==1) 
    {
        printf("0");
        exit(0);
    }
}

void work()
{
    int tmp=a[1];
    for(int i=2;i<=n;i++)
    {
        tmp=gcd(tmp,a[i]);
    }
    if(tmp!=1)
    {
        printf("0");
        return;
    }
    for(int i=1;i<=n;i++)
    {
        for(int j=0;j<=maxn;j++)
        {
            if(j+a[i]<=maxn && used[j] )used[j+a[i]]=1;
        }
    }
    for(int i=maxn;i>=0;i--)
    {
        if(!used[i])
        {
            printf("%d",i);
            return;
        }
    }
}

int main()
{
//	init();
    readdata();
    work();
    return 0;
}

T2:Lg P1472 奶牛家谱 Cow Pedigrees

奶牛家谱
(nocows.cpp/in/out)
题目描述
农民约翰准备购买一群新奶牛。 在这个新的奶牛群中, 每一个母亲奶牛都生两个小奶牛。这些奶牛间的关系可以用二叉树来表示。这些二叉树总共有N个节点(3 <= N < 200)。这些二叉树有如下性质:
每一个节点的度是0或2。度是这个节点的孩子的数目。
树的高度等于K(1 < K < 100)。高度是从根到最远的那个叶子所需要经过的结点数; 叶子是指没有孩子的节点。
有多少不同的家谱结构? 如果一个家谱的树结构不同于另一个的, 那么这两个家谱就是不同的。输出可能的家谱树的个数除以9901的余数。
输入输出格式
输入格式:
两个空格分开的整数, N和K。
输出格式:
一个整数,表示可能的家谱树的个数除以9901的余数。
输入输出样例
输入样例:
5 3
输出样例:
2

思路及分析

d p [ i ] [ j ] dp[i][j] dp[i][j]表示i个点刚好j层的方案数,然后弄个4层循环,还有组合数什么乱七八糟的,不仅思维难度高,编程难度高,时空复杂度都高!

既然设刚好j层那么麻烦,我们不妨设dp[i][j]表示i个点小于等于j层的方案数,那么最终我们所需的答案就是 d p [ n ] [ k ] − d p [ n ] [ k − 1 ] dp[n][k]-dp[n][k-1] dp[n][k]dp[n][k1]是不是?
via
所以说转移方程:
f ( i , j ) = Σ f ( l , j − 1 ) + f ( n − l − 1 , j − 1 ) f(i,j)=\Sigma f(l,j-1)+f(n-l-1,j-1) f(i,j)=Σf(l,j1)+f(nl1,j1)

T2::CODE
#include <bits/stdc++.h>
#define mod 9901
using namespace std;
const int maxn=210;
template <typename t>void read(t &x)
{
    x=0;int f=1;char ch=getchar();
    while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0' && ch<='9'){x=x*10+ch-'0';ch=getchar();}
    x=x*f;
}
int n,m,dp[maxn][maxn];
void init()
{
    freopen("Lg P","r",stdin);
}

void readdata()
{
    read(n),read(m);
}

void work()
{
    for(int i=1;i<=m;i++)dp[1][i]=1;
    for(int k=1;k<=m;k++)
    {
        for(int i=3;i<=n;i=i+2)
        {
            for(int j=1;j<i;j=j+2)
            {
                dp[i][k]+=dp[j][k-1]*dp[i-j-1][k-1];
                dp[i][k]=dp[i][k]%mod;
            }
        }
    }
    printf("%d",(dp[n][m]-dp[n][m-1]+mod)%mod);
}

int main()
{
//	init();
    readdata();
    work();
    return 0;
}

T3:Lg P2747 [USACO5.4]周游加拿大Canada Tour

加拿大旅游
(tour.cpp/in/out)
题目描述
你赢得了一场航空公司举办的比赛,奖品是一张加拿大环游机票。旅行在这家航空公司开放的最西边的城市开始,然后一直自西向东旅行,直到你到达最东边的城市,再由东向西返回,直到你回到开始的城市。除了旅行开始的城市之外,每个城市只能访问一次,因为开始的城市必定要被访问两次(在旅行的开始和结束)。
当然不允许使用其他公司的航线或者用其他的交通工具。
给出这个航空公司开放的城市的列表,和两两城市之间的直达航线列表。找出能够访问尽可能多的城市的路线,这条路线必须满足上述条件,也就是从列表中的第一个城市开始旅行,访问到列表中最后一个城市之后再返回第一个城市。
输入输出格式
输入格式:
第 1 行: 航空公司开放的城市数 N 和将要列出的直达航线的数量 V。N 是一个不大于 100 的正整数。V 是任意的正整数。
第 2…N+1 行: 每行包括一个航空公司开放的城市名称。城市名称按照自西向东排列。不会出现两个城市在同一条经线上的情况。每个城市的名称都 是一个字符串,最多15字节,由拉丁字母表上的字母组成;城市名称中没有空格。
第 N+2…N+2+V-1 行: 每行包括两个城市名称(由上面列表中的城市名称组成),用一个空格分开。这样就表示两个城市之间的直达双程航线。
输出格式:
Line 1: 按照最佳路线访问的不同城市的数量 M。如果无法找到路线,输出 1。
输入输出样例
输入样例:
8 9
Vancouver
Yellowknife
Edmonton
Calgary
Winnipeg
Toronto
Montreal
Halifax
Vancouver Edmonton
Vancouver Calgary
Calgary Winnipeg
Winnipeg Toronto
Toronto Halifax
Montreal Halifax
Edmonton Montreal
Edmonton Yellowknife
Edmonton Calgary
输出样例:
7

思路及分析

不妨将一条回路拆成两条路且假设两个人同时从起点出发到终点
我们令 d p [ i ] [ j ] dp[i][j] dp[i][j]表示其中一个人甲从起点到 i i i点和另外一个人乙从起点到 j j j点所经过的路径数,不难得出状态转移方程:
f i , j = M a x { f k , j } f_i,_j=Max \{f_k,_j\} fi,j=Max{fk,j}( k k k i i i到相通的边)
f i , j = M a x { f i , k } f_i,_j=Max \{f_i,_k\} fi,j=Max{fi,k}( k k k j j j到相通的边)
注意一下存图和答案为 f n , n − 1 f_n,_n-1 fn,n1即可

T3::CODE
#include <bits/stdc++.h>
using namespace std;
const int maxn=105;
template<typename t>void read(t &x)
{
	int f=1;x=0;char ch=getchar();
	while(ch<'0' || ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch>='0' && ch<='9'){x=x*10+ch-'0';ch=getchar();}
	x=x*f;
}
int n,m;
int road[maxn][maxn],dp[maxn][maxn];

map<string,int>name;

int Min(int a,int b){return a<b?a:b;}
int Max(int a,int b){return a>b?a:b;}

void init()
{
	freopen("tour2.in","r",stdin);
//	freopen("tour.out","w",stdout);
}

void readdata()
{
	read(n),read(m);
	scanf("\n");char s[maxn],s1[maxn],s2[maxn];
	for(int i=1;i<=n;i++)
	{
		scanf("%s\n",s);
		name[s]=i;
	}
	for(int i=1;i<=m;i++)
	{
		scanf("%s %s\n",s1,s2);
		road[name[s1]][name[s2]]=1;
		road[name[s2]][name[s1]]=1;
	}
}

void work()
{
	memset(dp,-0x7f,sizeof(dp));
	dp[1][1]=1;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			for(int k=1;k<=Min(i,j);k++)
			{
				if(k==i && i!=1)continue;
				if(k==j && j!=1)continue;
				if(road[k][i])dp[i][j]=Max(dp[i][j],dp[k][j]+1);
				if(road[k][j])dp[i][j]=Max(dp[i][j],dp[i][k]+1);
			}
		}
	}
	if(dp[n][n]<0)
	{
		printf("1");
		exit(0);
	}
	printf("%d",dp[n][n]-1);
}

int main()
{
//	init();
	readdata();
	work();	
	return 0;
}

附:考场上的记录

2019.5.4DP测试
看题:
T2:
可能只DFS一遍,然后ans*2(对称)
dp[][][]
T1:暴力(完全背包)or 找规律?????!!!注意有Debug
T3:Map…


8.50 T2 暴力放弃 预计:30


9.05 搞T1√预计:50


9.48 搞T3
看成两个点分别从起点和终点出发
9.50
方程:f[甲走到i][乙走到j]走过城市和…
转移:f[j][i]=f[i][j]=Max(f[i][k]+1)(k到j存在路径 && f[i][k]>0 && k>=1)
初始化:f[1][1]=1;
答案:ans=1;
ans=Max(f[i][n])

【不可行!!!】
10.02
方程:f[起点到i][起点到j]走过的路线
转移: f[i][j]=Max(f[k][j]+1)i到k存在路径
f[i][j]=Max(f[i][k]+1)k到j存在路径
初始化:f[][]=-2333333333;f[1][1]=1;
答案: <0–>0
else f[n][n]

10.35:多了一个1???
预计:玄学???!(不知道正确性)


10.37搞T2:
难道是防AK???
做不来…
大样例过不了…

10.48查出一个错误:dfs(num+4,deep+1)
大样例还是过不了…
DP应该打不出来了吧…
暴力放弃…
预计:30


预计
T1:50
T2:30
T3:100 or 0


10.57弄完了扫会儿雷~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值