把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

USACO4.1 Beef McNuggets【数学/结论】

吐槽/心路历程

打开这道题的时候:*&@#%*#?!这不是小凯的疑惑吗?好像还是个加强版的?我疑惑了。原来USACO才是真的强,不知道什么时候随随便便就押中了题目。

对于我这种蒟蒻来说,这种有结论的题真是令人头疼,又不会证明,只能猜,要是猜错了就身败名裂了。

如果是考试的时候写这种题的话,我会直接上一个完全背包,并且价值不会开到题目骗我的那个2,000,000,000,差不多估摸着复杂度能过就这么写。

但是还是没搞懂为什么有一个上界,然后超过那个上界的答案会输出0(明明程序跑出来就是凑不了那么多啊)

就是这组数据:

 4

252

250

254

256

 

 

上网看别人的题解我更疑惑了,都直接甩结论的啊喂,神仙啊,怎么想到的啊喂(ノ"◑ ◑)ノ"(。•́︿•̀。)

可能我这种数论渣渣在考场上只能猜结论然后暴力验证什么了


 

最后还是硬着头皮看了一波最强押题人USACO的英文官方题解(又开始折磨我这个英语渣渣了):

第一种做法:

一来就说如果不存在最大不能买到的块数,所有盒子大小的最大公约数大于1,先特判这种情况(也就是如果这一堆数的最大公因数不为1,就输出0)

然后从小到大进行更新,如果X能被凑出来,那么X+Vi也能被凑出来。

然后又开始甩结论了:如果有连续256个值都能被凑出来,那么从此之后所有的大小都可以被凑出来。

事实上,只要有连续的min(Vi)个值都能被凑出来,那么从此之后所有的大小都可以被凑出来。

(题外话:记笔记 短语  from here on out 从此以后)

所以...我还是要证结论?

 

 

先证这个吧:

只要有连续的min(Vi)个值都能被凑出来,那么从此之后所有的大小都可以被凑出来

设最小的那个数为V0

首先考虑特殊情况,特别地,如果从1V0都可以被凑出来的话,那么后面的数可以由V0k+前面凑出1到V0的方法来凑(k为正整数)

然后是一般情况,假设是从ii+V0都可以被凑出来,相同地,后面的数都可以用V0k++前面凑出ii+V0的方法来凑(k为正整数)

那么问题来了,如果一直没有连续的V0个数都能被凑出来怎么办呢?

那就是不存在不能买到块数的上限,也就是gcd特判的那种情况。

那么,再证公约数那个:

特殊情况:如果有1,所有的方案都能够被凑出来,输出0

考虑到能够被凑出来的数只能是gcd的倍数。因为每个数都可以被分解成为kgcd的形式,能够被凑出来的数也可以被表达为m1k1gcd+m2k2gcd+m3k3gcd+...+mnkngcd=gcd(m1k1+...+mnkn)

一定是gcd的倍数。(要注意反过来不成立,gcd的倍数不一定能被凑出来

如果gcd!=1,那么被凑出来的数是不连续的,也就是没有上界。

如果gcd==1,当i(能够被凑出来的数从小到大排序形成的队列的项数(下标))足够大时,就会形成一串连续的数,当这一串连续的数达到了V0个,就是之前讨论过的那种情况了。

 

想通之后还是不难吧。

 

复制代码
 1 /*
 2 ID: Starry21
 3 LANG: C++
 4 TASK: nuggets            
 5 */  
 6 #include<cstdio>
 7 #include<algorithm>
 8 using namespace std;
 9 #define N 300005
10 bool f[N];
11 int n,a[15];
12 int gcd(int x,int y)
13 {
14     if(y==0) return x;
15     return gcd(y,x%y);
16 }
17 int main()
18 {
19     //freopen("nuggets.in","r",stdin);
20     //freopen("nuggets.out","w",stdout);
21     scanf("%d",&n);
22     for(int i=1;i<=n;i++)
23         scanf("%d",&a[i]);
24     sort(a+1,a+n+1);
25     int d=a[1];
26     bool flag=0;
27     for(int i=2;i<=n;i++)
28     {
29         d=gcd(d,a[i]);
30         if(a[i]==1) flag=1;
31     }
32     if(flag||d>1)
33     {
34         puts("0");
35         return 0;
36     }
37     int cnt=0,j=1,ans=0;
38     f[0]=1;
39     while(1)
40     {
41         for(int i=1;i<=n;i++)
42         {
43             if(j<a[i]) continue;
44             if(f[j-a[i]])
45             {
46                 f[j]=1,cnt++;
47                 break;
48             }
49         }
50         if(!f[j]) cnt=0,ans=j;
51         if(cnt==a[1])
52             break;
53         j++;
54     }
55     printf("%d\n",ans);
56 }
Code
复制代码

 

 


 

第二种做法:

就是小凯的疑惑。

确实疑惑,我太疑惑了,D1T1居然做了一个上午?还有半个月就考联赛了,退役算了哭唧唧。

第一种做法明白了之后,瞬间知道了开头那里为什么超过上界之后直接输出0,因为那就是gcd>1,不存在不能买到块数的上限。

(放了张图片,快要受不了这个老是吞掉我的数学公式的编辑器了,证明放在了小凯的疑惑里)

 

这里不是两个互质的数,但是即使我们只用两个数(可以感性理解,如果在这两个数上再多一些数,原来不能凑成的一些数就可以被凑成,上界(有一定概率)会缩小,所以只用两个数的上界是最大的),这两个数最大就是256,然后上界就是2562562256,在这个范围内进行完全背包就可以了。如果最后出来的答案超过了上界,就是不存在不能买到块数的上限。

复制代码
 1 /*
 2 ID: Starry21
 3 LANG: C++
 4 TASK: nuggets            
 5 */
 6 #include<cstdio>
 7 #include<algorithm>
 8 using namespace std;
 9 #define N 512
10 #define M 65536
11 bool f[M+10];
12 int n,a[15];
13 int main()
14 {
15     freopen("nuggets.in","r",stdin);
16     freopen("nuggets.out","w",stdout);
17     scanf("%d",&n);
18     for(int i=1;i<=n;i++)
19         scanf("%d",&a[i]);
20     sort(a+1,a+n+1);
21     if(a[1]==1)
22     {
23         printf("0\n");
24         return 0;
25     }
26     f[0]=1;
27     int temp;
28     for(int i=0;i<=M;i++)
29     {
30         if(f[i])
31             for(int j=1;j<=n;j++)
32             {
33                 if(i+a[j]>M) continue;
34                 f[i+a[j]]=1;
35             }
36         else temp=i;
37     }
38     if(temp>M-N) temp=0;
39     printf("%d\n",temp);
40 }
Code
复制代码

第三种做法:

网上看到的,很高能,很佩服,可惜没有时间仔细研究(挖坑待填)

这里是超链接

总结

感觉第一种做法要顺理成章一些(虽然我写得不太顺理成章),在考虑凑数的时候手推一些什么性质的时候可能会想到发掘这些结论。而第二种做法对于我这种数学渣渣来说是不会在dp明显会挂掉的情况下用数学去分析范围的。

 

最后,发现了自己去年在洛谷上过的非常暴力的做法(年少无知),对,它过了;当然,在"严谨"的USACO上,它,过不了!

 

复制代码
 1 /*
 2 ID: Starry21
 3 LANG: C++
 4 TASK: nuggets            
 5 */ 
 6 #include<cstdio>
 7 #include<algorithm>
 8 using namespace std;
 9 #define LIMIT 20000000
10 int n,a[15],ans=-1;
11 bool f[LIMIT];
12 int main()
13 {
14     freopen("nuggets.in","r",stdin);
15     freopen("nuggets.out","w",stdout);
16     scanf("%d",&n);
17     for(int i=1;i<=n;i++)
18         scanf("%d",&a[i]);
19     f[0]=true;
20     for(int i=0;i<=LIMIT;i++)
21         if(f[i])
22             for(int j=1;j<=n;j++)
23             {
24                 if(i+a[j]>LIMIT) continue;
25                 f[i+a[j]]=true;
26             }
27                 
28         else ans=i;
29     if(ans==-1||ans>=LIMIT-1) printf("0");
30     else printf("%d",ans);
31 }
复制代码

 

posted @   Starlight_Glimmer  阅读(186)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
浏览器标题切换
浏览器标题切换end
点击右上角即可分享
微信分享提示