石子类问题总结
2015-08-18 20:06
441 查看
石子类问题总结
做题需要找兴趣,比如我就比较喜欢做石子题1.石子归并
Description
你有一堆石头质量分别为W1,W2,W3…WN.(W<=100000)现在需要你将石头合并为两堆,使两堆质量的差为最小。
Input
第一行为整数N(1<=N<=20),表示有N堆石子。接下去N行,为每堆石子的质量。
Output
输出合并后两堆的质量差的最小值
Sample Input
5
5
8
13
27
14
Sample Output
3
这是一个背包问题.
什么?
背包问题!
设sum=w1+w2+…+wn;
要使差变小,就要让两堆都向sum/2靠近,相当于有一个箱子容量为sum/2,同时有n个物品,每个物品有一个体积 (正整数)。要求从n个物品中,任取若干个装入箱内,使箱子的剩余空间为最小。(对,这就是NOIP2001普及组的题)
#include<cstdio> #include<iostream> using namespace std; bool f[102000]; int n,a[21],sum; int main() { int i,j; cin>>n; //别问我下面这句话对不对 for(i=1;i<=n;i++) cin>>a[i],sum+=a[i]; int tmp=sum/2; f[0]=true; for(i=1;i<=n;i++) for(j=tmp-a[i];j>=0;j--)//倒着来! if(f[j]) f[j+a[i]]=true; for(i=tmp;i;i--) if(f[i]) break; cout<<sum-2*i; return 0; }
2.石子合并(一)
Description
在一个操场上摆放着一行共n堆的石子。现要将石子有序地合并成一堆。规定每次只能选相邻的两堆合并成新的一堆,并将新的一堆石子数记为该次合并的得分。请编辑计算出将n堆石子合并成一堆的最小得分和将n堆石子合并成一堆的最大得分。
Input
输入第一行为n(n<=100),表示有n堆石子,第二行为n个用空格隔开的整数,依次表示这n堆石子的石子数量(<=1000)
Output
输出将n堆石子合并成一堆的最小得分和将n堆石子合并成一堆的最大得分
Sample Input
3
1 2 3
Sample Output
9 11
dp!(NO 贪心)
f_max[i][j] 表示 把从i到j的石子合并的最大得分,f_min同理
f_max[i][j]=max{ f_max[i][k]+f_max[k+1][j],i<=j<=k-1 }+sum[j]-sum[i-1]
f_min[i][j]=min{ f_min[i][k]+f_min[k+1][j],i<=k<=j-1 }+sum[j]-sum[i-1]
#include<cstdio> #include<iostream> #include<cstring> using namespace std; const int maxn=110; int s[maxn],f[maxn][maxn],i,j,k,n,x,g[maxn][maxn]; int min(int a,int b){return a>b? b:a;} int main() { cin>>n; for(i=1;i<=n;i++) for(j=1;j<=n;j++) g[i][j]=0; memset(f,127/3,sizeof(f));//特别大 ,多少呢? 707406378 for(i=1;i<=n;i++) f[i][i]=0; for(i=1;i<=n;i++) { cin>>x; s[i]=s[i-1]+x; } for(i=n-1;i>0;i--) for(j=i+1;j<=n;j++) for(k=i;k<=j-1;k++) { g[i][j]=max(g[i][j],g[i][k]+g[k+1][j]+s[j]-s[i-1]); f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+s[j]-s[i-1]); } cout<<f[1] <<" "<<g[1] <<endl; return 0; }
3.石子合并(二)
Description
在一个园形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。试设计出1个算法,计算出将N堆石子合并成1堆的最小得分和最大得分。
Input
输入数据的第1行试正整数N,1≤N≤1000,表示有N堆石子.第2行有N个数,分别表示每堆石子的个数。
Output
输出共2行,第1行为最小得分,第2行为最大得分.
Sample Input
4
4 4 5 9
Sample Output
43
54
难道和上一个不一样?
不一样,这是一个环
要是能把环变成直线,再用刚才的动态转移方程就ok啦
for(i=1;i<=n;i++) cin>>a[i],a[i+n]=a[i];
这样n堆石子就变成了2n堆,环就变成了直线
maxn=max(f_max[i][i+n-1],1<=i<=n),
minn=min(f_min[i][i+n-1],1<=i<=n)
Attention!时间复杂度O(n^3) 超时!
需要动态规划优化到O(n^2)
求最小值的话用平行四边形优化:
设p[i][j]表示把从i到j堆石子合并时k的取值,k就是要合并的位置
f_min[i][j]=max{ f[i][j],f_min[i][k]+f_min[k+1][j]+sum[j]-sum[i-1], p[i][j-1]<=k<=p[i+1][j] }
求最大值的话有这样一个结论:f_max[i][j]=max{ f_max[i+1][j],f_max[i][j-1])+sum[j]-sum[i-1]; } 证明略
#include<cstdio> #include<cstring> #include<iostream> using namespace std; int n,f_max[2001][2001],f_min[2001][2001],i,j,k,a[2001],sum[2002],maxn,minn=1000000,p[2001][2010]; int main() { memset(f_min,127/3,sizeof(f_min)); cin>>n; for(i=1;i<=n;i++) cin>>a[i],a[i+n]=a[i]; for(i=1;i<=2*n;i++) sum[i]=sum[i-1]+a[i],f_min[i][i]=0,s[i][i]=i,p[i][i]=i; for(i=2*n-1;i;i--) for(j=i+1;j<=2*n;j++) { f_max[i][j]=max(f_max[i+1][j],f_max[i][j-1])+sum[j]-sum[i-1]; } for(i=2*n-1;i;i--) for(j=i+1;j<=2*n;j++) for(k=p[i][j-1];k<=p[i+1][j];k++) { if(f_min[i][j]>f_min[i][k]+f_min[k+1][j]+sum[j]-sum[i-1]) f_min[i][j]=f_min[i][k]+f_min[k+1][j]+sum[j]-sum[i-1],p[i][j]=k; } for(i=1;i<=n;i++) { maxn=max(f_max[i][i+n-1],maxn); minn=min(minn,f_min[i][i+n-1]); } cout<<minn<<endl<<maxn; }
4.SDOI2008石子合并
Description
在一个操场上摆放着一排N堆石子。现要将石子有次序地合并成一堆。规定每次只能选相邻的2堆石子合并成新的一堆,并将新的一堆石子数记为该次合并的得分。
试设计一个算法,计算出将N堆石子合并成一堆的最小得分。
Input
第一行是一个数N。
以下N行每行一个数A,表示石子数目。
Output
共一个数,即N堆石子合并成一堆的最小得分。
Sample Input
4
1
1
1
1
Sample Output
8
Hint
【数据规模和约定】
对于 30% 的数据,1≤N≤100
对于 60% 的数据,1≤N≤1000
对于 100% 的数据,1≤N≤40000
对于 100% 的数据,1≤A≤200
哈哈 O(n^2)也过不了
GarsiaWachs算法登场 看代码吧
/* GarsiaWachs算法的流程: 【假设a[0]=a[n+1]=inf】 1.从序列的左端开始找第一个a[k-1]≤a[k+1]的k,然后合并a[k-1],a[k] 2.从当前位置开始向左找第一个a[j]>a[k-1]+a[k]的j,把合并后的值插到j的后面,没有就当第一个 3.一直这样重复下去直到剩下一堆 */ #include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> using namespace std; long long ans; int n,a[50100],t; void work(int x) { int i,j; int tmp=a[x]+a[x-1]; ans+=tmp; for( i=x;i<t-1;i++) a[i]=a[i+1];//后面的数向前一位 t--; for( j=x-1;j>0&&a[j-1]<tmp;j--) a[j]=a[j-1]; a[j]=tmp; //注意下面的循环,算法每次是从左往右找第一个k,而找到并归位以后 有可能在前面 出现新的满足条件的k while(j>=2&&a[j]>=a[j-2]) { int d=t-j;work(j-1);j=t-d;} } int main() { int i,j; cin>>n; for(i=0;i<n;i++) scanf("%d",&a[i]); t=1; for(i=1;i<n;i++) { a[t++]=a[i]; while(t>=3&&a[t-3]<=a[t-1]) work(t-2); } while(t>1) work(t-1); cout<<ans; return 0; }
5.质数取石子
Description
DD 和 MM 正在玩取石子游戏。他们的游戏规则是这样的:桌上有若干石子,DD 先取,轮流取,每次必须取质数个。如果某一时刻某一方无法从桌上的石子中取质数个,比如说剩下 0 个或 1 个石子,那么他/她就输了。
DD 和 MM 都很聪明,不管哪方存在一个可以必胜的最优策略,他/她都会按照最优策略保证胜利。于是,DD 想知道,对于给定的桌面上的石子数,他究竟能不能取得胜利呢?
当 DD 确定会取得胜利时,他会说:“不管 MM 选择怎样的取石子策略,我都能保证至多 X 步以后就能取得胜利。”那么,最小的满足要求的 X 是多少呢?注意,不管是 DD 取一次石子还是 MM 取一次石子都应该被计算为“一步”。
Input
第一行有一个整数 N,表示这个输入文件中包含 N 个测试数据。
第二行开始,每行有一个测试数据,其中仅包含一个整数,表示桌面上的石子数。
Output
你需要对于每个输入文件中的 N 个测试数据输出相应的 N 行。
如果对于该种情形是 DD 一定取得胜利,那么输出最小的 X。否则该行输出 -1。
Sample Input
3
8
9
16
Sample Output
1
-1
3
Hint
【样例说明】
当桌上有 8 个石子时,先取的 DD 只需要取走 7 个石子剩下 1 个就可以在一步之后保证胜利,输出 1。
当桌上有 9 个石子时。若 DD 取走 2 个,MM 会取走 7 个,剩下 0 个,DD 输。若 DD 取走 3 个,MM 会取走 5 个,剩下 1 个,DD 输。DD 取走 5 个或者 7 个的情况同理可知。所以当桌上有 9 个石子时,不管 DD 怎么取,MM 都可以让 DD 输,输出 -1。
当桌上有 16 个石子时,DD 可以保证在 3 步以内取得胜利。可以证明,为了在 3 步内取得胜利,DD 第一步必须取 7 个石子。剩下 9 个石子之后,不管第二步 MM 怎么取,DD 取了第三步以后可以保证胜利,所以输出 3。
【数据范围】
输入文件中的数据数 N<=10。
每次桌上初始的石子数都不超过 20000。
有没有博弈论的感觉?
没有,还是dp
g[i]表示当桌上还有i个石子时接下来取的人赢(true)还是输(false)
g[i]=true 当且仅当 存在prime[j]满足g[i-prime[j]]=false
若不存在则g[i]=false
输出步数比较麻烦(以下废话)
因为DD知道了自己会赢,他就会哈皮的和MM说:“我最多在x步之内赢你。”
而MM听到DD这么说,MM还就不信这个邪,她就会每次尽量少取,让DD多取几回
而DD知道MM会这样取,DD为了自己的尊严不受到践踏,他就会每次尽量多取
然而他不知道x最小是多少,他就找到了会编程的你
#include<cstdio> #include<cstring> #include<cmath> #include<iostream> using namespace std; int g[20020],prime[2263],cnt,sp[20020]; int step(int x) { memset(sp,0,sizeof(sp)); for(int i=2;i<=x;i++) { for(int j=1;j<=cnt;j++) { if(x<prime[j]) break; if(g[i]==1&&g[i-prime[j]]==-1) if(sp[i]==0) sp[i]=sp[i-prime[j]]+1;else sp[i]=min(sp[i],sp[i-prime[j]]+1); if(g[i]==-1&&g[i-prime[j]]==1) if(sp[i]==0) sp[i]=sp[i-prime[j]]+1;else sp[i]=max(sp[i],sp[i-prime[j]]+1); } } return sp[x]; } int work(int x) { memset(g,0,sizeof(g)); g[0]=g[1]=-1; for(int i=2;i<=x;i++) { bool flag=false; for(int j=1;j<=cnt;j++) { if(prime[j]>x) break; if(g[i-prime[j]]==-1) { flag=true; g[i]=1; break; }//必胜 } if(!flag) g[i]=-1;//必败 } return g[x]; } void makeprime() { for(int i=2;i<=20000;i++) { bool tmp=false; for(int j=2;j<=sqrt(i);j++) if(i%j==0) tmp=true; if(!tmp) prime[++cnt]=i; } } int main() { makeprime(); int n,i,x,f; cin>>n; for(i=1;i<=n;i++) { cin>>x; f=work(x); if(f==-1) cout<<-1<<endl; if(f==1) cout<<step(x)<<endl; } return 0; }
6.取石子
Description
有n个石子围成一圈,每个石子都有一个权值a[i],你需要取一些石子,每个石子的得分是a[i]*d,d表示这个石子到两边被取了的石子的距离和。
现在你可以取若干石子,使总得分最大。
Input
第1行一个整数n。
接下来n行,每行一个整数a[i]。
Output
仅一个整数,表示最大得分。
Sample Input
5
1
2
3
4
20
Sample Output
80
【样例解释】
取出20后,20旁的石头只剩一侧,长度为4,20*(0+4)=80.距离指两粒石子之间的石子数
Hint
【数据规模】
1≤a[i]≤100000
对于30%的数据,n≤60
对于60%的数据,n≤300
对于100%的数据,n≤100000
这个题需要好好理解,再自己算一算
首先可以肯定的是,当石子个数相同时,权值越大,分数越大
eg: 1 2 3 4 20
取20时的分数是80,取4和20时的分数为 4*(3+0)+20*(0+3)=72
你会发现取的越多分数反而越小
呃~例子不好再找一个
eg: 1 18 3 19 20
取20时的分数是80,
取19和20时的分数为19*(3+0)+20*(0+3)=117>80,
取 18,19,20时的分数为18*(2+1)+19*1+20*(0+1)=93<117
你会发现取的石子超过两个 分数就会变低,而取一个还是取两个这是一个问题
直接在程序里比较一下就行了
找到最大值max1和次大值max2(不需要排序)
ans=max( max1*(n-1),(max1+max2)*(n-2) )
#include<cstdio> #include<algorithm> #include<iostream> using namespace std; int main() { long long n,i,x; long long max1,max2; cin>>n; if(n>=2) cin>>max1>>max2;else cin>>max1; if(max1<max2) swap(max1,max2); for(i=3;i<=n;i++)//寻找最大值和次大值 { scanf("%lld",&x); if(x>max1) { max2=max1;max1=x; }else if(x>max2) max2=x; } // long long a[100010]; // for(i=1;i<=n;i++) scanf("%d",&a[i]); // sort(a+1,a+n+1); // max1=a ;max2=a[n-1]; long long ans1=max1*(n-1); long long ans2=(max1+max2)*(n-2); printf("%lld",max(ans1,ans2)); return 0; }
7.取石子游戏(一)
Description
有一种有趣的游戏,玩法如下
玩家:2人
道具:N颗石子
规则:
1.游戏双方轮流取石子;
2.每人每次取走若干颗石子(最少取1颗,最多取K颗);
3.石子取光,则游戏结束;
4.最后取石子的一方为胜;
假如参与游戏的玩家都非常聪明,问最后谁会获胜?
Input
一行,两个整数N和K。(1<=N<=100000,K<=N)
Output
一行, 一个整数,若先手获胜输出1,后手获胜输出2
Sample Input
23 3
Sample Output
1
博弈论基础
还需要再解释什么吗?
#include<cstdio> #include<iostream> using namespace std; int main() { int n,k; cin>>n>>k; if(n%(k+1)) cout<<1;else cout<<2; return 0; }
8.取石子游戏(二)
Description
有一种有趣的游戏,玩法如下
玩家:2人
道具:N堆石子,每堆石子的数量分别为X1,X2,…,Xn
规则:
1.游戏双方轮流取石子;
2.每人每次选一堆石子,并从中取走若干颗石子(至少取1颗);
3.所有石子被取完,则游戏结束;
4.如果轮到某人取时已没有石子可取,那此人算负;
假如两个游戏玩家都非常聪明,问谁胜谁负?
游戏试玩(只有三堆的情况):
Input
第一行,一个整数N(N<=10000)
第二行,N个空格间隔的整数Xi,表示每一堆石子的颗数(1<=Xi<=100000)。
Output
一行, 一个整数,若先手获胜输出1,后手获胜输出2
Sample Input
4
7 12 9 15
Sample Output
1
经典Nim博弈
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; int main() { int n,x,f; cin>>n>>f; for(int i=1;i<n;i++) { cin>>x; f=f^x; } if(!f) cout<<'2';else cout<<'1'; return 0; }
相关文章推荐
- 基于Android中dp和px之间进行转换的实现代码
- Android中dip、dp、sp、pt和px的区别详解
- LFC1.0.0 版本发布
- 博弈论
- Android px、dp、sp之间相互转换
- IT审计【转载】
- android中像素单位dp、px、pt、sp的比较
- Android对px和dip进行尺寸转换的方法
- Android根据分辨率进行单位转换-(dp,sp转像素px)
- android 尺寸 dp,sp,px,dip,pt详解
- DP问题各种模型的状态转移方程
- TYVJ1193 括号序列解题报告
- 对DP的一点感想
- TYVJ上一些DP的解题报告
- soj1005. Roll Playing Games
- 01背包问题
- LeetCode之Maximum Product Subarray
- DP Flow
- zoj3605 Find the Marble(三维dp)
- Word Break I,II, Triangle,Palindrome Partitioning 动态规划 DP