您的位置:首页 > 其它

石子类问题总结

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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  石子 杂题 dp 博弈论